20
20
import java .io .StringReader ;
21
21
import java .util .ArrayList ;
22
22
import java .util .Arrays ;
23
- import java .util .Collections ;
24
- import java .util .Comparator ;
25
23
import java .util .LinkedHashMap ;
26
24
import java .util .List ;
27
25
import java .util .Map ;
28
26
import java .util .Properties ;
29
- import java .util .TreeMap ;
30
- import java .util .stream .Collectors ;
31
27
32
28
import org .apache .commons .logging .Log ;
33
29
import org .apache .commons .logging .LogFactory ;
41
37
import org .springframework .core .env .MapPropertySource ;
42
38
import org .springframework .core .env .PropertySource ;
43
39
import org .springframework .core .env .PropertySources ;
44
- import org .springframework .core .io .ClassPathResource ;
45
40
import org .springframework .core .io .Resource ;
46
41
import org .springframework .core .io .ResourceLoader ;
47
42
import org .springframework .core .io .support .ResourcePropertySource ;
48
43
import org .springframework .test .context .TestPropertySource ;
49
44
import org .springframework .test .context .util .TestContextResourceUtils ;
50
45
import org .springframework .util .Assert ;
51
- import org .springframework .util .ClassUtils ;
52
46
import org .springframework .util .ObjectUtils ;
53
- import org .springframework .util .ResourceUtils ;
54
47
import org .springframework .util .StringUtils ;
55
48
56
49
/**
@@ -75,19 +68,6 @@ public abstract class TestPropertySourceUtils {
75
68
76
69
private static final Log logger = LogFactory .getLog (TestPropertySourceUtils .class );
77
70
78
- /**
79
- * Compares {@link MergedAnnotation} instances (presumably within the same
80
- * aggregate index) by their meta-distance, in reverse order.
81
- * <p>Using this {@link Comparator} to sort according to reverse meta-distance
82
- * ensures that directly present annotations take precedence over meta-present
83
- * annotations (within a given aggregate index). In other words, this follows
84
- * the last-one-wins principle of overriding properties.
85
- * @see MergedAnnotation#getAggregateIndex()
86
- * @see MergedAnnotation#getDistance()
87
- */
88
- private static final Comparator <? super MergedAnnotation <?>> reversedMetaDistanceComparator =
89
- Comparator .<MergedAnnotation <?>> comparingInt (MergedAnnotation ::getDistance ).reversed ();
90
-
91
71
92
72
static MergedTestPropertySources buildMergedTestPropertySources (Class <?> testClass ) {
93
73
MergedAnnotations mergedAnnotations = MergedAnnotations .from (testClass , SearchStrategy .EXHAUSTIVE );
@@ -103,123 +83,21 @@ private static MergedTestPropertySources mergeTestPropertySources(MergedAnnotati
103
83
private static List <TestPropertySourceAttributes > resolveTestPropertySourceAttributes (
104
84
MergedAnnotations mergedAnnotations ) {
105
85
106
- // Group by aggregate index to ensure proper separation of inherited and local annotations.
107
- Map <Integer , List <MergedAnnotation <TestPropertySource >>> aggregateIndexMap = mergedAnnotations
108
- .stream (TestPropertySource .class )
109
- .collect (Collectors .groupingBy (MergedAnnotation ::getAggregateIndex , TreeMap ::new ,
110
- Collectors .mapping (x -> x , Collectors .toList ())));
111
-
112
- // Stream the lists of annotations per aggregate index, merge each list into a
113
- // single TestPropertySourceAttributes instance, and collect the results.
114
- return aggregateIndexMap .values ().stream ()
115
- .map (TestPropertySourceUtils ::createTestPropertySourceAttributes )
116
- .collect (Collectors .toList ());
86
+ List <TestPropertySourceAttributes > result = new ArrayList <>();
87
+ mergedAnnotations .stream (TestPropertySource .class )
88
+ .forEach (annotation -> addOrMergeTestPropertySourceAttributes (result , annotation ));
89
+ return result ;
117
90
}
118
91
119
- /**
120
- * Create a merged {@link TestPropertySourceAttributes} instance from all
121
- * annotations in the supplied list for a given aggregate index as if there
122
- * were only one such annotation.
123
- * <p>Within the supplied list, sort according to reversed meta-distance of
124
- * the annotations from the declaring class. This ensures that directly present
125
- * annotations take precedence over meta-present annotations within the current
126
- * aggregate index.
127
- * <p>If a given {@link TestPropertySource @TestPropertySource} does not
128
- * declare properties or locations, an attempt will be made to detect a default
129
- * properties file.
130
- */
131
- private static TestPropertySourceAttributes createTestPropertySourceAttributes (
132
- List <MergedAnnotation <TestPropertySource >> list ) {
133
-
134
- list .sort (reversedMetaDistanceComparator );
135
-
136
- List <String > locations = new ArrayList <>();
137
- List <String > properties = new ArrayList <>();
138
- Class <?> declaringClass = null ;
139
- Boolean inheritLocations = null ;
140
- Boolean inheritProperties = null ;
141
-
142
- // Merge all @TestPropertySource annotations within the current
143
- // aggregate index into a single TestPropertySourceAttributes instance,
144
- // simultaneously ensuring that all such annotations have the same
145
- // declaringClass, inheritLocations, and inheritProperties values.
146
- for (MergedAnnotation <TestPropertySource > mergedAnnotation : list ) {
147
- Class <?> currentDeclaringClass = (Class <?>) mergedAnnotation .getSource ();
148
- if (declaringClass != null && !declaringClass .equals (currentDeclaringClass )) {
149
- throw new IllegalStateException ("Detected @TestPropertySource declarations within an aggregate index " +
150
- "with different declaring classes: " + declaringClass .getName () + " and " +
151
- currentDeclaringClass .getName ());
152
- }
153
- declaringClass = currentDeclaringClass ;
154
-
155
- TestPropertySource testPropertySource = mergedAnnotation .synthesize ();
156
- if (logger .isTraceEnabled ()) {
157
- logger .trace (String .format ("Retrieved %s for declaring class [%s]." , testPropertySource ,
158
- declaringClass .getName ()));
159
- }
160
-
161
- Boolean currentInheritLocations = testPropertySource .inheritLocations ();
162
- assertConsistentValues (testPropertySource , declaringClass , "inheritLocations" , inheritLocations ,
163
- currentInheritLocations );
164
- inheritLocations = currentInheritLocations ;
92
+ private static void addOrMergeTestPropertySourceAttributes (
93
+ List <TestPropertySourceAttributes > result ,
94
+ MergedAnnotation <TestPropertySource > annotation ) {
165
95
166
- Boolean currentInheritProperties = testPropertySource .inheritProperties ();
167
- assertConsistentValues (testPropertySource , declaringClass , "inheritProperties" , inheritProperties ,
168
- currentInheritProperties );
169
- inheritProperties = currentInheritProperties ;
170
-
171
- String [] currentLocations = testPropertySource .locations ();
172
- String [] currentProperties = testPropertySource .properties ();
173
- if (ObjectUtils .isEmpty (currentLocations ) && ObjectUtils .isEmpty (currentProperties )) {
174
- locations .add (detectDefaultPropertiesFile (declaringClass ));
175
- }
176
- else {
177
- Collections .addAll (locations , currentLocations );
178
- Collections .addAll (properties , currentProperties );
179
- }
180
- }
181
-
182
- TestPropertySourceAttributes attributes = new TestPropertySourceAttributes (declaringClass , locations ,
183
- inheritLocations , properties , inheritProperties );
184
- if (logger .isTraceEnabled ()) {
185
- logger .trace (String .format ("Resolved @TestPropertySource attributes %s for declaring class [%s]." ,
186
- attributes , declaringClass .getName ()));
187
- }
188
- return attributes ;
189
- }
190
-
191
- private static void assertConsistentValues (TestPropertySource testPropertySource , Class <?> declaringClass ,
192
- String attributeName , Object trackedValue , Object currentValue ) {
193
-
194
- Assert .isTrue ((trackedValue == null || trackedValue .equals (currentValue )),
195
- () -> String .format ("%s on class [%s] must declare the same value for '%s' " +
196
- "as other directly present or meta-present @TestPropertySource annotations on [%2$s]." ,
197
- testPropertySource , declaringClass .getName (), attributeName ));
198
- }
199
-
200
- /**
201
- * Detect a default properties file for the supplied class, as specified
202
- * in the class-level Javadoc for {@link TestPropertySource}.
203
- */
204
- private static String detectDefaultPropertiesFile (Class <?> testClass ) {
205
- String resourcePath = ClassUtils .convertClassNameToResourcePath (testClass .getName ()) + ".properties" ;
206
- ClassPathResource classPathResource = new ClassPathResource (resourcePath );
207
-
208
- if (classPathResource .exists ()) {
209
- String prefixedResourcePath = ResourceUtils .CLASSPATH_URL_PREFIX + resourcePath ;
210
- if (logger .isInfoEnabled ()) {
211
- logger .info (String .format ("Detected default properties file \" %s\" for test class [%s]" ,
212
- prefixedResourcePath , testClass .getName ()));
213
- }
214
- return prefixedResourcePath ;
96
+ if (result .isEmpty () || !result .get (result .size ()-1 ).canMerge (annotation )) {
97
+ result .add (new TestPropertySourceAttributes (annotation ));
215
98
}
216
99
else {
217
- String msg = String .format ("Could not detect default properties file for test class [%s]: " +
218
- "%s does not exist. Either declare the 'locations' or 'properties' attributes " +
219
- "of @TestPropertySource or make the default properties file available." , testClass .getName (),
220
- classPathResource );
221
- logger .error (msg );
222
- throw new IllegalStateException (msg );
100
+ result .get (result .size () - 1 ).merge (annotation );
223
101
}
224
102
}
225
103
0 commit comments