Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle NoClassDefFoundError for TestExecutionListeners consistently in the TestContext framework [SPR-11347] #15971

Closed
1 task done
spring-projects-issues opened this issue Jan 22, 2014 · 9 comments

Comments

@spring-projects-issues
Copy link
Collaborator

@spring-projects-issues spring-projects-issues commented Jan 22, 2014

Gary Russell opened SPR-11347 and commented

Background

Changes introduced in #15964 cause subclasses of abstract base test classes (i.e. AbstractJUnit4SpringContextTests and AbstractTestNGSpringContextTests) in a non-web environment to fail with java.lang.NoClassDefFoundError: javax/servlet/ServletContext.

There are a number of such test classes in Spring Integration that now break due to #15964.


Analysis

The fact that abstract base test classes pull in ServletTestExecutionListener is not problematic on its own (at least not with regard to the intended behavior of the TestContext framework's support for default listeners). Rather, the issue here is that a NoClassDefFoundError is handled differently for implicit default listeners (i.e., listeners not declared via @TestExecutionListeners) and listeners explicitly declared via @TestExecutionListeners.

The following code snippet from TestContextManager.retrieveTestExecutionListeners() represents the status quo.

try {
	listeners.add(BeanUtils.instantiateClass(listenerClass));
}
catch (NoClassDefFoundError err) {
	if (defaultListeners) {
		if (logger.isDebugEnabled()) {
			logger.debug("Could not instantiate default TestExecutionListener class ["
					+ listenerClass.getName()
					+ "]. Specify custom listener classes or make the default listener classes available.");
		}
	}
	else {
		throw err;
	}
}

In the case of AbstractJUnit4SpringContextTests and AbstractTestNGSpringContextTests, the ServletTestExecutionListener is explicitly declared via @TestExecutionListeners as a convenience for the developer; however, ServletTestExecutionListener is still considered a default from the perspective of the end user. The try-catch block in retrieveTestExecutionListeners() should therefore be amended to work as intended for both implicit and explicit defaults.


Deliverables

  1. Refactor the try-catch block in TestContextManager.retrieveTestExecutionListeners() as follows:
    1. Always swallow the NoClassDefFoundError.
    2. Change log level from DEBUG to INFO.
    3. Do not include the stack trace with log output.

Affects: 4.0.1

Issue Links:

  • #15964 ServletTestExecutionListener is not enabled by default in abstract base test classes
  • #16424 getTestExecutionListeners() doesn't detect NoClassDefFoundError within BeanInstantiationException

Referenced from: commits fb12e23, c1569d7

Backported to: 3.2.7

0 votes, 6 watchers

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 22, 2014

Juergen Hoeller commented

Sam, can we maybe check for the presence of the Servlet API on the classpath there, and just register the ServletTestExecutionListener conditionally?

Juergen

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 22, 2014

Sam Brannen commented

Gary Russell, can you please provide a stacktrace for one of those failing tests ASAP?

I'd like to see where the in the code the NoClassDefFoundError is being thrown.

Thanks,

Sam

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 22, 2014

Bruce Brouwer commented

I just ran into this as well. Here is my stack trace (from using 4.0.1.BUILD-SNAPSHOT):

java.lang.NoClassDefFoundError: javax/servlet/ServletContext
	at java.lang.Class.getDeclaredConstructors0(Native Method)
	at java.lang.Class.privateGetDeclaredConstructors(Class.java:2389)
	at java.lang.Class.getConstructor0(Class.java:2699)
	at java.lang.Class.getDeclaredConstructor(Class.java:1985)
	at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:105)
	at org.springframework.test.context.TestContextManager.retrieveTestExecutionListeners(TestContextManager.java:232)
	at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:120)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTestContextManager(SpringJUnit4ClassRunner.java:120)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.<init>(SpringJUnit4ClassRunner.java:109)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
	at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:31)
	at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:24)
	at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57)
	at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:29)
	at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57)
	at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:24)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.<init>(JUnit4TestReference.java:33)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestClassReference.<init>(JUnit4TestClassReference.java:25)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.createTest(JUnit4TestLoader.java:48)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.loadTests(JUnit4TestLoader.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:452)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.lang.ClassNotFoundException: javax.servlet.ServletContext
	at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
	... 27 more

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 22, 2014

Sam Brannen commented

Addressed as described in the comments for GitHub commit fb12e23 (4.0.1) and GitHub commit c1569d7 (3.2.7):

Handle NoClassDefFoundError consistently for TELs

Prior to this commit, a NoClassDefFoundError caught in
TestContextManager's retrieveTestExecutionListeners() method would be
handled differently for implicit default listeners (i.e., listeners not
declared via @TestExecutionListeners) and listeners explicitly declared
via @TestExecutionListeners. Specifically, a NoClassDefFoundError would
cause a test to fail for an explicitly declared TestExecutionListener
but not for an implicitly declared one.

This commit addresses this issue by:

  • Always swallowing a NoClassDefFoundError for both implicitly and
    explicitly declared TestExecutionListeners.
  • Changing the log level from DEBUG to INFO to make such situations
    more visible to the average end user.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 22, 2014

Sam Brannen commented

Bruce Brouwer, thanks for providing the stack trace!

Please give the change a try with the next snapshot and let us know if you still have issues.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 23, 2014

Bruce Brouwer commented

It fixed my issue. Thanks.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented Jan 24, 2014

Sam Brannen commented

It fixed my issue. Thanks.

Great!

Thanks for the feedback.

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented May 19, 2014

Greg Allen commented

Similar issue trying to run demo.ApplicationTests with an absolutely vanilla spring-boot-starter-parent 1.0.2 project (boot+web+test), when running the test from Eclipse JUnit runner. Java 1.7.

thrown Exception is BeanInstantiationException, but catch block has catch NoClassDefFoundError

in org.springframework.test.context.TestContextManager.retrieveTestExecutionListeners(Class<?>)
after
11:38:08.568 [main] DEBUG o.s.test.context.TestContextManager - @TestExecutionListeners is not present for class [class demo.ApplicationTests]: using defaults.

org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.springframework.test.context.transaction.TransactionalTestExecutionListener]: Constructor threw exception; nested exception is java.lang.NoClassDefFoundError: org/springframework/transaction/annotation/AnnotationTransactionAttributeSource
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:164)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:105)
at org.springframework.test.context.TestContextManager.retrieveTestExecutionListeners(TestContextManager.java:230)
at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:120)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTestContextManager(SpringJUnit4ClassRunner.java:120)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.<init>(SpringJUnit4ClassRunner.java:109)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:29)
at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:21)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:26)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59)
at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:26)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.<init>(JUnit4TestReference.java:33)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestClassReference.<init>(JUnit4TestClassReference.java:25)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.createTest(JUnit4TestLoader.java:48)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.loadTests(JUnit4TestLoader.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:452)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.lang.NoClassDefFoundError: org/springframework/transaction/annotation/AnnotationTransactionAttributeSource
at org.springframework.test.context.transaction.TransactionalTestExecutionListener.<init>(TransactionalTestExecutionListener.java:108)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:148)
... 23 more
Caused by: java.lang.ClassNotFoundException: org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
... 29 more

@spring-projects-issues
Copy link
Collaborator Author

@spring-projects-issues spring-projects-issues commented May 19, 2014

Juergen Hoeller commented

Greg, please turn this into a separate follow-up issue... We'll try to fix this for 4.0.5 and 3.2.9 still.

Juergen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants