Sbt似乎正在使用不同的类加载器,在sbt会话中多次运行时会使某些测试失败,并出现以下错误:
[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使用模式匹配而不是asInstanceOf
,我得到相同的结果。
如何确保sbt在同一会话中为所有测试执行使用相同的类加载器?
答案 0 :(得分:1)
我认为这与此有关:Do security providers cause ClassLoader leaks in Java?。基本上Security
正在重复使用旧类加载器中的提供程序。所以这可能发生在任何多类路径环境(如OSGi)中,而不仅仅是SBT。
修复您的build.sbt
(无分叉):
testOptions in Test += Tests.Cleanup(() =>
java.security.Security.removeProvider("BC"))
实验:
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
说明:
正如我从您的代码(已发布的here)中看到的那样:
Security.addProvider(new BouncyCastleProvider)
您每次运行测试时都会重复使用相同的BouncyCastleProvider
提供商,因为Security.addProvider
有效only first time。由于sbt为每次“测试”运行创建了新的类加载器,但是重新使用相同的JVM - Security
是由JVM-bootstrap加载的JVM范围的单例,因此classOf[java.security.Security].getClassLoader() == null
并且sbt无法重新加载/重新初始化此类。
你可以轻松检查
classOf[org.bouncycastle.jce.spec.ECParameterSpec].getClassLoader()
res30: ClassLoader = URLClassLoader with NativeCopyLoader with RawResources
org.bouncycastle
个类加载了自定义类加载器(来自sbt),每次运行时都会更改test
。
所以这段代码:
val generator = KeyPairGenerator.getInstance("ECDSA", "BC")
获取从旧类加载器加载的类的实例(用于第一次“测试”运行的类)并且您尝试使用新类加载器的规范初始化它:
generator.initialize(ecSpec)
这就是为什么你得到“参数对象而不是ECParameterSpec”的例外。 “net.i2p.crypto.eddsa.EdDSAPublicKey无法转换为net.i2p.crypto.eddsa.EdDSAPublicKey”的推理基本相同。