Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement include and exclude filtering when calling registerAutoDetectedExtensions in MutableExtensionRegistry #4120

Conversation

YongGoose
Copy link
Contributor

fix #3717

Overview

This PR enables the activation of specific global extensions in JUnit Jupiter.

I've already worked on this in my personal repository, and since there’s a large amount of code changes, I decided to split it into two PRs.

YongGoose@a2ef07f
I plan to proceed in the following two ways.

  1. Add an include method to ClassNamePatternFilterUtils.
  2. Implement include and exclude filtering when calling registerAutoDetectedExtensions in MutableExtensionRegistry.

I hereby agree to the terms of the JUnit Contributor License Agreement.


Definition of Done

Issue: junit-team#3717
Signed-off-by: yongjunhong <kevin0928@naver.com>
@YongGoose
Copy link
Contributor Author

@marcphilipp

There are a few points we need to discuss further.

  1. When an extension is both included and excluded, exclude takes priority. WDYT?
  2. Should the log display the extensions that were either included or excluded?

…tension/MutableExtensionRegistry.java

Co-authored-by: Marc Philipp <mail@marcphilipp.de>
@YongGoose YongGoose force-pushed the feature/enable-specific-global-extensions-in-junit branch 2 times, most recently from 39f138a to 15467ad Compare November 15, 2024 13:15
Issue: junit-team#3717
Signed-off-by: yongjunhong <kevin0928@naver.com>
@YongGoose YongGoose force-pushed the feature/enable-specific-global-extensions-in-junit branch from 15467ad to 4a896f1 Compare November 16, 2024 12:48
Copy link
Member

@marcphilipp marcphilipp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're getting there but I found a few more things. 😅

@marcphilipp
Copy link
Member

When an extension is both included and excluded, exclude takes priority. WDYT?

Yes, I think that's how it generally works.

Should the log display the extensions that were either included or excluded?

Yes, I think that's a good idea. We should use Logger.config to log the classes that don't make it through the combined predicate.

When used on the module path, `ServiceLoader` determines its calling
module from the stack and check whether its module descriptor contains a
`uses` directive for the service type. If not, it will throw an
exception. Therefore, `ServiceLoader.load` needs to be called directly
in the module using the service but the filtering can still occur in
junit-platform-commons.
@marcphilipp
Copy link
Member

@YongGoose I sent a PR to your PR branch: YongGoose#1

Initially, I wanted to reuse the new ServiceLoaderUtils in LauncherFactory. However, the tests made me realize we can't call ServiceLoader.load from junit-platform-commons because it reflectively validates its caller. See YongGoose/junit5@d5a3511 (#1) for details.

Please merge my PR into your branch so we can incorporate its commits in this PR. I didn't want to push to your PR branch directly to avoid conflicts.

…cution-listener-instantiation

Avoid instantiating deactivated TestExecutionListeners
@YongGoose
Copy link
Contributor Author

@YongGoose I sent a PR to your PR branch: YongGoose#1

Initially, I wanted to reuse the new ServiceLoaderUtils in LauncherFactory. However, the tests made me realize we can't call ServiceLoader.load from junit-platform-commons because it reflectively validates its caller. See YongGoose/junit5@d5a3511 (#1) for details.

Please merge my PR into your branch so we can incorporate its commits in this PR. I didn't want to push to your PR branch directly to avoid conflicts.

@marcphilipp
Thank you for your detailed explanation and for sending the PR. 👍

I understand the issue with ServiceLoader.load's caller validation and the resulting limitations in junit-platform-commons. I've reviewed the solution you proposed (YongGoose/junit5@d5a3511), and it seems like a good approach.

I'll merge the PR into my branch as requested.

YongGoose and others added 4 commits November 19, 2024 22:36
…tension/MutableExtensionRegistry.java

Co-authored-by: Marc Philipp <mail@marcphilipp.de>
…tension/MutableExtensionRegistry.java

Co-authored-by: Marc Philipp <mail@marcphilipp.de>
…tension/MutableExtensionRegistry.java

Co-authored-by: Marc Philipp <mail@marcphilipp.de>
…tension/MutableExtensionRegistry.java

Co-authored-by: Marc Philipp <mail@marcphilipp.de>
@marcphilipp
Copy link
Member

@YongGoose Please let me know when you're done with your changes and I should re-review.

Issue: junit-team#3717
Signed-off-by: yongjunhong <kevin0928@naver.com>
@marcphilipp marcphilipp self-requested a review November 20, 2024 17:50
Issue: junit-team#3717
Signed-off-by: yongjunhong <kevin0928@naver.com>
@YongGoose YongGoose force-pushed the feature/enable-specific-global-extensions-in-junit branch from edce1d6 to 02c91b5 Compare November 21, 2024 14:47
Issue: junit-team#3717
Signed-off-by: yongjunhong <kevin0928@naver.com>
@YongGoose YongGoose requested a review from marcphilipp December 4, 2024 06:28
Comment on lines 292 to 304
[[automatic-extension-registration-pattern-matching]]
===== Pattern Matching Syntax

Pattern matching syntax is used to automatically detect and register extensions. Here are some examples of pattern matching syntax:

- `*`: includes all extensions.
- `org.junit.*`: includes every extension under the `org.junit` base package and any of its subpackages.
- `*.MyExtension`: includes every extension whose simple class name is exactly `MyExtension`.
- `*System*`: includes every extension whose FQCN contains `System`.
- `*System*, *Dev*`: includes every extension whose FQCN contains `System` or `Dev`.
- `org.example.MyExtension, org.example.TheirExtension`: includes extensions whose FQCN is exactly `org.example.MyExtension` or `org.example.TheirExtension`.

> **Note**: A class that matches both an inclusion and exclusion pattern will be excluded.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should instead just link to the running-tests-config-params-deactivation-pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not very skilled at documentation work yet 😅

[[automatic-extension-registration-pattern-matching]]
===== Pattern Matching Syntax

Refer to <<running-tests-config-params-deactivation-pattern>> for details.

Would this change be fine?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't checked the history but I think the main reason this indirection exists in the other places is because we didn't want to break existing links. Since we're introducing new content here, I think we can simplify and link to the target section directly, i.e. without introducing another subsection.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made this change in 13c1b19

YongGoose and others added 5 commits December 5, 2024 19:05
…tension/MutableExtensionRegistry.java

Co-authored-by: Marc Philipp <mail@marcphilipp.de>
Issue: junit-team#3717
Signed-off-by: yongjunhong <kevin0928@naver.com>
Issue: junit-team#3717
Signed-off-by: yongjunhong <kevin0928@naver.com>
@YongGoose
Copy link
Contributor Author

@marcphilipp

Thank you very much for correcting my mistakes. 6e5b161
For the documentation work in other PRs, I will take more time to analyze and improve my approach to ensure better quality.

Copy link
Member

@marcphilipp marcphilipp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add integration tests for this to ExtensionRegistryTests. It currently verifies that ServiceLoaderExtension gets registered. If you add another one, you should be able to configure the two new config params in jupiter-tests/src/test/resources/junit-platform.properties so that one gets included and the other excluded.

Comment on lines 170 to 178
/**
* A blank pattern used for class name filtering: {@value}
*
* <p>This constant is used to represent an empty or blank pattern in class name filtering operations.
*
* @see ClassNamePatternFilterUtils#BLANK
*/
public static final String BLANK = ClassNamePatternFilterUtils.BLANK;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems weird to me to introduce a constant for an empty string here. Let's keep it internal and remove it from here, ok?

@marcphilipp
Copy link
Member

For the documentation work in other PRs, I will take more time to analyze and improve my approach to ensure better quality.

No worries, that always requires a lot of context. I took a stab at it and pushed a commit to your branch.

@YongGoose
Copy link
Contributor Author

Please also add integration tests for this to ExtensionRegistryTests

I couldn't work on this because I had an interview this week. I apologize for the delay.

