diff --git a/org.springframework.test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java b/org.springframework.test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java index 574ff323b6b..773821a65eb 100644 --- a/org.springframework.test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java +++ b/org.springframework.test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java @@ -18,9 +18,9 @@ package org.springframework.test.context; import java.util.ArrayList; import java.util.Arrays; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -243,13 +243,16 @@ abstract class ContextLoaderUtils { annotationType, clazz)); } - final Set activeProfiles = new LinkedHashSet(); + // Active profiles must be sorted due to cache key generation in + // TestContext. Specifically, profile sets {foo,bar} and {bar,foo} + // must both result in the same array (e.g., [bar,foo]). + final SortedSet activeProfiles = new TreeSet(); while (declaringClass != null) { ActiveProfiles annotation = declaringClass.getAnnotation(annotationType); if (logger.isTraceEnabled()) { - logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", activeProfiles, + logger.trace(String.format("Retrieved @ActiveProfiles [%s] for declaring class [%s].", annotation, declaringClass)); } diff --git a/org.springframework.test/src/main/java/org/springframework/test/context/TestContext.java b/org.springframework.test/src/main/java/org/springframework/test/context/TestContext.java index b5adae9916f..de69d6ae62c 100644 --- a/org.springframework.test/src/main/java/org/springframework/test/context/TestContext.java +++ b/org.springframework.test/src/main/java/org/springframework/test/context/TestContext.java @@ -170,12 +170,24 @@ public class TestContext extends AttributeAccessorSupport { if (context == null) { try { context = loadApplicationContext(); + if (logger.isDebugEnabled()) { + logger.debug(String.format( + "Storing ApplicationContext for test class [%s] in cache under key [%s].", testClass, + contextKey)); + } contextCache.put(contextKey, context); } catch (Exception ex) { throw new IllegalStateException("Failed to load ApplicationContext", ex); } } + else { + if (logger.isDebugEnabled()) { + logger.debug(String.format( + "Retrieved ApplicationContext for test class [%s] from cache with key [%s].", testClass, + contextKey)); + } + } return context; } } diff --git a/org.springframework.test/src/test/java/org/springframework/test/context/SpringRunnerContextCacheTests.java b/org.springframework.test/src/test/java/org/springframework/test/context/SpringRunnerContextCacheTests.java index 95aa0633b3f..264c8a510c7 100644 --- a/org.springframework.test/src/test/java/org/springframework/test/context/SpringRunnerContextCacheTests.java +++ b/org.springframework.test/src/test/java/org/springframework/test/context/SpringRunnerContextCacheTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2011 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. @@ -39,6 +39,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; * @author Sam Brannen * @author Juergen Hoeller * @since 2.5 + * @see TestContextCacheKeyTests */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml") @@ -51,17 +52,31 @@ public class SpringRunnerContextCacheTests { /** - * Asserts the statistics of the supplied context cache. + * Asserts the statistics of the context cache in {@link TestContextManager}. * * @param usageScenario the scenario in which the statistics are used * @param expectedSize the expected number of contexts in the cache * @param expectedHitCount the expected hit count * @param expectedMissCount the expected miss count */ - public static final void assertContextCacheStatistics(String usageScenario, int expectedSize, int expectedHitCount, - int expectedMissCount) { + private static final void assertContextCacheStatistics(String usageScenario, int expectedSize, + int expectedHitCount, int expectedMissCount) { + assertContextCacheStatistics(TestContextManager.contextCache, usageScenario, expectedSize, expectedHitCount, + expectedMissCount); + } + + /** + * Asserts the statistics of the supplied context cache. + * + * @param contextCache the cache to assert against + * @param usageScenario the scenario in which the statistics are used + * @param expectedSize the expected number of contexts in the cache + * @param expectedHitCount the expected hit count + * @param expectedMissCount the expected miss count + */ + public static final void assertContextCacheStatistics(ContextCache contextCache, String usageScenario, + int expectedSize, int expectedHitCount, int expectedMissCount) { - ContextCache contextCache = TestContextManager.contextCache; assertEquals("Verifying number of contexts in cache (" + usageScenario + ").", expectedSize, contextCache.size()); assertEquals("Verifying number of cache hits (" + usageScenario + ").", expectedHitCount, diff --git a/org.springframework.test/src/test/java/org/springframework/test/context/TestContextCacheKeyTests.java b/org.springframework.test/src/test/java/org/springframework/test/context/TestContextCacheKeyTests.java new file mode 100644 index 00000000000..41c0d9a3035 --- /dev/null +++ b/org.springframework.test/src/test/java/org/springframework/test/context/TestContextCacheKeyTests.java @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2011 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context; + +import static org.junit.Assert.assertNotNull; +import static org.springframework.test.context.SpringRunnerContextCacheTests.assertContextCacheStatistics; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.support.AnnotationConfigContextLoader; + +/** + * Unit tests for verifying proper behavior of the {@link ContextCache} in + * conjunction with cache keys generated in {@link TestContext}. + * + * @author Sam Brannen + * @since 3.1 + * @see SpringRunnerContextCacheTests + */ +public class TestContextCacheKeyTests { + + private ContextCache contextCache = new ContextCache(); + + + @Before + public void initialCacheState() { + assertContextCacheStatistics(contextCache, "initial state", 0, 0, 0); + } + + private void loadAppCtxAndAssertCacheStats(Class testClass, int expectedSize, int expectedHitCount, + int expectedMissCount) { + TestContext testContext = new TestContext(testClass, contextCache); + ApplicationContext context = testContext.getApplicationContext(); + assertNotNull(context); + assertContextCacheStatistics(contextCache, testClass.getName(), expectedSize, expectedHitCount, + expectedMissCount); + } + + @Test + public void verifyCacheKeyIsBasedOnContextLoader() { + loadAppCtxAndAssertCacheStats(AnnotationConfigContextLoaderTestCase.class, 1, 0, 1); + loadAppCtxAndAssertCacheStats(AnnotationConfigContextLoaderTestCase.class, 1, 1, 1); + loadAppCtxAndAssertCacheStats(CustomAnnotationConfigContextLoaderTestCase.class, 2, 1, 2); + loadAppCtxAndAssertCacheStats(CustomAnnotationConfigContextLoaderTestCase.class, 2, 2, 2); + loadAppCtxAndAssertCacheStats(AnnotationConfigContextLoaderTestCase.class, 2, 3, 2); + loadAppCtxAndAssertCacheStats(CustomAnnotationConfigContextLoaderTestCase.class, 2, 4, 2); + } + + @Test + public void verifyCacheKeyIsBasedOnActiveProfiles() { + loadAppCtxAndAssertCacheStats(FooBarProfilesTestCase.class, 1, 0, 1); + loadAppCtxAndAssertCacheStats(FooBarProfilesTestCase.class, 1, 1, 1); + // Profiles {foo, bar} should hash to the same as {bar,foo} + loadAppCtxAndAssertCacheStats(BarFooProfilesTestCase.class, 1, 2, 1); + loadAppCtxAndAssertCacheStats(FooBarProfilesTestCase.class, 1, 3, 1); + loadAppCtxAndAssertCacheStats(FooBarProfilesTestCase.class, 1, 4, 1); + loadAppCtxAndAssertCacheStats(BarFooProfilesTestCase.class, 1, 5, 1); + } + + + @Configuration + static class Config { + } + + @ContextConfiguration(classes = Config.class, loader = AnnotationConfigContextLoader.class) + private static class AnnotationConfigContextLoaderTestCase { + } + + @ContextConfiguration(classes = Config.class, loader = CustomAnnotationConfigContextLoader.class) + private static class CustomAnnotationConfigContextLoaderTestCase { + } + + private static class CustomAnnotationConfigContextLoader extends AnnotationConfigContextLoader { + } + + @ActiveProfiles({ "foo", "bar" }) + @ContextConfiguration(classes = Config.class, loader = AnnotationConfigContextLoader.class) + private static class FooBarProfilesTestCase { + } + + @ActiveProfiles({ "bar", "foo" }) + @ContextConfiguration(classes = Config.class, loader = AnnotationConfigContextLoader.class) + private static class BarFooProfilesTestCase { + } + +}