diff --git a/documentation/modules/ROOT/partials/release-notes/release-notes-6.1.0-M2.adoc b/documentation/modules/ROOT/partials/release-notes/release-notes-6.1.0-M2.adoc index c38852832522..a382adfc6de5 100644 --- a/documentation/modules/ROOT/partials/release-notes/release-notes-6.1.0-M2.adoc +++ b/documentation/modules/ROOT/partials/release-notes/release-notes-6.1.0-M2.adoc @@ -38,6 +38,8 @@ repository on GitHub. objects. * New `selectClasspathResources(String...)` and `selectClasspathResources(List configFileUrls = findConfigFiles(configFileName); + // Reverse the list, so that files early on the classpath take priority where there's a conflict + Collections.reverse(configFileUrls); + + for (URL url : configFileUrls) { + loadClasspathResource(url, props); } } catch (Exception ex) { @@ -269,15 +272,11 @@ private static Properties loadClasspathResource(String configFileName) { return props; } - private static @Nullable URL findConfigFile(String configFileName) throws IOException { + private static List findConfigFiles(String configFileName) throws IOException { ClassLoader classLoader = ClassLoaderUtils.getDefaultClassLoader(); List urls = Collections.list(classLoader.getResources(configFileName)); - if (urls.size() == 1) { - return urls.get(0); - } - if (urls.size() > 1) { List resources = urls.stream() // @@ -293,14 +292,12 @@ private static Properties loadClasspathResource(String configFileName) { Stream.of(configFileUrl + " (*)"), // resources.stream().skip(1).map(URI::toString) // ).collect(joining("\n- ", "\n- ", "")); - return "Discovered %d '%s' configuration files on the classpath (see below); only the first (*) will be used.%s".formatted( + return "Discovered %d '%s' configuration files on the classpath (see below); properties will be merged, but when there is a conflict, only the first (*) will be used.%s".formatted( resources.size(), configFileName, formattedResourceList); }); } - return configFileUrl; } - - return null; + return urls; } private static void loadClasspathResource(URL configFileUrl, Properties props) throws IOException { diff --git a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java index 76548b2c5452..86d3d5151d78 100644 --- a/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java +++ b/platform-tests/src/test/java/org/junit/platform/launcher/core/LauncherConfigurationParametersTests.java @@ -201,11 +201,15 @@ void ignoresSystemPropertyAndConfigFileWhenImplicitLookupsAreDisabled() { } @Test - void warnsOnMultiplePropertyResources(@TempDir Path tempDir, @TrackLogRecords LogRecordListener logRecordListener) + void mergesOnMultiplePropertyResources(@TempDir Path tempDir, @TrackLogRecords LogRecordListener logRecordListener) throws Exception { + String uniqueKey = KEY + ".unique-modifier"; + String someValue = "another value from second config file"; Properties properties = new Properties(); properties.setProperty(KEY, "from second config file"); + + properties.setProperty(uniqueKey, someValue); try (var out = Files.newOutputStream(tempDir.resolve(CONFIG_FILE_NAME))) { properties.store(out, ""); } @@ -217,14 +221,18 @@ void warnsOnMultiplePropertyResources(@TempDir Path tempDir, @TrackLogRecords Lo Thread.currentThread().setContextClassLoader(customClassLoader); ConfigurationParameters configParams = fromMapAndFile(Map.of(), CONFIG_FILE_NAME); - assertThat(configParams.get(KEY)).contains(CONFIG_FILE); + assertThat(configParams.get(KEY)).isPresent(); + assertThat(configParams.get(KEY).orElse("")).isEqualTo(CONFIG_FILE); + + assertThat(configParams.get(uniqueKey)).isPresent(); + assertThat(configParams.get(uniqueKey).orElse("")).isEqualTo(someValue); assertThat(logRecordListener.stream(Level.WARNING).map(LogRecord::getMessage)) // .hasSize(1) // .first(as(InstanceOfAssertFactories.STRING)) // .contains(""" Discovered 2 '%s' configuration files on the classpath (see below); \ - only the first (*) will be used. + properties will be merged, but when there is a conflict, only the first (*) will be used. - %s (*) - %s"""// .formatted(CONFIG_FILE_NAME, originalResource,