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

NullPointerException when attempting to debug with JUnit5 on Windows 10 with IntelliJ #540

Closed
m-kuklinski opened this issue Aug 6, 2018 · 8 comments
Labels

Comments

@m-kuklinski
Copy link

Version: JMockit 1.28, JUnit5 5.20, IntelliJ 2018.2, tested JDK 1.8.0.181, 10.0.2, and 11.

When attempting to debug a unit test of a Maven project in IntelliJ on Windows, I get a NullPointerException in createSyntheticFieldsInJREClassToHoldClassLoadingBridges. The call stack is as follows:

java.lang.NullPointerException at mockit.internal.startup.ClassLoadingBridgeFields.createSyntheticFieldsInJREClassToHoldClassLoadingBridges(ClassLoadingBridgeFields.java:27) at mockit.internal.startup.Startup.initializeIfPossible(Startup.java:126) at mockit.integration.junit5.JMockitTestEngine.<init>(JMockitTestEngine.java:26) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at java.lang.Class.newInstance(Class.java:442) at java.util.ServiceLoader$LazyIterator.nextService(ServiceLoader.java:380) at java.util.ServiceLoader$LazyIterator.next(ServiceLoader.java:404) at java.util.ServiceLoader$1.next(ServiceLoader.java:480) at org.junit.platform.launcher.core.DefaultLauncher.validateUniqueIds(DefaultLauncher.java:65) at org.junit.platform.launcher.core.DefaultLauncher.<init>(DefaultLauncher.java:60) at org.junit.platform.launcher.core.LauncherFactory.create(LauncherFactory.java:57) at com.intellij.junit5.JUnit5IdeaTestRunner.createListeners(JUnit5IdeaTestRunner.java:42) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:45) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.CommandLineWrapper.main(CommandLineWrapper.java:67)

Upon further investigation, this is happening as IntelliJ does not pass any runtime information, which causes JMockit to execute the agent, which updates the variable instrumentation, and then continues execution with it upon the return of the agent. On my system, the splitting of the VM during this process appears to create a detached ClassLoader, and thus creates a separate class altogether, complete with its own copy of the static instrumentation variable. I confirmed this via debugging. Thus, when the instrumentation variable is updated, it is not updated in the original context, which is under a separate class altogether (although with the exact same prototype/name, but a different ID in the VM and a different memory space).

This does not happen:

  1. When running the unit tests under Maven. I presume SureFire et al are handling passing the agent to it.
  2. On OSX. This procedure seems to run fine on OSX.

I have forked the project on github, and patched it in a way that uses reflection to update all potential copies of instrumentation to work around this issue, which appears to work on my system.

@rliesenfeld
Copy link
Member

rliesenfeld commented Aug 7, 2018

What I need to know is: how to reproduce the problem? Also, JMockit 1.28 is very old; does it still occur with the current version (1.41)?

@jmockit jmockit deleted a comment from m-kuklinski Aug 7, 2018
@m-kuklinski
Copy link
Author

It also happens on trunk from the git repository, which was what I developed my patch off of.

To reproduce it? Windows 10, JDK8, IntelliJ... and try to debug a JUnit5 unit test. I'd have to set up a minimal case for you otherwise. I'll work on that in a bit.

@rliesenfeld
Copy link
Member

It's working fine for me (specifically, with https://github.com/jmockit/jmockit1/blob/master/samples/java8testing/test/java8testing/JUnit5Test.java, and putting a breakpoint at line 14 of https://github.com/jmockit/jmockit1/blob/master/samples/java8testing/src/java8testing/BusinessService.java. Using IntelliJ 2018.1 and JDK 1.8.0_144 on Windows 7 (but I doubt that IntelliJ, Windows, or even the JDK has anything to do with it).

@m-kuklinski
Copy link
Author

m-kuklinski commented Aug 27, 2018

I am able to reproduce it with your JUnit5Test if I change the debugger to use classpath files instead of the default 'none' (which we cannot use on Windows due to our class paths being insanely long).

I'd suggest adding unit tests for the differing 'Shorten command line' options.

JUnit5Test$InnerTest.innerTest

none: works
JAR manifest: works
classpath file: fails due to not initializing JMockit (due to no agent being loaded).

The output for classpath file in that situation looks like this:

