From e713e0d6d5e836a7c84eeebb2afd3fdeb5d18158 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Mon, 2 Nov 2020 16:32:07 +0100 Subject: [PATCH] Preserve registration order in @ActiveProfiles With this commit, bean definition profiles declared via @ActiveProfiles are once again stored in registration order, in order to support use cases in Spring Boot and other frameworks that depend on the registration order. This effectively reverts the changes made in conjunction with gh-25973. Closes gh-26004 --- .../test/context/ActiveProfiles.java | 4 +-- .../context/MergedContextConfiguration.java | 8 +++--- .../context/support/ActiveProfilesUtils.java | 25 ++++++++++++----- .../DefaultActiveProfilesResolver.java | 28 +++++-------------- .../MergedContextConfigurationTests.java | 6 ++-- .../test/context/cache/ContextCacheTests.java | 6 ++-- .../support/ActiveProfilesUtilsTests.java | 14 +++++----- 7 files changed, 44 insertions(+), 47 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java b/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java index a5ee5b264a9..9ad1e3068aa 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java +++ b/spring-test/src/main/java/org/springframework/test/context/ActiveProfiles.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -79,7 +79,7 @@ public @interface ActiveProfiles { *

The default value is {@code true}, which means that a test * class will inherit bean definition profiles defined by a * test superclass. Specifically, the bean definition profiles for a test - * class will be added to the list of bean definition profiles + * class will be appended to the list of bean definition profiles * defined by a test superclass. Thus, subclasses have the option of * extending the list of bean definition profiles. *

If {@code inheritProfiles} is set to {@code false}, the bean diff --git a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java index db37af2f02b..f434b14022a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java +++ b/spring-test/src/main/java/org/springframework/test/context/MergedContextConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,8 @@ package org.springframework.test.context; import java.io.Serializable; import java.util.Arrays; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.Set; -import java.util.TreeSet; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; @@ -533,8 +533,8 @@ public class MergedContextConfiguration implements Serializable { return EMPTY_STRING_ARRAY; } - // Active profiles must be unique and sorted - Set profilesSet = new TreeSet<>(Arrays.asList(activeProfiles)); + // Active profiles must be unique + Set profilesSet = new LinkedHashSet<>(Arrays.asList(activeProfiles)); return StringUtils.toStringArray(profilesSet); } diff --git a/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java b/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java index 5e2c76582ab..491ee370279 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/ActiveProfilesUtils.java @@ -16,8 +16,11 @@ package org.springframework.test.context.support; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; -import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -67,7 +70,7 @@ abstract class ActiveProfilesUtils { static String[] resolveActiveProfiles(Class testClass) { Assert.notNull(testClass, "Class must not be null"); - Set activeProfiles = new TreeSet<>(); + List profileArrays = new ArrayList<>(); Class annotationType = ActiveProfiles.class; AnnotationDescriptor descriptor = @@ -106,17 +109,25 @@ abstract class ActiveProfilesUtils { String[] profiles = resolver.resolve(rootDeclaringClass); if (!ObjectUtils.isEmpty(profiles)) { - for (String profile : profiles) { - if (StringUtils.hasText(profile)) { - activeProfiles.add(profile.trim()); - } - } + profileArrays.add(profiles); } descriptor = (annotation.inheritProfiles() ? MetaAnnotationUtils.findAnnotationDescriptor( rootDeclaringClass.getSuperclass(), annotationType) : null); } + // Reverse the list so that we can traverse "down" the hierarchy. + Collections.reverse(profileArrays); + + Set activeProfiles = new LinkedHashSet<>(); + for (String[] profiles : profileArrays) { + for (String profile : profiles) { + if (StringUtils.hasText(profile)) { + activeProfiles.add(profile.trim()); + } + } + } + return StringUtils.toStringArray(activeProfiles); } diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java b/spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java index 83ee7d6909f..44439638866 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/DefaultActiveProfilesResolver.java @@ -16,9 +16,6 @@ package org.springframework.test.context.support; -import java.util.Set; -import java.util.TreeSet; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -26,7 +23,6 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfilesResolver; import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDescriptor; @@ -43,6 +39,8 @@ import static org.springframework.test.util.MetaAnnotationUtils.findAnnotationDe */ public class DefaultActiveProfilesResolver implements ActiveProfilesResolver { + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + private static final Log logger = LogFactory.getLog(DefaultActiveProfilesResolver.class); @@ -58,36 +56,24 @@ public class DefaultActiveProfilesResolver implements ActiveProfilesResolver { @Override public String[] resolve(Class testClass) { Assert.notNull(testClass, "Class must not be null"); - - Set activeProfiles = new TreeSet<>(); - - Class annotationType = ActiveProfiles.class; - AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, annotationType); + AnnotationDescriptor descriptor = findAnnotationDescriptor(testClass, ActiveProfiles.class); if (descriptor == null) { if (logger.isDebugEnabled()) { logger.debug(String.format( "Could not find an 'annotation declaring class' for annotation type [%s] and class [%s]", - annotationType.getName(), testClass.getName())); + ActiveProfiles.class.getName(), testClass.getName())); } + return EMPTY_STRING_ARRAY; } else { - Class declaringClass = descriptor.getDeclaringClass(); ActiveProfiles annotation = descriptor.synthesizeAnnotation(); - if (logger.isTraceEnabled()) { logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation, - declaringClass.getName())); - } - - for (String profile : annotation.profiles()) { - if (StringUtils.hasText(profile)) { - activeProfiles.add(profile.trim()); - } + descriptor.getDeclaringClass().getName())); } + return annotation.profiles(); } - - return StringUtils.toStringArray(activeProfiles); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java b/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java index 26bac7594c4..002da9c991c 100644 --- a/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/MergedContextConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -143,7 +143,7 @@ class MergedContextConfigurationTests { EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader); MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader); - assertThat(mergedConfig2).hasSameHashCodeAs(mergedConfig1); + assertThat(mergedConfig2.hashCode()).isNotEqualTo(mergedConfig1.hashCode()); } @Test @@ -339,7 +339,7 @@ class MergedContextConfigurationTests { EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader); MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader); - assertThat(mergedConfig2).isEqualTo(mergedConfig1); + assertThat(mergedConfig2).isNotEqualTo(mergedConfig1); } @Test diff --git a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java index 357535ce41e..202af4bb4b1 100644 --- a/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/cache/ContextCacheTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -90,8 +90,8 @@ class ContextCacheTests { int size = 0, hit = 0, miss = 0; loadCtxAndAssertStats(FooBarProfilesTestCase.class, ++size, hit, ++miss); loadCtxAndAssertStats(FooBarProfilesTestCase.class, size, ++hit, miss); - // Profiles {foo, bar} MUST hash to the same as {bar, foo} - loadCtxAndAssertStats(BarFooProfilesTestCase.class, size, ++hit, miss); + // Profiles {foo, bar} should not hash to the same as {bar,foo} + loadCtxAndAssertStats(BarFooProfilesTestCase.class, ++size, hit, ++miss); loadCtxAndAssertStats(FooBarProfilesTestCase.class, size, ++hit, miss); loadCtxAndAssertStats(FooBarProfilesTestCase.class, size, ++hit, miss); loadCtxAndAssertStats(BarFooProfilesTestCase.class, size, ++hit, miss); diff --git a/spring-test/src/test/java/org/springframework/test/context/support/ActiveProfilesUtilsTests.java b/spring-test/src/test/java/org/springframework/test/context/support/ActiveProfilesUtilsTests.java index 7228415d140..b192b34ee24 100644 --- a/spring-test/src/test/java/org/springframework/test/context/support/ActiveProfilesUtilsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/support/ActiveProfilesUtilsTests.java @@ -67,12 +67,12 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests { @Test void resolveActiveProfilesWithDuplicatedProfiles() { - assertResolvedProfiles(DuplicatedProfiles.class, "bar", "baz", "foo"); + assertResolvedProfiles(DuplicatedProfiles.class, "foo", "bar", "baz"); } @Test void resolveActiveProfilesWithLocalAndInheritedDuplicatedProfiles() { - assertResolvedProfiles(ExtendedDuplicatedProfiles.class, "bar", "baz", "cat", "dog", "foo"); + assertResolvedProfiles(ExtendedDuplicatedProfiles.class, "foo", "bar", "baz", "cat", "dog"); } @Test @@ -92,12 +92,12 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests { @Test void resolveActiveProfilesWithLocalAndInheritedAnnotations() { - assertResolvedProfiles(LocationsBar.class, "bar", "foo"); + assertResolvedProfiles(LocationsBar.class, "foo", "bar"); } @Test void resolveActiveProfilesWithOverriddenAnnotation() { - assertResolvedProfiles(Animals.class, "cat", "dog"); + assertResolvedProfiles(Animals.class, "dog", "cat"); } /** @@ -129,7 +129,7 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests { */ @Test void resolveActiveProfilesWithLocalAndInheritedMetaAnnotations() { - assertResolvedProfiles(MetaLocationsBar.class, "bar", "foo"); + assertResolvedProfiles(MetaLocationsBar.class, "foo", "bar"); } /** @@ -137,7 +137,7 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests { */ @Test void resolveActiveProfilesWithOverriddenMetaAnnotation() { - assertResolvedProfiles(MetaAnimals.class, "cat", "dog"); + assertResolvedProfiles(MetaAnimals.class, "dog", "cat"); } /** @@ -161,7 +161,7 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests { */ @Test void resolveActiveProfilesWithMergedInheritedResolver() { - assertResolvedProfiles(MergedInheritedFooActiveProfilesResolverTestCase.class, "bar", "foo"); + assertResolvedProfiles(MergedInheritedFooActiveProfilesResolverTestCase.class, "foo", "bar"); } /**