junit5 icon indicating copy to clipboard operation
junit5 copied to clipboard

LauncherConfig does not effectively use system properties due to LauncherConfig.DEFAULT and unpopulated ConfigurationParameters context

Open GAATTC0 opened this issue 7 months ago • 3 comments

Issue Description: In JUnit Platform LauncherFactory , the no-argument LauncherFactory.create() method directly uses LauncherConfig.DEFAULT. This LauncherConfig.DEFAULT instance has its auto-registration booleans (e.g., testExecutionListenerAutoRegistrationEnabled) hardcoded to true within LauncherConfig.Builder and is not initialized based on values from an externally populated ConfigurationParameters instance.

This means directly invoking LauncherFactory.openSession() or LauncherFactory.create()(I tried both IDEA and gradle, they all use the LauncherFactory.openSession() and cannot modify) cannot effectively use system properties to control these common listener configuration aspects, as the necessary LauncherConfig booleans are predetermined and the ConfigurationParameters context used for deactivation isn't populated from external sources by LauncherFactory itself.

a simple code discribe:

public class LauncherFactory {

    // IDEA or Gradle etc., start LauncherSession use this method
    public static LauncherSession openSession() throws PreconditionViolationException {
        return openSession(LauncherConfig.DEFAULT);
    }

    @API(status = STABLE, since = "1.10")
    public static LauncherSession openSession(LauncherConfig config) throws PreconditionViolationException {
        // here can we set system property to affect `testExecutionListenerAutoRegistrationEnabled`
        LauncherConfigurationParameters configurationParameters = LauncherConfigurationParameters.builder().build();
        return new DefaultLauncherSession(collectLauncherInterceptors(configurationParameters),
                () -> createLauncherSessionListener(config), () -> createDefaultLauncher(config, configurationParameters));
    }

    private static DefaultLauncher createDefaultLauncher(LauncherConfig config,
                                                         LauncherConfigurationParameters configurationParameters) {
        // ...
        registerTestExecutionListeners(config, launcher, configurationParameters);
        return launcher;
    }

    private static void registerTestExecutionListeners(LauncherConfig config, Launcher launcher,
                                                       LauncherConfigurationParameters configurationParameters) {
        // this always return true:
        if (config.isTestExecutionListenerAutoRegistrationEnabled()) {
            loadAndFilterTestExecutionListeners(configurationParameters).forEach(
                    launcher::registerTestExecutionListeners);
        }
        config.getAdditionalTestExecutionListeners().forEach(launcher::registerTestExecutionListeners);
    }

    private static Stream<TestExecutionListener> loadAndFilterTestExecutionListeners(
            ConfigurationParameters configurationParameters) {
        // this `DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME` can be read from system properties
        String deactivatedListenersPattern = configurationParameters.get(
                DEACTIVATE_LISTENERS_PATTERN_PROPERTY_NAME).orElse(null);
        // ...
    }
}

Suggested Solution: 1.Enhance LauncherFactory.create() (no-arg version): Instead of directly using LauncherConfig.DEFAULT, this method could internally:

  • Create a ConfigurationParameters instance that is populated from standard sources (e.g., system properties, junit-platform.properties via a mechanism similar to LauncherConfigurationParametersAdapter).
  • Use this populated ConfigurationParameters instance to build a LauncherConfig (e.g., by passing relevant string values from it to LauncherConfig.builder().enableTestExecutionListenerAutoRegistration(converter.toBoolean(params.get(...))), etc.).
  • This newly built LauncherConfig would then be passed to create(LauncherConfig config).
  • The populated ConfigurationParameters instance (or one derived from it) should also be the one used by loadAndFilterTestExecutionListeners. 2.Modify LauncherConfig.DEFAULT's initialization: LauncherConfig.DEFAULT could be initialized (lazily or at static init time) with a LauncherConfig instance that is built by consulting a globally accessible, pre-populated ConfigurationParameters instance (which itself loads from system properties, etc.). This would make LauncherConfig.DEFAULT inherently reflect external configuration. This might be a more breaking change.

this can helps people who use the IDEA or gradle integrated test runner to control behavior in LauncherConfig

GAATTC0 avatar May 17 '25 07:05 GAATTC0

Thanks for raising this issue! If I understand correctly, you're asking for a way to control the boolean flags in LauncherConfig via system properties or configuration parameters. Could you please share your concrete use case? Is there a TestEngine/LauncherSessionListener/LauncherDiscoveryListener/TestExecutionListener/PostDiscoveryFilter that you wish to deactivate?

I'm not sure what you mean by "unpopulated ConfigurationParameters context" as they are currently not read from configuration parameters. Could you please elaborate?


Related existing configuration parameters:

  • junit.platform.execution.listeners.deactivate which allows deactivating ServiceLoader-registered TestExecutionListeners
  • junit.jupiter.extensions.autodetection.include/exclude which allows filtering which Jupiter Extensions get auto-registered (if auto-registration is enabled via junit.jupiter.extensions.autodetection.enabled configuration parameter

marcphilipp avatar May 17 '25 09:05 marcphilipp

thank you for reply! this is my use case: my project dependency including a jar which could not be modified, and the jar provided a spi service for TestExecutionListener,the listener import some class which cannot import to project dependency because of conflict (logback and log4j) so when a test run, the listener throws ClassNotFoundException during init

i tried use junit.platform.execution.listeners.deactivate to disable this listener, but i find it is call listener.getClass().getName() to filter,exception happened drying getClass.

i tried use testExecutionListenerAutoRegistrationEnabled to disable all listener, but idea/gradle both use the LauncherFactory.openSession() without argument, so there is no way to set it to false.

so , as a normal developer, we have no idea to prevent listener's exception during class init. a temporary way is overwrite it in project file, and load preferentially in /classes path

GAATTC0 avatar May 21 '25 15:05 GAATTC0

Thanks for providing more context! I think being able to disable these via configuration parameters makes sense. I'll discuss with the team and will get back to you.

marcphilipp avatar May 26 '25 07:05 marcphilipp