"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" -agentlib:jdwp=transport=dt_shmem,address=javadebug,suspend=y,server=n -ea -javaagent:C:\Users\kuklinsk\.m2\repository/org/jmockit/jmockit/1.42/jmockit-1.42.jar -Didea.test.cyclic.buffer.size=1048576 -javaagent:C:\Users\kuklinsk\.IdeaIC2018.2\system\captureAgent\debugger-agent.jar=file:/C:/Users/kuklinsk/AppData/Local/Temp/capture.props -Dfile.encoding=UTF-8 -classpath "C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2018.1.6\lib\idea_rt.jar" com.intellij.rt.execution.CommandLineWrapper C:\Users\kuklinsk\AppData\Local\Temp\idea_classpath1632644453 com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit5 java8testing.JUnit5Test$InnerTest,innerTest
Connected to the target VM, address: 'javadebug', transport: 'shared memory'
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.intellij.rt.execution.CommandLineWrapper.main(CommandLineWrapper.java:67)
Caused by: java.util.ServiceConfigurationError: org.junit.platform.engine.TestEngine: Provider mockit.integration.junit5.JMockitTestEngine could not be instantiated
	at java.util.ServiceLoader.fail(ServiceLoader.java:232)
	at java.util.ServiceLoader.access$100(ServiceLoader.java:185)
	at java.util.ServiceLoader$LazyIterator.nextService(ServiceLoader.java:384)
	at java.util.ServiceLoader$LazyIterator.next(ServiceLoader.java:404)
	at java.util.ServiceLoader$1.next(ServiceLoader.java:480)
	at org.junit.platform.launcher.core.DefaultLauncher.validateUniqueIds(DefaultLauncher.java:63)
	at org.junit.platform.launcher.core.DefaultLauncher.<init>(DefaultLauncher.java:58)
	at org.junit.platform.launcher.core.LauncherFactory.create(LauncherFactory.java:59)
	at com.intellij.junit5.JUnit5IdeaTestRunner.createListeners(JUnit5IdeaTestRunner.java:42)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:45)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
	... 5 more
Caused by: java.lang.IllegalStateException: JMockit didn't get initialized; please check the -javaagent JVM initialization parameter was used
	at mockit.internal.startup.Startup.verifyInitialization(Startup.java:91)
	at mockit.integration.junit5.JMockitTestEngine.<init>(JMockitTestEngine.java:26)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at java.lang.Class.newInstance(Class.java:442)
	at java.util.ServiceLoader$LazyIterator.nextService(ServiceLoader.java:380)
	... 14 more
Disconnected from the target VM, address: 'javadebug', transport: 'shared memory'

Process finished with exit code 1
Empty test suite.

The output for using a JAR manifest looks like:

"C:\Program Files\Java\jdk1.8.0_181\bin\java.exe" -agentlib:jdwp=transport=dt_shmem,address=javadebug,suspend=y,server=n -ea -javaagent:C:\Users\kuklinsk\.m2\repository/org/jmockit/jmockit/1.42/jmockit-1.42.jar -Didea.test.cyclic.buffer.size=1048576 -javaagent:C:\Users\kuklinsk\.IdeaIC2018.2\system\captureAgent\debugger-agent.jar=file:/C:/Users/kuklinsk/AppData/Local/Temp/capture.props -Dfile.encoding=UTF-8 -classpath C:\Users\kuklinsk\AppData\Local\Temp\classpath1088636867.jar com.intellij.rt.execution.junit.JUnitStarter -ideVersion5 -junit5 java8testing.JUnit5Test$InnerTest,innerTest
Connected to the target VM, address: 'javadebug', transport: 'shared memory'
Disconnected from the target VM, address: 'javadebug', transport: 'shared memory'

Process finished with exit code 0

@rliesenfeld rliesenfeld reopened this Aug 30, 2018
@rliesenfeld
Copy link
Member

Yeah, I could reproduce it now. It only happens with one of four different options for the "Shorten command line" field in IntelliJ's run configuration, the "classpath file" one. And it occurs with any test runner (JUnit 4 or 5, and I imagine TestNG as well). Nothing to do with the debugger, or the JDK version, or the OS.

The problem occurs because IDEA launches the test run in two steps, first using the regular app classloader, with jmockit.jar present, but junit.jar absent. And then it starts actually running the tests under a custom classloader, having the full classpath. So, in the first step, JMockit cannot activate its JUnit integration since JUnit is not in the runtime classpath; and in the second step, it can't find the Instrumentation object loaded in the first step, since now everything is loaded on a different classloader (therefore, the static field value got lost).

However, I think the simplest solution is to not use the "classpath file" option. The other three options should work (they work for me on a large project where the runtime classpath is over 15000 characters long, on Windows 10).

@rliesenfeld
Copy link
Member

@baudev
Copy link

baudev commented Dec 5, 2018

I'm facing the same issue with the version v1.44. So I downgraded to v1.41 and it's fully working now...

My config : JUnit5 5.10, IntelliJ 2018.2, JDK 1.8.0.131

@trauchhaupt
Copy link

The same problem with detached classloaders exist when using jmockit with arquillian based tests.
The classloader of the tests is detached from the systemclassloader. Only the class mockit.internal.startup.Startup of the Systemclassloader was instrumented (instrumentation is not null) . All other AppClassloaders have no instrumentation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

4 participants