Skip to content

Commit 03c18f8

Browse files
authored
4.x: Make MP test annotations ordering deterministic (#9904)
1 parent 2ce9358 commit 03c18f8

File tree

17 files changed

+562
-19
lines changed

17 files changed

+562
-19
lines changed

docs/src/main/asciidoc/mp/testing/testing-common.adoc

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,15 +203,32 @@ include::{sourcedir}/mp/testing/TestingSnippets.java[tag=snippet_11, indent=0]
203203
The ordering of the test configuration can be controlled using the mechanism defined by the
204204
link:{microprofile-config-spec-url}#_configsource_ordering[MicroProfile Config specification].
205205
206-
NOTE: The configuration expressed with link:{mp-testing-javadoc-url}/AddConfig.html[`@AddConfig`] has a fixed ordinal value
207-
of `1000`
208-
209206
[source,java]
210207
.Add a properties text block with ordinal
211208
----
212209
include::{sourcedir}/mp/testing/TestingSnippets.java[tag=snippet_12, indent=0]
213210
----
214211
212+
The default ordering is the following
213+
214+
[cols="1,3"]
215+
|===
216+
|Annotation |Ordinal
217+
218+
|link:{mp-testing-javadoc-url}/AddConfig.html[`@AddConfig`]
219+
|1000
220+
221+
|link:{mp-testing-javadoc-url}/AddConfigBlock.html[`@AddConfigBlock`]
222+
|900
223+
224+
|link:{mp-testing-javadoc-url}/AddConfigSource.html[`@AddConfigSource`]
225+
|800
226+
227+
|link:{mp-testing-javadoc-url}/Configuration.html[`@Configuration`]
228+
|700
229+
230+
|===
231+
215232
=== Injectable Types
216233
217234
Helidon provides injection support for types that reflect the current server. E.g. JAXRS client.

microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/HelidonTestConfigSynthetic.java

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.eclipse.microprofile.config.spi.ConfigSource;
3838

3939
import static io.helidon.microprofile.testing.ReflectionHelper.invoke;
40+
import static io.helidon.microprofile.testing.ReflectionHelper.isDefaultMethod;
4041
import static io.helidon.microprofile.testing.ReflectionHelper.requireStatic;
4142

4243
/**
@@ -151,17 +152,20 @@ private Config buildConfig() {
151152
configSources.add(MpConfigSources.create(testInfo.id(), map));
152153
blocks.forEach((type, values) -> {
153154
for (String value : values) {
154-
configSources.add(MpConfigSources.create(type, new StringReader(value)));
155+
ConfigSource config = MpConfigSources.create(type, new StringReader(value));
156+
configSources.add(addConfigOrdinal(config, type, "900"));
155157
}
156158
});
157159
for (Method m : methods) {
158-
configSources.add(invoke(ConfigSource.class, requireStatic(m), null));
160+
ConfigSource config = invoke(ConfigSource.class, requireStatic(m), null);
161+
configSources.add(new ConfigSourceWrapper(config));
159162
}
160163
for (String source : resources) {
161164
String filename = source.trim();
162165
for (URL url : resources(filename)) {
163166
String type = extension(filename);
164-
configSources.add(MpConfigSources.create(type, url));
167+
ConfigSource config = MpConfigSources.create(type, url);
168+
configSources.add(addConfigOrdinal(config, type, "700"));
165169
}
166170
}
167171
ConfigBuilder builder = ConfigProviderResolver.instance()
@@ -173,6 +177,12 @@ private Config buildConfig() {
173177
return builder.build();
174178
}
175179

180+
private ConfigSource addConfigOrdinal(ConfigSource config, String type, String ordinal) {
181+
Map<String, String> properties = new HashMap<>(config.getProperties());
182+
properties.putIfAbsent(ConfigSource.CONFIG_ORDINAL, ordinal);
183+
return MpConfigSources.create(type, properties);
184+
}
185+
176186
private static String extension(String filename) {
177187
int idx = filename.lastIndexOf('.');
178188
return idx > -1 ? filename.substring(idx + 1) : "properties";
@@ -190,4 +200,33 @@ private static Collection<URL> resources(String name) {
190200
"Failed to read '%s' from classpath", name), e);
191201
}
192202
}
203+
204+
private record ConfigSourceWrapper(ConfigSource delegate) implements ConfigSource {
205+
@Override
206+
public Set<String> getPropertyNames() {
207+
return delegate.getPropertyNames();
208+
}
209+
210+
@Override
211+
public String getValue(String propertyName) {
212+
return delegate.getValue(propertyName);
213+
}
214+
215+
@Override
216+
public String getName() {
217+
return delegate.getName();
218+
}
219+
220+
@Override
221+
public Map<String, String> getProperties() {
222+
return delegate.getProperties();
223+
}
224+
225+
@Override
226+
public int getOrdinal() {
227+
boolean isDefault = isDefaultMethod(delegate, "getOrdinal");
228+
boolean isDefaultOrdinal = delegate.getOrdinal() == ConfigSource.DEFAULT_ORDINAL;
229+
return isDefault && isDefaultOrdinal ? 800 : delegate.getOrdinal();
230+
}
231+
}
193232
}

microprofile/testing/testing/src/main/java/io/helidon/microprofile/testing/ReflectionHelper.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,29 @@ static List<Method> methodHierarchy(Method method) {
5959
*
6060
* @param method base method
6161
* @param override override method
62-
* @return {@code true} if overrides, {@code false otherwise}
62+
* @return {@code true} if overrides, {@code false} otherwise
6363
*/
6464
static boolean isOverride(Method method, Method override) {
6565
return override.getName().equals(method.getName())
6666
&& override.getReturnType().isAssignableFrom(method.getReturnType())
6767
&& Arrays.equals(override.getParameterTypes(), method.getParameterTypes());
6868
}
6969

70+
/**
71+
* Test if the given instance has a default method by name. If the method does not exist, return {@code false}.
72+
*
73+
* @param instance instance
74+
* @param name method name
75+
* @return {@code true} if is default, {@code false} otherwise
76+
*/
77+
static boolean isDefaultMethod(Object instance, String name) {
78+
try {
79+
return instance.getClass().getMethod(name).isDefault();
80+
} catch (NoSuchMethodException e) {
81+
return false;
82+
}
83+
}
84+
7085
/**
7186
* Collect all types in the type hiearchy of the given type.
7287
*

microprofile/tests/testing/junit5/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,11 @@
8585
<java.util.logging.config.file>
8686
${project.build.testOutputDirectory}/logging.properties
8787
</java.util.logging.config.file>
88+
<foo>systemProperty</foo>
8889
</systemPropertyVariables>
90+
<environmentVariables>
91+
<foo>environmentProperty</foo>
92+
</environmentVariables>
8993
<redirectTestOutputToFile>${redirectTestOutputToFile}</redirectTestOutputToFile>
9094
</configuration>
9195
</plugin>

microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestAddConfigBlockProperties.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 Oracle and/or its affiliates.
2+
* Copyright (c) 2024, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,9 +16,6 @@
1616

1717
package io.helidon.microprofile.tests.testing.junit5;
1818

19-
import static org.hamcrest.CoreMatchers.is;
20-
import static org.hamcrest.MatcherAssert.assertThat;
21-
2219
import jakarta.inject.Inject;
2320

2421
import io.helidon.microprofile.testing.junit5.AddConfigBlock;
@@ -27,11 +24,14 @@
2724
import org.eclipse.microprofile.config.inject.ConfigProperty;
2825
import org.junit.jupiter.api.Test;
2926

27+
import static org.hamcrest.CoreMatchers.is;
28+
import static org.hamcrest.MatcherAssert.assertThat;
29+
3030
@HelidonTest
3131
@AddConfigBlock("""
3232
some.key1=some.value1
3333
some.key2=some.value2
34-
""")
34+
""")
3535
class TestAddConfigBlockProperties {
3636

3737
@Inject

microprofile/tests/testing/junit5/src/test/java/io/helidon/microprofile/tests/testing/junit5/TestAddConfigBlockYaml.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 Oracle and/or its affiliates.
2+
* Copyright (c) 2024, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,9 +16,6 @@
1616

1717
package io.helidon.microprofile.tests.testing.junit5;
1818

19-
import static org.hamcrest.CoreMatchers.is;
20-
import static org.hamcrest.MatcherAssert.assertThat;
21-
2219
import jakarta.inject.Inject;
2320

2421
import io.helidon.microprofile.testing.junit5.AddConfigBlock;
@@ -27,13 +24,16 @@
2724
import org.eclipse.microprofile.config.inject.ConfigProperty;
2825
import org.junit.jupiter.api.Test;
2926

27+
import static org.hamcrest.CoreMatchers.is;
28+
import static org.hamcrest.MatcherAssert.assertThat;
29+
3030
@HelidonTest
3131
@AddConfigBlock(type = "yaml", value = """
3232
another1:
3333
key: "another1.value"
3434
another2:
3535
key: "another2.value"
36-
""")
36+
""")
3737
class TestAddConfigBlockYaml {
3838

3939
@Inject
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright (c) 2025 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.helidon.microprofile.tests.testing.junit5;
17+
18+
import java.util.Iterator;
19+
import java.util.List;
20+
import java.util.Set;
21+
22+
import io.helidon.microprofile.testing.AddConfig;
23+
import io.helidon.microprofile.testing.AddConfigBlock;
24+
import io.helidon.microprofile.testing.AddConfigSource;
25+
import io.helidon.microprofile.testing.Configuration;
26+
import io.helidon.microprofile.testing.junit5.HelidonTest;
27+
28+
import jakarta.inject.Inject;
29+
import org.eclipse.microprofile.config.Config;
30+
import org.eclipse.microprofile.config.spi.ConfigSource;
31+
import org.junit.jupiter.api.Test;
32+
33+
import static org.hamcrest.MatcherAssert.assertThat;
34+
import static org.hamcrest.Matchers.is;
35+
import static org.hamcrest.Matchers.iterableWithSize;
36+
37+
@HelidonTest
38+
@AddConfig(key = "foo", value = "addConfig")
39+
@AddConfig(key = "config_ordinal", value = "999")
40+
@AddConfigBlock(value = """
41+
foo=configBlock
42+
config_ordinal=998
43+
""")
44+
@Configuration(configSources = "ordinal-custom.properties")
45+
class TestConfigSourceOrderingCustom {
46+
47+
private final List<Ordering> ORDERINGS = List.of(
48+
new Ordering(999, "addConfig"),
49+
new Ordering(998, "configBlock"),
50+
new Ordering(997, "configSource"),
51+
new Ordering(996, "configuration"),
52+
new Ordering(400, "systemProperty"),
53+
new Ordering(300, "environmentProperty"));
54+
55+
@Inject
56+
@SuppressWarnings("CdiInjectionPointsInspection")
57+
private Config config;
58+
59+
@AddConfigSource
60+
static ConfigSource config() {
61+
return new CustomConfigSource();
62+
}
63+
64+
@Test
65+
void testOrdering() {
66+
Iterator<Ordering> ordering = ORDERINGS.iterator();
67+
Iterable<ConfigSource> configSources = config.getConfigSources();
68+
69+
assertThat(configSources, iterableWithSize(6));
70+
71+
for (ConfigSource configSource : configSources) {
72+
Ordering it = ordering.next();
73+
assertThat(it.ordinal(), is(configSource.getOrdinal()));
74+
assertThat(it.value(), is(configSource.getValue("foo")));
75+
}
76+
}
77+
78+
static class CustomConfigSource implements ConfigSource {
79+
@Override
80+
public Set<String> getPropertyNames() {
81+
return Set.of();
82+
}
83+
84+
@Override
85+
public String getValue(String propertyName) {
86+
return "foo".equals(propertyName) ? "configSource" : null;
87+
}
88+
89+
@Override
90+
public String getName() {
91+
return CustomConfigSource.class.getSimpleName();
92+
}
93+
94+
@Override
95+
public int getOrdinal() {
96+
return 997;
97+
}
98+
}
99+
100+
record Ordering(int ordinal, String value) {
101+
}
102+
}

0 commit comments

Comments
 (0)