Skip to content

Commit

Permalink
Make testing infrastructure servlet API independent. Fixes #9391
Browse files Browse the repository at this point in the history
  • Loading branch information
graemerocher committed Dec 1, 2015
1 parent 79c52a9 commit 970a959
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 71 deletions.
5 changes: 1 addition & 4 deletions grails-logging/build.gradle
@@ -1,6 +1,3 @@
dependencies {
compile project(":grails-core"),
project(':grails-web')

testCompile 'javax.servlet:javax.servlet-api:3.0.1'
compile project(":grails-core")
}
32 changes: 20 additions & 12 deletions grails-plugin-testing/build.gradle
@@ -1,17 +1,25 @@
dependencies {

compile project(':grails-plugin-url-mappings'),
project(':grails-plugin-databinding'),
project(':grails-plugin-controllers'),
project(':grails-plugin-domain-class'),
project(':grails-plugin-gsp'),
project(':grails-plugin-filters'),
project(':grails-plugin-interceptors'),
project(':grails-plugin-mimetypes'),
project(':grails-plugin-converters'),
project(':grails-plugin-rest'),
project(':grails-plugin-codecs'),
project(':grails-test')
provided project(':grails-plugin-url-mappings'),
project(':grails-plugin-databinding'),
project(':grails-plugin-controllers'),
project(':grails-plugin-domain-class'),
project(':grails-plugin-gsp'),
project(':grails-plugin-filters'),
project(':grails-plugin-interceptors'),
project(':grails-plugin-mimetypes'),
project(':grails-plugin-converters'),
project(':grails-plugin-rest'),
project(':grails-plugin-codecs')

compile ( project(':grails-test') ) {
exclude group: 'org.grails', module:'grails-web'
exclude group: 'org.grails', module:'grails-plugin-mimetypes'
}
compile ( project(':grails-logging') ) {
exclude group: 'org.grails', module:'grails-web'
}
compile ( project(':grails-async') )

compile("org.springframework:spring-test:${springVersion}") {
exclude group: 'commons-logging', module:'commons-logging'
Expand Down
Expand Up @@ -14,13 +14,14 @@
* limitations under the License.
*/
package grails.test.mixin.support

import grails.config.Config
import grails.core.GrailsApplication
import groovy.transform.CompileStatic
import junit.framework.AssertionFailedError
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.MessageSource
import org.springframework.web.context.support.GenericWebApplicationContext
/**
* A base unit testing mixin that watches for MetaClass changes and unbinds them on tear down.
*
Expand Down Expand Up @@ -109,12 +110,12 @@ class GrailsUnitTestMixin extends TestMixinRuntimeSupport {
runtime.publishEvent("defineBeans", [closure: closure], [immediateDelivery: immediateDelivery])
}

GenericWebApplicationContext getApplicationContext() {
ConfigurableApplicationContext getApplicationContext() {
getMainContext()
}

GenericWebApplicationContext getMainContext() {
(GenericWebApplicationContext)grailsApplication.mainContext
ConfigurableApplicationContext getMainContext() {
(ConfigurableApplicationContext)grailsApplication.mainContext
}

GrailsApplication getGrailsApplication() {
Expand Down
Expand Up @@ -15,19 +15,21 @@
*/

package grails.test.runtime

import grails.core.GrailsApplication
import grails.core.support.proxy.DefaultProxyHandler
import grails.validation.ConstraintsEvaluator
import groovy.transform.CompileStatic
import groovy.transform.TypeCheckingMode
import org.grails.plugins.databinding.DataBindingGrailsPlugin
import org.grails.spring.beans.GrailsApplicationAwareBeanPostProcessor
import org.grails.spring.context.support.GrailsPlaceholderConfigurer
import org.grails.spring.context.support.MapBasedSmartPropertyOverrideConfigurer
import org.grails.transaction.TransactionManagerPostProcessor
import org.grails.validation.DefaultConstraintEvaluator
import org.springframework.context.support.ConversionServiceFactoryBean
import org.springframework.context.support.StaticMessageSource
import org.springframework.util.ClassUtils

/**
* a TestPlugin for TestRuntime that adds some generic beans that are
* required in Grails applications
Expand All @@ -48,10 +50,12 @@ public class CoreBeansTestPlugin implements TestPlugin {
conversionService(ConversionServiceFactoryBean)
}

def plugin = new DataBindingGrailsPlugin()
plugin.grailsApplication = grailsApplicationParam
plugin.applicationContext = grailsApplicationParam.mainContext
defineBeans(runtime, plugin.doWithSpring())
if(ClassUtils.isPresent("org.grails.plugins.databinding.DataBindingGrailsPlugin", CoreBeansTestPlugin.classLoader)) {
def plugin = ClassUtils.forName("org.grails.plugins.databinding.DataBindingGrailsPlugin").newInstance()
plugin.grailsApplication = grailsApplicationParam
plugin.applicationContext = grailsApplicationParam.mainContext
defineBeans(runtime, plugin.doWithSpring())
}

defineBeans(runtime) {
xmlns context:"http://www.springframework.org/schema/context"
Expand Down
Expand Up @@ -34,7 +34,7 @@ import org.grails.datastore.mapping.reflect.ClassPropertyFetcher
import org.grails.datastore.mapping.simple.SimpleMapDatastore
import org.grails.datastore.mapping.transactions.DatastoreTransactionManager
import org.grails.validation.ConstraintsEvaluatorFactoryBean
import org.springframework.web.context.ConfigurableWebApplicationContext
import org.springframework.context.ConfigurableApplicationContext

/**
* a TestPlugin for TestRuntime for adding Grails DomainClass (GORM) support
Expand Down Expand Up @@ -69,7 +69,7 @@ class DomainClassTestPlugin implements TestPlugin {
}

protected void initializeDatastoreImplementation(GrailsApplication grailsApplication) {
ConfigurableWebApplicationContext applicationContext = (ConfigurableWebApplicationContext)grailsApplication.mainContext
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext)grailsApplication.mainContext
SimpleMapDatastore simpleDatastore = applicationContext.getBean(SimpleMapDatastore)
((AbstractMappingContext)simpleDatastore.mappingContext).setCanInitializeEntities(false)
applicationContext.addApplicationListener applicationContext.getBean(GrailsDomainClassCleaner)
Expand All @@ -83,7 +83,7 @@ class DomainClassTestPlugin implements TestPlugin {
}

protected void connectDatastore(TestRuntime runtime, GrailsApplication grailsApplication) {
ConfigurableWebApplicationContext applicationContext = (ConfigurableWebApplicationContext)grailsApplication.mainContext
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext)grailsApplication.mainContext
SimpleMapDatastore simpleDatastore = applicationContext.getBean(SimpleMapDatastore)
ConstrainedProperty.registerNewConstraint("unique", new UniqueConstraintFactory(simpleDatastore))
Session currentSession = DatastoreUtils.bindSession(simpleDatastore.connect())
Expand All @@ -96,7 +96,7 @@ class DomainClassTestPlugin implements TestPlugin {
currentSession.disconnect()
DatastoreUtils.unbindSession(currentSession)
}
ConfigurableWebApplicationContext applicationContext = (ConfigurableWebApplicationContext)grailsApplication.mainContext
ConfigurableApplicationContext applicationContext = (ConfigurableApplicationContext)grailsApplication.mainContext
SimpleMapDatastore simpleDatastore = applicationContext.getBean(SimpleMapDatastore)
simpleDatastore.clearData()
}
Expand Down
Expand Up @@ -38,30 +38,26 @@ import org.grails.plugins.IncludingPluginFilter
import org.grails.spring.RuntimeSpringConfiguration
import org.grails.spring.beans.factory.OptimizedAutowireCapableBeanFactory
import org.grails.web.context.ServletEnvironmentGrailsApplicationDiscoveryStrategy
import org.grails.web.converters.configuration.ConvertersConfigurationHolder
import org.grails.web.servlet.context.GrailsConfigUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.CachedIntrospectionResults
import org.springframework.beans.MutablePropertyValues
import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.beans.factory.config.ConfigurableBeanFactory
import org.springframework.beans.factory.config.ConstructorArgumentValues
import org.springframework.beans.factory.support.BeanDefinitionRegistry
import org.springframework.beans.factory.support.DefaultListableBeanFactory
import org.springframework.beans.factory.support.RootBeanDefinition
import org.springframework.boot.test.ConfigFileApplicationContextInitializer
import org.springframework.context.ApplicationContext
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.AnnotationConfigUtils
import org.springframework.mock.web.MockServletContext
import org.springframework.test.annotation.DirtiesContext
import org.springframework.test.context.MergedContextConfiguration
import org.springframework.test.context.TestContextManager
import org.springframework.web.context.support.GenericWebApplicationContext
import org.springframework.util.ClassUtils

import javax.servlet.ServletContext
import java.lang.reflect.Modifier

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

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

void initGrailsApplication(final TestRuntime runtime, final Map callerInfo) {
ServletContext servletContext = createServletContext(runtime, callerInfo)
Object servletContext = createServletContext(runtime, callerInfo)
runtime.putValue("servletContext", servletContext)

GenericWebApplicationContext mainContext = createMainContext(runtime, callerInfo, servletContext)
ConfigurableApplicationContext mainContext = createMainContext(runtime, callerInfo, servletContext)

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

if(servletContext != null) {
Holders.setServletContext(servletContext);
Holders.addApplicationDiscoveryStrategy(new ServletEnvironmentGrailsApplicationDiscoveryStrategy(servletContext));
GrailsConfigUtils.configureServletContextAttributes(servletContext, grailsApplication, mainContext.getBean(GrailsPluginManager.BEAN_NAME, GrailsPluginManager), mainContext)
if(isServletApiPresent && servletContext != null) {
configureServletEnvironment(servletContext, grailsApplication, mainContext)
}

if(!grailsApplication.isInitialised()) {
Expand All @@ -109,9 +104,25 @@ class GrailsApplicationTestPlugin implements TestPlugin {
applicationInitialized(runtime, grailsApplication)
}

protected GenericWebApplicationContext createMainContext(final TestRuntime runtime, final Map callerInfo, final ServletContext servletContext) {
GenericWebApplicationContext context = new GenericWebApplicationContext(new OptimizedAutowireCapableBeanFactory(), servletContext);
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory()
@CompileDynamic
protected void configureServletEnvironment(servletContext, GrailsApplication grailsApplication, ConfigurableApplicationContext mainContext) {
Holders.setServletContext(servletContext);
Holders.addApplicationDiscoveryStrategy(new ServletEnvironmentGrailsApplicationDiscoveryStrategy(servletContext));
GrailsConfigUtils.configureServletContextAttributes(servletContext, grailsApplication, mainContext.getBean(GrailsPluginManager.BEAN_NAME, GrailsPluginManager), mainContext)
}

protected ConfigurableApplicationContext createMainContext(final TestRuntime runtime, final Map callerInfo, final servletContext) {
ConfigurableApplicationContext context

if(isServletApiPresent && servletContext != null) {
context = (ConfigurableApplicationContext)ClassUtils.forName("org.springframework.web.context.support.GenericWebApplicationContext").newInstance( new OptimizedAutowireCapableBeanFactory(), servletContext);
}
else {
context = (ConfigurableApplicationContext)ClassUtils.forName("org.springframework.context.support.GenericApplicationContext").newInstance( new OptimizedAutowireCapableBeanFactory());
}


ConfigurableBeanFactory beanFactory = context.getBeanFactory()

prepareContext(context, beanFactory, runtime, callerInfo);
customizeContext(context, beanFactory, runtime, callerInfo);
Expand All @@ -121,17 +132,17 @@ class GrailsApplicationTestPlugin implements TestPlugin {
return context
}

protected void prepareContext(ConfigurableApplicationContext applicationContext, DefaultListableBeanFactory beanFactory, TestRuntime runtime, Map callerInfo) {
protected void prepareContext(ConfigurableApplicationContext applicationContext, ConfigurableBeanFactory beanFactory, TestRuntime runtime, Map callerInfo) {
registerGrailsAppPostProcessorBean(applicationContext, beanFactory, runtime, callerInfo)

AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
AnnotationConfigUtils.registerAnnotationConfigProcessors((BeanDefinitionRegistry)beanFactory);

ConfigFileApplicationContextInitializer contextInitializer = new ConfigFileApplicationContextInitializer();
contextInitializer.initialize(applicationContext);
}

@CompileDynamic
protected void registerGrailsAppPostProcessorBean(ConfigurableApplicationContext applicationContext, DefaultListableBeanFactory beanFactory, TestRuntime runtime, Map callerInfo) {
protected void registerGrailsAppPostProcessorBean(ConfigurableApplicationContext applicationContext, ConfigurableBeanFactory beanFactory, TestRuntime runtime, Map callerInfo) {
Closure doWithSpringClosure = {
GrailsApplication grailsApplication = (GrailsApplication)runtime.getValueIfExists("grailsApplication")

Expand Down Expand Up @@ -159,13 +170,14 @@ class GrailsApplicationTestPlugin implements TestPlugin {
beanFactory.registerBeanDefinition("grailsApplicationPostProcessor", beandef);
}

protected void customizeContext(ConfigurableApplicationContext applicationContext, DefaultListableBeanFactory beanFactory, TestRuntime runtime, Map callerInfo) {
protected void customizeContext(ConfigurableApplicationContext applicationContext, ConfigurableBeanFactory beanFactory, TestRuntime runtime, Map callerInfo) {

}

protected ServletContext createServletContext(final TestRuntime runtime, final Map callerInfo) {
MockServletContext servletContext = new MockServletContext()
return servletContext
protected Object createServletContext(final TestRuntime runtime, final Map callerInfo) {
if(isServletApiPresent) {
return ClassUtils.forName("org.springframework.mock.web.MockServletContext").newInstance()
}
}

protected void customizeGrailsApplication(final GrailsApplication grailsApplication, final TestRuntime runtime, final Map callerInfo) {
Expand Down Expand Up @@ -273,7 +285,14 @@ class GrailsApplicationTestPlugin implements TestPlugin {
((DefaultGrailsApplication)runtime.getValue('grailsApplication'))?.clear()
}
runtime.removeValue("loadedCodecs")
ConvertersConfigurationHolder.clear()
if(ClassUtils.isPresent("org.grails.web.converters.configuration.ConvertersConfigurationHolder", getClass().classLoader)) {
clearConvertersHolder()
}
}

@CompileDynamic
void clearConvertersHolder() {
ClassUtils.forName("org.grails.web.converters.configuration.ConvertersConfigurationHolder", getClass().classLoader).clear()
}

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

runtime.removeValue("grailsApplication")

Holders.setServletContext null

if(isServletApiPresent) {
Holders.setServletContext null
}
runtime.removeValue("servletContext")

Promises.promiseFactory = null
Expand Down
Expand Up @@ -20,6 +20,7 @@ import grails.test.mixin.TestRuntimeAwareMixin
import grails.test.mixin.UseTestPlugin
import grails.test.mixin.support.MixinInstance
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import org.grails.core.io.support.GrailsFactoriesLoader

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

Expand Down Expand Up @@ -265,7 +267,7 @@ class TestRuntimeFactory {

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

List<TestPlugin> availablePlugins = pluginClassesToUse.collect { Class<? extends TestPlugin> clazz -> clazz.newInstance() }
List<TestPlugin> availablePlugins = instantiatePlugins(pluginClassesToUse)
for(TestPlugin plugin : availablePlugins) {
for(String feature : plugin.getProvidedFeatures()) {
def pluginList = featureToPlugins.get(feature)
Expand All @@ -286,6 +288,21 @@ class TestRuntimeFactory {
}
}

protected List<TestPlugin> instantiatePlugins(Set<Class<? extends TestPlugin>> pluginClassesToUse) {
List<TestPlugin> plugins = []
for(Class<? extends TestPlugin> testPlugin in pluginClassesToUse) {
try {
plugins.add(
testPlugin.newInstance()
)

} catch (Throwable e) {
log.debug("Error creating test plugin: ${e.message} due to missing dependencies. Ignoring.", e)
}
}
return plugins
}

private Set<Class<? extends TestPlugin>> resolvePluginClassesToUse(TestRuntimeSettings runtimeSettings) {
Set<Class<? extends TestPlugin>> pluginClassesToUse = [] as Set
pluginClassesToUse.addAll(availablePluginClasses)
Expand Down

0 comments on commit 970a959

Please sign in to comment.