Can I proceed with the integration test after mocking ConfigurationParameters as shown in the code below?

@Test
void registryIncludesAndExcludesAutoDetectedExtensionsBasedOnConfiguration() {
	ConfigurationParameters configurationParameters = mock(ConfigurationParameters.class);
	when(configurationParameters.get("junit.jupiter.extensions.autodetection.enabled"))
			.thenReturn(Optional.of("true"));
	when(configurationParameters.get("junit.jupiter.extensions.autodetection.include"))
			.thenReturn(Optional.of("org.junit.jupiter.engine.extension.AutoCloseExtension"));
	when(configurationParameters.get("junit.jupiter.extensions.autodetection.exclude"))
			.thenReturn(Optional.of("org.junit.jupiter.engine.extension.DisabledCondition"));

	JupiterConfiguration configuration = new DefaultJupiterConfiguration(configurationParameters, null);

	registry = createRegistryWithDefaultExtensions(configuration);
	assertExtensionRegistered(registry, AutoCloseExtension.class);
}

I tried implementing it using junit-platform.properties, but I couldn't figure out how to do it because JupiterConfiguration was mocked.

@marcphilipp
Copy link
Member

marcphilipp commented Dec 13, 2024

I couldn't work on this because I had an interview this week. I apologize for the delay.

No worries! I hope the interview went well!

Can I proceed with the integration test after mocking ConfigurationParameters as shown in the code below?

LGTM 👍

@YongGoose
Copy link
Contributor Author

YongGoose commented Dec 13, 2024

After much consideration, I’ve concluded that mocking the getFilterForAutoDetectedExtensions method in the configuration is the correct approach. 😅

However, the getFilterForAutoDetectedExtensions method returns a Predicate<Class<? extends Extension>>.
Could you help me a bit with writing the thenReturn part for this?

@Test
void registryIncludesAndExcludesAutoDetectedExtensionsBasedOnConfiguration() {
	when(configuration.isExtensionAutoDetectionEnabled()).thenReturn(true);
	when(configuration.getFilterForAutoDetectedExtensions()).thenReturn(/* need help..  */);
	registry = createRegistryWithDefaultExtensions(configuration);

	assertExtensionRegistered(registry, ServiceLoaderExtension.class);
}

Additionally, through debugging, I confirmed that the intended Extension is applied based on the value of the property.

  • junit-platform.properties
junit.jupiter.extensions.autodetection.enabled=true
junit.jupiter.extensions.autodetection.include=org.junit.jupiter.engine.extension.ServiceLoaderExtension
junit.jupiter.extensions.autodetection.exclude=org.junit.jupiter.engine.extension.ConfigLoaderExtension
  • org.junit.jupiter.api.extension.Extension
org.junit.jupiter.engine.extension.ServiceLoaderExtension
org.junit.jupiter.engine.extension.ConfigLoaderExtension

image

@YongGoose
Copy link
Contributor Author

No worries! I hope the interview went well!

Thank you as always.
I will do my best for the JUnit project❕

Issue: junit-team#3717
Signed-off-by: yongjunhong <kevin0928@naver.com>
…xtensions-in-junit' into feature/enable-specific-global-extensions-in-junit
@YongGoose
Copy link
Contributor Author

After much consideration, I’ve concluded that mocking the getFilterForAutoDetectedExtensions method in the configuration is the correct approach. 😅

However, the getFilterForAutoDetectedExtensions method returns a Predicate<Class<? extends Extension>>. Could you help me a bit with writing the thenReturn part for this?

I made this change in ec13b6d !

@marcphilipp marcphilipp merged commit 16c6f72 into junit-team:main Dec 16, 2024
16 checks passed
@marcphilipp
Copy link
Member

@YongGoose Thank you for your contribution! I improved a few mostly minor things in the last few commits.

@YongGoose YongGoose deleted the feature/enable-specific-global-extensions-in-junit branch December 18, 2024 03:29
marcphilipp added a commit that referenced this pull request Jan 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Introduce mechanism to enable specific global extensions in JUnit Jupiter
2 participants