Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ repository on GitHub.
objects.
* New `selectClasspathResources(String...)` and `selectClasspathResources(List<String)`
methods in `DiscoverySelectors`.
* Multiple `junit-platform.properties` files are now supported. Where the same property is set
in more than one file, the first value will be used.


[[v6.1.0-M2-junit-jupiter]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,12 @@ private static Properties loadClasspathResource(String configFileName) {
Properties props = new Properties();

try {
URL configFileUrl = findConfigFile(configFileName);
if (configFileUrl != null) {
loadClasspathResource(configFileUrl, props);
List<URL> 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) {
Expand All @@ -269,15 +272,11 @@ private static Properties loadClasspathResource(String configFileName) {
return props;
}

private static @Nullable URL findConfigFile(String configFileName) throws IOException {
private static List<URL> findConfigFiles(String configFileName) throws IOException {

ClassLoader classLoader = ClassLoaderUtils.getDefaultClassLoader();
List<URL> urls = Collections.list(classLoader.getResources(configFileName));

if (urls.size() == 1) {
return urls.get(0);
}

if (urls.size() > 1) {

List<URI> resources = urls.stream() //
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, "");
}
Expand All @@ -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("<missing>")).isEqualTo(CONFIG_FILE);

assertThat(configParams.get(uniqueKey)).isPresent();
assertThat(configParams.get(uniqueKey).orElse("<missing>")).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,
Expand Down