Skip to content

Commit aa0a3bd

Browse files
committed
Avoid cache miss for @activeprofiles w/ same profiles but different order
Prior to this commit, two @activeprofiles declarations with the same profiles but different order resulted in an identical duplicate ApplicationContext in the context cache in the Spring TestContext Framework. This commit uses a TreeSet to ensure that registered active profiles are both unique and sorted, thereby avoiding cache misses for semantically identical active profiles configuration on different test classes. Closes gh-25973
1 parent d5e637a commit aa0a3bd

File tree

7 files changed

+30
-41
lines changed

7 files changed

+30
-41
lines changed

spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
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.
@@ -79,7 +79,7 @@
7979
* <p>The default value is {@code true}, which means that a test
8080
* class will <em>inherit</em> bean definition profiles defined by a
8181
* test superclass. Specifically, the bean definition profiles for a test
82-
* class will be appended to the list of bean definition profiles
82+
* class will be added to the list of bean definition profiles
8383
* defined by a test superclass. Thus, subclasses have the option of
8484
* <em>extending</em> the list of bean definition profiles.
8585
* <p>If {@code inheritProfiles} is set to {@code false}, the bean

spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
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.
@@ -19,8 +19,8 @@
1919
import java.io.Serializable;
2020
import java.util.Arrays;
2121
import java.util.Collections;
22-
import java.util.LinkedHashSet;
2322
import java.util.Set;
23+
import java.util.TreeSet;
2424

2525
import org.springframework.context.ApplicationContext;
2626
import org.springframework.context.ApplicationContextInitializer;
@@ -533,8 +533,8 @@ private static String[] processActiveProfiles(@Nullable String[] activeProfiles)
533533
return EMPTY_STRING_ARRAY;
534534
}
535535

536-
// Active profiles must be unique
537-
Set<String> profilesSet = new LinkedHashSet<>(Arrays.asList(activeProfiles));
536+
// Active profiles must be unique and sorted
537+
Set<String> profilesSet = new TreeSet<>(Arrays.asList(activeProfiles));
538538
return StringUtils.toStringArray(profilesSet);
539539
}
540540

spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@
1616

1717
package org.springframework.test.context.support;
1818

19-
import java.util.ArrayList;
20-
import java.util.Collections;
21-
import java.util.LinkedHashSet;
22-
import java.util.List;
2319
import java.util.Set;
20+
import java.util.TreeSet;
2421

2522
import org.apache.commons.logging.Log;
2623
import org.apache.commons.logging.LogFactory;
@@ -70,7 +67,7 @@ abstract class ActiveProfilesUtils {
7067
static String[] resolveActiveProfiles(Class<?> testClass) {
7168
Assert.notNull(testClass, "Class must not be null");
7269

73-
final List<String[]> profileArrays = new ArrayList<>();
70+
Set<String> activeProfiles = new TreeSet<>();
7471

7572
Class<ActiveProfiles> annotationType = ActiveProfiles.class;
7673
AnnotationDescriptor<ActiveProfiles> descriptor =
@@ -109,25 +106,17 @@ static String[] resolveActiveProfiles(Class<?> testClass) {
109106

110107
String[] profiles = resolver.resolve(rootDeclaringClass);
111108
if (!ObjectUtils.isEmpty(profiles)) {
112-
profileArrays.add(profiles);
109+
for (String profile : profiles) {
110+
if (StringUtils.hasText(profile)) {
111+
activeProfiles.add(profile.trim());
112+
}
113+
}
113114
}
114115

115116
descriptor = (annotation.inheritProfiles() ? MetaAnnotationUtils.findAnnotationDescriptor(
116117
rootDeclaringClass.getSuperclass(), annotationType) : null);
117118
}
118119

119-
// Reverse the list so that we can traverse "down" the hierarchy.
120-
Collections.reverse(profileArrays);
121-
122-
final Set<String> activeProfiles = new LinkedHashSet<>();
123-
for (String[] profiles : profileArrays) {
124-
for (String profile : profiles) {
125-
if (StringUtils.hasText(profile)) {
126-
activeProfiles.add(profile.trim());
127-
}
128-
}
129-
}
130-
131120
return StringUtils.toStringArray(activeProfiles);
132121
}
133122

spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
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,8 +16,8 @@
1616

1717
package org.springframework.test.context.support;
1818

19-
import java.util.LinkedHashSet;
2019
import java.util.Set;
20+
import java.util.TreeSet;
2121

2222
import org.apache.commons.logging.Log;
2323
import org.apache.commons.logging.LogFactory;
@@ -59,7 +59,7 @@ public class DefaultActiveProfilesResolver implements ActiveProfilesResolver {
5959
public String[] resolve(Class<?> testClass) {
6060
Assert.notNull(testClass, "Class must not be null");
6161

62-
final Set<String> activeProfiles = new LinkedHashSet<>();
62+
Set<String> activeProfiles = new TreeSet<>();
6363

6464
Class<ActiveProfiles> annotationType = ActiveProfiles.class;
6565
AnnotationDescriptor<ActiveProfiles> descriptor = findAnnotationDescriptor(testClass, annotationType);

spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
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.
@@ -143,7 +143,7 @@ public void hashCodeWithSameProfilesReversed() {
143143
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader);
144144
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
145145
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader);
146-
assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
146+
assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
147147
}
148148

149149
@Test
@@ -339,7 +339,7 @@ public void equalsWithSameProfilesReversed() {
339339
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader);
340340
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
341341
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader);
342-
assertNotEquals(mergedConfig1, mergedConfig2);
342+
assertEquals(mergedConfig1, mergedConfig2);
343343
}
344344

345345
@Test

spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
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.
@@ -90,8 +90,8 @@ public void verifyCacheKeyIsBasedOnActiveProfiles() {
9090
int size = 0, hit = 0, miss = 0;
9191
loadCtxAndAssertStats(FooBarProfilesTestCase.class, ++size, hit, ++miss);
9292
loadCtxAndAssertStats(FooBarProfilesTestCase.class, size, ++hit, miss);
93-
// Profiles {foo, bar} should not hash to the same as {bar,foo}
94-
loadCtxAndAssertStats(BarFooProfilesTestCase.class, ++size, hit, ++miss);
93+
// Profiles {foo, bar} MUST hash to the same as {bar, foo}
94+
loadCtxAndAssertStats(BarFooProfilesTestCase.class, size, ++hit, miss);
9595
loadCtxAndAssertStats(FooBarProfilesTestCase.class, size, ++hit, miss);
9696
loadCtxAndAssertStats(FooBarProfilesTestCase.class, size, ++hit, miss);
9797
loadCtxAndAssertStats(BarFooProfilesTestCase.class, size, ++hit, miss);

spring-test/src/test/java/org/springframework/test/context/support/ActiveProfilesUtilsTests.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
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.
@@ -65,12 +65,12 @@ public void resolveActiveProfilesWithEmptyProfiles() {
6565

6666
@Test
6767
public void resolveActiveProfilesWithDuplicatedProfiles() {
68-
assertResolvedProfiles(DuplicatedProfiles.class, "foo", "bar", "baz");
68+
assertResolvedProfiles(DuplicatedProfiles.class, "bar", "baz", "foo");
6969
}
7070

7171
@Test
7272
public void resolveActiveProfilesWithLocalAndInheritedDuplicatedProfiles() {
73-
assertResolvedProfiles(ExtendedDuplicatedProfiles.class, "foo", "bar", "baz", "cat", "dog");
73+
assertResolvedProfiles(ExtendedDuplicatedProfiles.class, "bar", "baz", "cat", "dog", "foo");
7474
}
7575

7676
@Test
@@ -90,12 +90,12 @@ public void resolveActiveProfilesWithInheritedAnnotationAndClasses() {
9090

9191
@Test
9292
public void resolveActiveProfilesWithLocalAndInheritedAnnotations() {
93-
assertResolvedProfiles(LocationsBar.class, "foo", "bar");
93+
assertResolvedProfiles(LocationsBar.class, "bar", "foo");
9494
}
9595

9696
@Test
9797
public void resolveActiveProfilesWithOverriddenAnnotation() {
98-
assertResolvedProfiles(Animals.class, "dog", "cat");
98+
assertResolvedProfiles(Animals.class, "cat", "dog");
9999
}
100100

101101
/**
@@ -127,15 +127,15 @@ public void resolveActiveProfilesWithMetaAnnotationAndOverriddenAttributes() {
127127
*/
128128
@Test
129129
public void resolveActiveProfilesWithLocalAndInheritedMetaAnnotations() {
130-
assertResolvedProfiles(MetaLocationsBar.class, "foo", "bar");
130+
assertResolvedProfiles(MetaLocationsBar.class, "bar", "foo");
131131
}
132132

133133
/**
134134
* @since 4.0
135135
*/
136136
@Test
137137
public void resolveActiveProfilesWithOverriddenMetaAnnotation() {
138-
assertResolvedProfiles(MetaAnimals.class, "dog", "cat");
138+
assertResolvedProfiles(MetaAnimals.class, "cat", "dog");
139139
}
140140

141141
/**
@@ -159,7 +159,7 @@ public void resolveActiveProfilesWithInheritedResolver() {
159159
*/
160160
@Test
161161
public void resolveActiveProfilesWithMergedInheritedResolver() {
162-
assertResolvedProfiles(MergedInheritedFooActiveProfilesResolverTestCase.class, "foo", "bar");
162+
assertResolvedProfiles(MergedInheritedFooActiveProfilesResolverTestCase.class, "bar", "foo");
163163
}
164164

165165
/**

0 commit comments

Comments
 (0)