Reputation: 15559
Sbt seems to be using different classloaders, making some tests failing when run more than once in an sbt session, with the following error:
[info] java.lang.ClassCastException: net.i2p.crypto.eddsa.EdDSAPublicKey cannot be cast to net.i2p.crypto.eddsa.EdDSAPublicKey
[info] at com.advancedtelematic.libtuf.crypt.EdcKeyPair$.generate(RsaKeyPair.scala:120)
I tried equivalent code using pattern matching instead of asInstanceOf
and I get the same result.
How can I make sure sbt uses the same class loader for all test executions in the same session?
Upvotes: 1
Views: 754
Reputation: 22374
I think it's related to this: Do security providers cause ClassLoader leaks in Java?. Basically Security
is re-using providers from old class-loaders. So this could happen in any multi-classpath environment (like OSGi), not just SBT.
Fix for your build.sbt
(without forking):
testOptions in Test += Tests.Cleanup(() =>
java.security.Security.removeProvider("BC"))
Experiment:
sbt-classloader-issue$ sbt
> test
[success] Total time: 1 s, completed Jul 6, 2017 11:43:53 PM
> test
[success] Total time: 0 s, completed Jul 6, 2017 11:43:55 PM
Explanation:
As I can see from your code (published here):
Security.addProvider(new BouncyCastleProvider)
you're reusing the same BouncyCastleProvider
provider every-time you run a test, as your Security.addProvider
works only first time. As sbt creates new class-loader for every "test" run, but re-uses the same JVM - Security
is kind-of JVM-scoped singleton as it was loaded by JVM-bootstrap, so classOf[java.security.Security].getClassLoader() == null
and sbt cannot reload/reinitialize this class.
And you can easily check that
classOf[org.bouncycastle.jce.spec.ECParameterSpec].getClassLoader()
res30: ClassLoader = URLClassLoader with NativeCopyLoader with RawResources
org.bouncycastle
classes are loaded with custom classloader (from sbt) which changes every-time you run test
.
So this code:
val generator = KeyPairGenerator.getInstance("ECDSA", "BC")
gets instance of class loaded from old classloader (the one used for first "test" run) and you're trying to initialize it with spec from new classloader:
generator.initialize(ecSpec)
That's why you're getting "parameter object not a ECParameterSpec" exception. The reasoning around "net.i2p.crypto.eddsa.EdDSAPublicKey cannot be cast to net.i2p.crypto.eddsa.EdDSAPublicKey" is basically same.
Upvotes: 1