11/*
2- * Copyright 2002-2015 the original author or authors.
2+ * Copyright 2002-2016 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.
1818
1919import java .util .ArrayList ;
2020import java .util .Arrays ;
21+ import java .util .Collection ;
2122import java .util .Collections ;
2223import java .util .HashSet ;
2324import java .util .LinkedHashSet ;
3031
3132import org .springframework .beans .BeanInstantiationException ;
3233import org .springframework .beans .BeanUtils ;
33- import org .springframework .context .ApplicationContextInitializer ;
34- import org .springframework .context .ConfigurableApplicationContext ;
3534import org .springframework .core .annotation .AnnotationAwareOrderComparator ;
3635import org .springframework .core .annotation .AnnotationUtils ;
3736import org .springframework .core .io .support .SpringFactoriesLoader ;
7170 *
7271 * @author Sam Brannen
7372 * @author Juergen Hoeller
73+ * @author Phillip Webb
7474 * @since 4.1
7575 */
7676public abstract class AbstractTestContextBootstrapper implements TestContextBootstrapper {
@@ -272,13 +272,8 @@ public final MergedContextConfiguration buildMergedContextConfiguration() {
272272CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate = getCacheAwareContextLoaderDelegate ();
273273
274274if (MetaAnnotationUtils .findAnnotationDescriptorForTypes (testClass , ContextConfiguration .class ,
275- ContextHierarchy .class ) == null ) {
276- if (logger .isInfoEnabled ()) {
277- logger .info (String .format (
278- "Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s]" ,
279- testClass .getName ()));
280- }
281- return new MergedContextConfiguration (testClass , null , null , null , null );
275+ ContextHierarchy .class ) == null ) {
276+ return buildDefaultMergedContextConfiguration (testClass , cacheAwareContextLoaderDelegate );
282277}
283278
284279if (AnnotationUtils .findAnnotation (testClass , ContextHierarchy .class ) != null ) {
@@ -297,7 +292,7 @@ public final MergedContextConfiguration buildMergedContextConfiguration() {
297292Class <?> declaringClass = reversedList .get (0 ).getDeclaringClass ();
298293
299294mergedConfig = buildMergedContextConfiguration (declaringClass , reversedList , parentConfig ,
300- cacheAwareContextLoaderDelegate );
295+ cacheAwareContextLoaderDelegate , true );
301296parentConfig = mergedConfig ;
302297}
303298
@@ -307,10 +302,29 @@ public final MergedContextConfiguration buildMergedContextConfiguration() {
307302else {
308303return buildMergedContextConfiguration (testClass ,
309304ContextLoaderUtils .resolveContextConfigurationAttributes (testClass ), null ,
310- cacheAwareContextLoaderDelegate );
305+ cacheAwareContextLoaderDelegate , true );
311306}
312307}
313308
309+ /**
310+ * @since 4.3
311+ */
312+ private MergedContextConfiguration buildDefaultMergedContextConfiguration (Class <?> testClass ,
313+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate ) {
314+
315+ List <ContextConfigurationAttributes > defaultConfigAttributesList
316+ = Collections .singletonList (new ContextConfigurationAttributes (testClass ));
317+
318+ ContextLoader contextLoader = resolveContextLoader (testClass , defaultConfigAttributesList );
319+ if (logger .isInfoEnabled ()) {
320+ logger .info (String .format (
321+ "Neither @ContextConfiguration nor @ContextHierarchy found for test class [%s], using %s" ,
322+ testClass .getName (), contextLoader .getClass ().getSimpleName ()));
323+ }
324+ return buildMergedContextConfiguration (testClass , defaultConfigAttributesList , null ,
325+ cacheAwareContextLoaderDelegate , false );
326+ }
327+
314328/**
315329 * Build the {@link MergedContextConfiguration merged context configuration}
316330 * for the supplied {@link Class testClass}, context configuration attributes,
@@ -324,6 +338,9 @@ public final MergedContextConfiguration buildMergedContextConfiguration() {
324338 * context in a context hierarchy, or {@code null} if there is no parent
325339 * @param cacheAwareContextLoaderDelegate the cache-aware context loader delegate to
326340 * be passed to the {@code MergedContextConfiguration} constructor
341+ * @param requireLocationsClassesOrInitializers whether locations, classes, or
342+ * initializers are required; typically {@code true} but may be set to {@code false}
343+ * if the configured loader supports empty configuration
327344 * @return the merged context configuration
328345 * @see #resolveContextLoader
329346 * @see ContextLoaderUtils#resolveContextConfigurationAttributes
@@ -335,11 +352,15 @@ public final MergedContextConfiguration buildMergedContextConfiguration() {
335352 */
336353private MergedContextConfiguration buildMergedContextConfiguration (Class <?> testClass ,
337354List <ContextConfigurationAttributes > configAttributesList , MergedContextConfiguration parentConfig ,
338- CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate ) {
355+ CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate ,
356+ boolean requireLocationsClassesOrInitializers ) {
357+
358+ Assert .notEmpty (configAttributesList , "ContextConfigurationAttributes list must not be null or empty" );
339359
340360ContextLoader contextLoader = resolveContextLoader (testClass , configAttributesList );
341- List <String > locationsList = new ArrayList <String >();
342- List <Class <?>> classesList = new ArrayList <Class <?>>();
361+ List <String > locations = new ArrayList <String >();
362+ List <Class <?>> classes = new ArrayList <Class <?>>();
363+ List <Class <?>> initializers = new ArrayList <Class <?>>();
343364
344365for (ContextConfigurationAttributes configAttributes : configAttributesList ) {
345366if (logger .isTraceEnabled ()) {
@@ -349,34 +370,53 @@ private MergedContextConfiguration buildMergedContextConfiguration(Class<?> test
349370if (contextLoader instanceof SmartContextLoader ) {
350371SmartContextLoader smartContextLoader = (SmartContextLoader ) contextLoader ;
351372smartContextLoader .processContextConfiguration (configAttributes );
352- locationsList .addAll (0 , Arrays .asList (configAttributes .getLocations ()));
353- classesList .addAll (0 , Arrays .asList (configAttributes .getClasses ()));
373+ locations .addAll (0 , Arrays .asList (configAttributes .getLocations ()));
374+ classes .addAll (0 , Arrays .asList (configAttributes .getClasses ()));
354375}
355376else {
356- String [] processedLocations = contextLoader .processLocations (configAttributes . getDeclaringClass (),
357- configAttributes .getLocations ());
358- locationsList .addAll (0 , Arrays .asList (processedLocations ));
377+ String [] processedLocations = contextLoader .processLocations (
378+ configAttributes . getDeclaringClass (), configAttributes .getLocations ());
379+ locations .addAll (0 , Arrays .asList (processedLocations ));
359380// Legacy ContextLoaders don't know how to process classes
360381}
382+ initializers .addAll (0 , Arrays .asList (configAttributes .getInitializers ()));
361383if (!configAttributes .isInheritLocations ()) {
362384break ;
363385}
364386}
365387
366- String [] locations = StringUtils . toStringArray ( locationsList );
367- Class <?>[] classes = ClassUtils . toClassArray ( classesList );
368- Set < Class <? extends ApplicationContextInitializer <? extends ConfigurableApplicationContext >>> initializerClasses = //
369- ApplicationContextInitializerUtils . resolveInitializerClasses ( configAttributesList );
370- String [] activeProfiles = ActiveProfilesUtils . resolveActiveProfiles ( testClass );
371- MergedTestPropertySources mergedTestPropertySources = TestPropertySourceUtils . buildMergedTestPropertySources ( testClass );
388+ if ( requireLocationsClassesOrInitializers && areAllEmpty ( locations , classes , initializers )) {
389+ throw new IllegalStateException ( String . format (
390+ "%s was unable to detect defaults, and no ApplicationContextInitializers "
391+ + "were declared for context configuration attributes %s" ,
392+ contextLoader . getClass (). getSimpleName (), configAttributesList ) );
393+ }
372394
373- MergedContextConfiguration mergedConfig = new MergedContextConfiguration (testClass , locations , classes ,
374- initializerClasses , activeProfiles , mergedTestPropertySources .getLocations (),
375- mergedTestPropertySources .getProperties (), contextLoader , cacheAwareContextLoaderDelegate , parentConfig );
395+ MergedTestPropertySources mergedTestPropertySources = TestPropertySourceUtils .buildMergedTestPropertySources (testClass );
396+ MergedContextConfiguration mergedConfig = new MergedContextConfiguration (testClass ,
397+ StringUtils .toStringArray (locations ),
398+ ClassUtils .toClassArray (classes ),
399+ ApplicationContextInitializerUtils .resolveInitializerClasses (configAttributesList ),
400+ ActiveProfilesUtils .resolveActiveProfiles (testClass ),
401+ mergedTestPropertySources .getLocations (),
402+ mergedTestPropertySources .getProperties (),
403+ contextLoader , cacheAwareContextLoaderDelegate , parentConfig );
376404
377405return processMergedContextConfiguration (mergedConfig );
378406}
379407
408+ /**
409+ * @since 4.3
410+ */
411+ private boolean areAllEmpty (Collection <?>... collections ) {
412+ for (Collection <?> collection : collections ) {
413+ if (!collection .isEmpty ()) {
414+ return false ;
415+ }
416+ }
417+ return true ;
418+ }
419+
380420/**
381421 * Resolve the {@link ContextLoader} {@linkplain Class class} to use for the
382422 * supplied list of {@link ContextConfigurationAttributes} and then instantiate
@@ -389,7 +429,7 @@ private MergedContextConfiguration buildMergedContextConfiguration(Class<?> test
389429 * @param testClass the test class for which the {@code ContextLoader} should be
390430 * resolved; must not be {@code null}
391431 * @param configAttributesList the list of configuration attributes to process; must
392- * not be {@code null} or <em>empty</em> ; must be ordered <em>bottom-up</em>
432+ * not be {@code null}; must be ordered <em>bottom-up</em>
393433 * (i.e., as if we were traversing up the class hierarchy)
394434 * @return the resolved {@code ContextLoader} for the supplied {@code testClass}
395435 * (never {@code null})
@@ -400,7 +440,7 @@ protected ContextLoader resolveContextLoader(Class<?> testClass,
400440List <ContextConfigurationAttributes > configAttributesList ) {
401441
402442Assert .notNull (testClass , "Class must not be null" );
403- Assert .notEmpty (configAttributesList , "ContextConfigurationAttributes list must not be empty " );
443+ Assert .notNull (configAttributesList , "ContextConfigurationAttributes list must not be null " );
404444
405445Class <? extends ContextLoader > contextLoaderClass = resolveExplicitContextLoaderClass (configAttributesList );
406446if (contextLoaderClass == null ) {
@@ -429,7 +469,7 @@ protected ContextLoader resolveContextLoader(Class<?> testClass,
429469 * step #1.</li>
430470 * </ol>
431471 * @param configAttributesList the list of configuration attributes to process;
432- * must not be {@code null} or <em>empty</em> ; must be ordered <em>bottom-up</em>
472+ * must not be {@code null}; must be ordered <em>bottom-up</em>
433473 * (i.e., as if we were traversing up the class hierarchy)
434474 * @return the {@code ContextLoader} class to use for the supplied configuration
435475 * attributes, or {@code null} if no explicit loader is found
@@ -439,7 +479,8 @@ protected ContextLoader resolveContextLoader(Class<?> testClass,
439479protected Class <? extends ContextLoader > resolveExplicitContextLoaderClass (
440480List <ContextConfigurationAttributes > configAttributesList ) {
441481
442- Assert .notEmpty (configAttributesList , "ContextConfigurationAttributes list must not be empty" );
482+ Assert .notNull (configAttributesList , "ContextConfigurationAttributes list must not be null" );
483+
443484for (ContextConfigurationAttributes configAttributes : configAttributesList ) {
444485if (logger .isTraceEnabled ()) {
445486logger .trace (String .format ("Resolving ContextLoader for context configuration attributes %s" ,
0 commit comments