Skip to content

Commit 970a959

Browse files
committed
Make testing infrastructure servlet API independent. Fixes #9391
1 parent 79c52a9 commit 970a959

File tree

9 files changed

+128
-71
lines changed

9 files changed

+128
-71
lines changed

grails-logging/build.gradle

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
11
dependencies {
2-
compile project(":grails-core"),
3-
project(':grails-web')
4-
5-
testCompile 'javax.servlet:javax.servlet-api:3.0.1'
2+
compile project(":grails-core")
63
}

grails-plugin-testing/build.gradle

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
dependencies {
22

3-
compile project(':grails-plugin-url-mappings'),
4-
project(':grails-plugin-databinding'),
5-
project(':grails-plugin-controllers'),
6-
project(':grails-plugin-domain-class'),
7-
project(':grails-plugin-gsp'),
8-
project(':grails-plugin-filters'),
9-
project(':grails-plugin-interceptors'),
10-
project(':grails-plugin-mimetypes'),
11-
project(':grails-plugin-converters'),
12-
project(':grails-plugin-rest'),
13-
project(':grails-plugin-codecs'),
14-
project(':grails-test')
3+
provided project(':grails-plugin-url-mappings'),
4+
project(':grails-plugin-databinding'),
5+
project(':grails-plugin-controllers'),
6+
project(':grails-plugin-domain-class'),
7+
project(':grails-plugin-gsp'),
8+
project(':grails-plugin-filters'),
9+
project(':grails-plugin-interceptors'),
10+
project(':grails-plugin-mimetypes'),
11+
project(':grails-plugin-converters'),
12+
project(':grails-plugin-rest'),
13+
project(':grails-plugin-codecs')
14+
15+
compile ( project(':grails-test') ) {
16+
exclude group: 'org.grails', module:'grails-web'
17+
exclude group: 'org.grails', module:'grails-plugin-mimetypes'
18+
}
19+
compile ( project(':grails-logging') ) {
20+
exclude group: 'org.grails', module:'grails-web'
21+
}
22+
compile ( project(':grails-async') )
1523

1624
compile("org.springframework:spring-test:${springVersion}") {
1725
exclude group: 'commons-logging', module:'commons-logging'

grails-plugin-testing/src/main/groovy/grails/test/mixin/support/GrailsUnitTestMixin.groovy

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414
* limitations under the License.
1515
*/
1616
package grails.test.mixin.support
17+
1718
import grails.config.Config
1819
import grails.core.GrailsApplication
1920
import groovy.transform.CompileStatic
2021
import junit.framework.AssertionFailedError
2122
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter
23+
import org.springframework.context.ConfigurableApplicationContext
2224
import org.springframework.context.MessageSource
23-
import org.springframework.web.context.support.GenericWebApplicationContext
2425
/**
2526
* A base unit testing mixin that watches for MetaClass changes and unbinds them on tear down.
2627
*
@@ -109,12 +110,12 @@ class GrailsUnitTestMixin extends TestMixinRuntimeSupport {
109110
runtime.publishEvent("defineBeans", [closure: closure], [immediateDelivery: immediateDelivery])
110111
}
111112

112-
GenericWebApplicationContext getApplicationContext() {
113+
ConfigurableApplicationContext getApplicationContext() {
113114
getMainContext()
114115
}
115116

116-
GenericWebApplicationContext getMainContext() {
117-
(GenericWebApplicationContext)grailsApplication.mainContext
117+
ConfigurableApplicationContext getMainContext() {
118+
(ConfigurableApplicationContext)grailsApplication.mainContext
118119
}
119120

120121
GrailsApplication getGrailsApplication() {

grails-plugin-testing/src/main/groovy/grails/test/runtime/CoreBeansTestPlugin.groovy

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,21 @@
1515
*/
1616

1717
package grails.test.runtime
18+
1819
import grails.core.GrailsApplication
1920
import grails.core.support.proxy.DefaultProxyHandler
2021
import grails.validation.ConstraintsEvaluator
2122
import groovy.transform.CompileStatic
2223
import groovy.transform.TypeCheckingMode
23-
import org.grails.plugins.databinding.DataBindingGrailsPlugin
2424
import org.grails.spring.beans.GrailsApplicationAwareBeanPostProcessor
2525
import org.grails.spring.context.support.GrailsPlaceholderConfigurer
2626
import org.grails.spring.context.support.MapBasedSmartPropertyOverrideConfigurer
2727
import org.grails.transaction.TransactionManagerPostProcessor
2828
import org.grails.validation.DefaultConstraintEvaluator
2929
import org.springframework.context.support.ConversionServiceFactoryBean
3030
import org.springframework.context.support.StaticMessageSource
31+
import org.springframework.util.ClassUtils
32+
3133
/**
3234
* a TestPlugin for TestRuntime that adds some generic beans that are
3335
* required in Grails applications
@@ -48,10 +50,12 @@ public class CoreBeansTestPlugin implements TestPlugin {
4850
conversionService(ConversionServiceFactoryBean)
4951
}
5052

51-
def plugin = new DataBindingGrailsPlugin()
52-
plugin.grailsApplication = grailsApplicationParam
53-
plugin.applicationContext = grailsApplicationParam.mainContext
54-
defineBeans(runtime, plugin.doWithSpring())
53+
if(ClassUtils.isPresent("org.grails.plugins.databinding.DataBindingGrailsPlugin", CoreBeansTestPlugin.classLoader)) {
54+
def plugin = ClassUtils.forName("org.grails.plugins.databinding.DataBindingGrailsPlugin").newInstance()
55+
plugin.grailsApplication = grailsApplicationParam
56+
plugin.applicationContext = grailsApplicationParam.mainContext
57+
defineBeans(runtime, plugin.doWithSpring())
58+
}
5559

5660
defineBeans(runtime) {
5761
xmlns context:"http://www.springframework.org/schema/context"

grails-plugin-testing/src/main/groovy/grails/test/runtime/DomainClassTestPlugin.groovy

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import org.grails.datastore.mapping.reflect.ClassPropertyFetcher
3434
import org.grails.datastore.mapping.simple.SimpleMapDatastore
3535
import org.grails.datastore.mapping.transactions.DatastoreTransactionManager
3636
import org.grails.validation.ConstraintsEvaluatorFactoryBean
37-
import org.springframework.web.context.ConfigurableWebApplicationContext
37+
import org.springframework.context.ConfigurableApplicationContext
3838

3939
/**
4040
* a TestPlugin for TestRuntime for adding Grails DomainClass (GORM) support
@@ -69,7 +69,7 @@ class DomainClassTestPlugin implements TestPlugin {
6969
}
7070

7171
protected void initializeDatastoreImplementation(GrailsApplication grailsApplication) {
72-
ConfigurableWebApplicationContext applicationContext = (ConfigurableWebApplicationContext)grailsApplication.mainContext
72+
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext)grailsApplication.mainContext
7373
SimpleMapDatastore simpleDatastore = applicationContext.getBean(SimpleMapDatastore)
7474
((AbstractMappingContext)simpleDatastore.mappingContext).setCanInitializeEntities(false)
7575
applicationContext.addApplicationListener applicationContext.getBean(GrailsDomainClassCleaner)
@@ -83,7 +83,7 @@ class DomainClassTestPlugin implements TestPlugin {
8383
}
8484

8585
protected void connectDatastore(TestRuntime runtime, GrailsApplication grailsApplication) {
86-
ConfigurableWebApplicationContext applicationContext = (ConfigurableWebApplicationContext)grailsApplication.mainContext
86+
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext)grailsApplication.mainContext
8787
SimpleMapDatastore simpleDatastore = applicationContext.getBean(SimpleMapDatastore)
8888
ConstrainedProperty.registerNewConstraint("unique", new UniqueConstraintFactory(simpleDatastore))
8989
Session currentSession = DatastoreUtils.bindSession(simpleDatastore.connect())
@@ -96,7 +96,7 @@ class DomainClassTestPlugin implements TestPlugin {
9696
currentSession.disconnect()
9797
DatastoreUtils.unbindSession(currentSession)
9898
}
99-
ConfigurableWebApplicationContext applicationContext = (ConfigurableWebApplicationContext)grailsApplication.mainContext
99+
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext)grailsApplication.mainContext
100100
SimpleMapDatastore simpleDatastore = applicationContext.getBean(SimpleMapDatastore)
101101
simpleDatastore.clearData()
102102
}

grails-plugin-testing/src/main/groovy/grails/test/runtime/GrailsApplicationTestPlugin.groovy

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -38,30 +38,26 @@ import org.grails.plugins.IncludingPluginFilter
3838
import org.grails.spring.RuntimeSpringConfiguration
3939
import org.grails.spring.beans.factory.OptimizedAutowireCapableBeanFactory
4040
import org.grails.web.context.ServletEnvironmentGrailsApplicationDiscoveryStrategy
41-
import org.grails.web.converters.configuration.ConvertersConfigurationHolder
4241
import org.grails.web.servlet.context.GrailsConfigUtils
4342
import org.slf4j.Logger
4443
import org.slf4j.LoggerFactory
4544
import org.springframework.beans.CachedIntrospectionResults
4645
import org.springframework.beans.MutablePropertyValues
4746
import org.springframework.beans.factory.config.BeanDefinition
47+
import org.springframework.beans.factory.config.ConfigurableBeanFactory
4848
import org.springframework.beans.factory.config.ConstructorArgumentValues
4949
import org.springframework.beans.factory.support.BeanDefinitionRegistry
50-
import org.springframework.beans.factory.support.DefaultListableBeanFactory
5150
import org.springframework.beans.factory.support.RootBeanDefinition
5251
import org.springframework.boot.test.ConfigFileApplicationContextInitializer
5352
import org.springframework.context.ApplicationContext
5453
import org.springframework.context.ConfigurableApplicationContext
5554
import org.springframework.context.annotation.AnnotationConfigUtils
56-
import org.springframework.mock.web.MockServletContext
5755
import org.springframework.test.annotation.DirtiesContext
5856
import org.springframework.test.context.MergedContextConfiguration
5957
import org.springframework.test.context.TestContextManager
60-
import org.springframework.web.context.support.GenericWebApplicationContext
58+
import org.springframework.util.ClassUtils
6159

62-
import javax.servlet.ServletContext
6360
import java.lang.reflect.Modifier
64-
6561
/**
6662
* A TestPlugin for TestRuntime that builds the GrailsApplication instance for tests
6763
*
@@ -72,6 +68,7 @@ import java.lang.reflect.Modifier
7268
@CompileStatic
7369
class GrailsApplicationTestPlugin implements TestPlugin {
7470
protected final Logger log = LoggerFactory.getLogger(getClass());
71+
protected static final boolean isServletApiPresent = ClassUtils.isPresent("javax.servlet.ServletContext", GrailsApplicationTestPlugin.classLoader)
7572

7673
static boolean disableClearSpringTestContextManagerCache = Boolean.getBoolean("grails.test.runtime.disable_clear_spring_tcf_cache")
7774
String[] requiredFeatures = ['metaClassCleaner']
@@ -89,17 +86,15 @@ class GrailsApplicationTestPlugin implements TestPlugin {
8986
}
9087

9188
void initGrailsApplication(final TestRuntime runtime, final Map callerInfo) {
92-
ServletContext servletContext = createServletContext(runtime, callerInfo)
89+
Object servletContext = createServletContext(runtime, callerInfo)
9390
runtime.putValue("servletContext", servletContext)
9491

95-
GenericWebApplicationContext mainContext = createMainContext(runtime, callerInfo, servletContext)
92+
ConfigurableApplicationContext mainContext = createMainContext(runtime, callerInfo, servletContext)
9693

9794
GrailsApplication grailsApplication = (GrailsApplication)runtime.getValueIfExists("grailsApplication")
9895

99-
if(servletContext != null) {
100-
Holders.setServletContext(servletContext);
101-
Holders.addApplicationDiscoveryStrategy(new ServletEnvironmentGrailsApplicationDiscoveryStrategy(servletContext));
102-
GrailsConfigUtils.configureServletContextAttributes(servletContext, grailsApplication, mainContext.getBean(GrailsPluginManager.BEAN_NAME, GrailsPluginManager), mainContext)
96+
if(isServletApiPresent && servletContext != null) {
97+
configureServletEnvironment(servletContext, grailsApplication, mainContext)
10398
}
10499

105100
if(!grailsApplication.isInitialised()) {
@@ -109,9 +104,25 @@ class GrailsApplicationTestPlugin implements TestPlugin {
109104
applicationInitialized(runtime, grailsApplication)
110105
}
111106

112-
protected GenericWebApplicationContext createMainContext(final TestRuntime runtime, final Map callerInfo, final ServletContext servletContext) {
113-
GenericWebApplicationContext context = new GenericWebApplicationContext(new OptimizedAutowireCapableBeanFactory(), servletContext);
114-
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory()
107+
@CompileDynamic
108+
protected void configureServletEnvironment(servletContext, GrailsApplication grailsApplication, ConfigurableApplicationContext mainContext) {
109+
Holders.setServletContext(servletContext);
110+
Holders.addApplicationDiscoveryStrategy(new ServletEnvironmentGrailsApplicationDiscoveryStrategy(servletContext));
111+
GrailsConfigUtils.configureServletContextAttributes(servletContext, grailsApplication, mainContext.getBean(GrailsPluginManager.BEAN_NAME, GrailsPluginManager), mainContext)
112+
}
113+
114+
protected ConfigurableApplicationContext createMainContext(final TestRuntime runtime, final Map callerInfo, final servletContext) {
115+
ConfigurableApplicationContext context
116+
117+
if(isServletApiPresent && servletContext != null) {
118+
context = (ConfigurableApplicationContext)ClassUtils.forName("org.springframework.web.context.support.GenericWebApplicationContext").newInstance( new OptimizedAutowireCapableBeanFactory(), servletContext);
119+
}
120+
else {
121+
context = (ConfigurableApplicationContext)ClassUtils.forName("org.springframework.context.support.GenericApplicationContext").newInstance( new OptimizedAutowireCapableBeanFactory());
122+
}
123+
124+
125+
ConfigurableBeanFactory beanFactory = context.getBeanFactory()
115126

116127
prepareContext(context, beanFactory, runtime, callerInfo);
117128
customizeContext(context, beanFactory, runtime, callerInfo);
@@ -121,17 +132,17 @@ class GrailsApplicationTestPlugin implements TestPlugin {
121132
return context
122133
}
123134

124-
protected void prepareContext(ConfigurableApplicationContext applicationContext, DefaultListableBeanFactory beanFactory, TestRuntime runtime, Map callerInfo) {
135+
protected void prepareContext(ConfigurableApplicationContext applicationContext, ConfigurableBeanFactory beanFactory, TestRuntime runtime, Map callerInfo) {
125136
registerGrailsAppPostProcessorBean(applicationContext, beanFactory, runtime, callerInfo)
126137

127-
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
138+
AnnotationConfigUtils.registerAnnotationConfigProcessors((BeanDefinitionRegistry)beanFactory);
128139

129140
ConfigFileApplicationContextInitializer contextInitializer = new ConfigFileApplicationContextInitializer();
130141
contextInitializer.initialize(applicationContext);
131142
}
132143

133144
@CompileDynamic
134-
protected void registerGrailsAppPostProcessorBean(ConfigurableApplicationContext applicationContext, DefaultListableBeanFactory beanFactory, TestRuntime runtime, Map callerInfo) {
145+
protected void registerGrailsAppPostProcessorBean(ConfigurableApplicationContext applicationContext, ConfigurableBeanFactory beanFactory, TestRuntime runtime, Map callerInfo) {
135146
Closure doWithSpringClosure = {
136147
GrailsApplication grailsApplication = (GrailsApplication)runtime.getValueIfExists("grailsApplication")
137148

@@ -159,13 +170,14 @@ class GrailsApplicationTestPlugin implements TestPlugin {
159170
beanFactory.registerBeanDefinition("grailsApplicationPostProcessor", beandef);
160171
}
161172

162-
protected void customizeContext(ConfigurableApplicationContext applicationContext, DefaultListableBeanFactory beanFactory, TestRuntime runtime, Map callerInfo) {
173+
protected void customizeContext(ConfigurableApplicationContext applicationContext, ConfigurableBeanFactory beanFactory, TestRuntime runtime, Map callerInfo) {
163174

164175
}
165176

166-
protected ServletContext createServletContext(final TestRuntime runtime, final Map callerInfo) {
167-
MockServletContext servletContext = new MockServletContext()
168-
return servletContext
177+
protected Object createServletContext(final TestRuntime runtime, final Map callerInfo) {
178+
if(isServletApiPresent) {
179+
return ClassUtils.forName("org.springframework.mock.web.MockServletContext").newInstance()
180+
}
169181
}
170182

171183
protected void customizeGrailsApplication(final GrailsApplication grailsApplication, final TestRuntime runtime, final Map callerInfo) {
@@ -273,7 +285,14 @@ class GrailsApplicationTestPlugin implements TestPlugin {
273285
((DefaultGrailsApplication)runtime.getValue('grailsApplication'))?.clear()
274286
}
275287
runtime.removeValue("loadedCodecs")
276-
ConvertersConfigurationHolder.clear()
288+
if(ClassUtils.isPresent("org.grails.web.converters.configuration.ConvertersConfigurationHolder", getClass().classLoader)) {
289+
clearConvertersHolder()
290+
}
291+
}
292+
293+
@CompileDynamic
294+
void clearConvertersHolder() {
295+
ClassUtils.forName("org.grails.web.converters.configuration.ConvertersConfigurationHolder", getClass().classLoader).clear()
277296
}
278297

279298
void shutdownApplicationContext(TestRuntime runtime) {
@@ -296,8 +315,10 @@ class GrailsApplicationTestPlugin implements TestPlugin {
296315
DeferredBindingActions.clear()
297316

298317
runtime.removeValue("grailsApplication")
299-
300-
Holders.setServletContext null
318+
319+
if(isServletApiPresent) {
320+
Holders.setServletContext null
321+
}
301322
runtime.removeValue("servletContext")
302323

303324
Promises.promiseFactory = null

grails-plugin-testing/src/main/groovy/grails/test/runtime/TestRuntimeFactory.groovy

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import grails.test.mixin.TestRuntimeAwareMixin
2020
import grails.test.mixin.UseTestPlugin
2121
import grails.test.mixin.support.MixinInstance
2222
import groovy.transform.CompileStatic
23+
import groovy.util.logging.Slf4j
2324
import org.grails.core.io.support.GrailsFactoriesLoader
2425

2526
import java.lang.reflect.Field
@@ -39,6 +40,7 @@ import org.springframework.util.ReflectionUtils
3940
*/
4041
@CompileStatic
4142
@Singleton
43+
@Slf4j
4244
class TestRuntimeFactory {
4345
private static Set<Class<? extends TestPlugin>> availablePluginClasses = new HashSet<>()
4446

@@ -265,7 +267,7 @@ class TestRuntimeFactory {
265267

266268
Set<Class<? extends TestPlugin>> pluginClassesToUse = resolvePluginClassesToUse(runtimeSettings)
267269

268-
List<TestPlugin> availablePlugins = pluginClassesToUse.collect { Class<? extends TestPlugin> clazz -> clazz.newInstance() }
270+
List<TestPlugin> availablePlugins = instantiatePlugins(pluginClassesToUse)
269271
for(TestPlugin plugin : availablePlugins) {
270272
for(String feature : plugin.getProvidedFeatures()) {
271273
def pluginList = featureToPlugins.get(feature)
@@ -286,6 +288,21 @@ class TestRuntimeFactory {
286288
}
287289
}
288290

291+
protected List<TestPlugin> instantiatePlugins(Set<Class<? extends TestPlugin>> pluginClassesToUse) {
292+
List<TestPlugin> plugins = []
293+
for(Class<? extends TestPlugin> testPlugin in pluginClassesToUse) {
294+
try {
295+
plugins.add(
296+
testPlugin.newInstance()
297+
)
298+
299+
} catch (Throwable e) {
300+
log.debug("Error creating test plugin: ${e.message} due to missing dependencies. Ignoring.", e)
301+
}
302+
}
303+
return plugins
304+
}
305+
289306
private Set<Class<? extends TestPlugin>> resolvePluginClassesToUse(TestRuntimeSettings runtimeSettings) {
290307
Set<Class<? extends TestPlugin>> pluginClassesToUse = [] as Set
291308
pluginClassesToUse.addAll(availablePluginClasses)

0 commit comments

Comments
 (0)