如何为 sbt 测试正确加载本机库?
How to properly load a native library for sbt tests?
我有一个 sbt
项目和一个静态加载本机库并包含本机方法的 java
class。它看起来像这样:
public class NativeContainer {
static {
System.load("/path-to-lib");
}
public static native void nativeFunc(int n);
}
我还有一个 Scala
测试调用本机函数是这样的:
class TestJni extends FunSpec {
describe("JNI test") {
NativeContainer.nativeFunc(5);
}
}
当我 运行 通过 sbt
测试一次时,一切正常。然而,在接下来的每个 运行 我得到:
[error] Could not run test intrinsics.TestJni:
java.lang.UnsatisfiedLinkError: Native Library /path-to-lib already
loaded in another classloader
加载库以避免这种情况的正确方法是什么?重新启动 sbt
有效,但我一直在寻找更灵活的解决方案。
我不使用任何库或插件来粘合 sbt
和 JNI
。
这是完整的堆栈跟踪:
[debug] Running TaskDef(TestJni, org.scalatest.tools.Framework$$anon@1d29c60d, false, [SuiteSelector]) java.lang.UnsatisfiedLinkError: Native Library /path-to-lib/libNativeContainer.dylib already loaded in another classloader
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1907)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1824)
at java.lang.Runtime.load0(Runtime.java:809)
at java.lang.System.load(System.java:1086)
at NativeContainer.<clinit>(NativeContainer.java:5)
at TestJni$$anonfun.apply$mcV$sp(TestJni.scala:16)
at org.scalatest.SuperEngine.registerNestedBranch(Engine.scala:613)
at org.scalatest.FunSpecLike$class.describe(FunSpecLike.scala:357)
at org.scalatest.FunSpec.describe(FunSpec.scala:1626)
at TestJni.<init>(TestJni.scala:7)
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 org.scalatest.tools.Framework$ScalaTestTask.execute(Framework.scala:646)
at sbt.TestRunner.runTest(TestFramework.scala:76)
at sbt.TestRunner.run(TestFramework.scala:85)
at sbt.TestFramework$$anon$$anonfun$$init$$$anonfun$apply.apply(TestFramework.scala:197)
at sbt.TestFramework$$anon$$anonfun$$init$$$anonfun$apply.apply(TestFramework.scala:197)
at sbt.TestFramework$.sbt$TestFramework$$withContextLoader(TestFramework.scala:185)
at sbt.TestFramework$$anon$$anonfun$$init$.apply(TestFramework.scala:197)
at sbt.TestFramework$$anon$$anonfun$$init$.apply(TestFramework.scala:197)
at sbt.TestFunction.apply(TestFramework.scala:202)
at sbt.Tests$.sbt$Tests$$processRunnable(Tests.scala:239)
at sbt.Tests$$anonfun$makeSerial.apply(Tests.scala:245)
at sbt.Tests$$anonfun$makeSerial.apply(Tests.scala:245)
at sbt.std.Transform$$anon$$anonfun$apply.apply(System.scala:44)
at sbt.std.Transform$$anon$$anonfun$apply.apply(System.scala:44)
at sbt.std.Transform$$anon.work(System.scala:63)
at sbt.Execute$$anonfun$submit$$anonfun$apply.apply(Execute.scala:226)
at sbt.Execute$$anonfun$submit$$anonfun$apply.apply(Execute.scala:226)
at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:17)
at sbt.Execute.work(Execute.scala:235)
at sbt.Execute$$anonfun$submit.apply(Execute.scala:226)
at sbt.Execute$$anonfun$submit.apply(Execute.scala:226)
at sbt.ConcurrentRestrictions$$anon$$anonfun.apply(ConcurrentRestrictions.scala:159)
at sbt.CompletionService$$anon.call(CompletionService.scala:28)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745) [error] Could not run test TestJni: java.lang.UnsatisfiedLinkError: Native Library /path-to-lib/libNativeContainer.dylib already loaded in another classloader
我猜看起来问题是,默认情况下,sbt 运行s 每轮测试都在同一个 JVM 但不同的 ClassLoader 中进行,但是 JNI 库只能是 linked 一次,而不是在多个 ClassLoader 中多次。
sbt 有一个设置...
fork := true
...这导致测试 运行 在新的 JVM 中而不是在新的 ClassLoader 下的 sbt 的 JVM 中。 (参见 the docs。)在此设置下,您的 类 每个 JVM 将只加载一次(因为它们可能在生产场景中),而不会非法尝试将 link JNI 库乘以不同的类加载器。这应该可以解决您的问题。
我有一个 sbt
项目和一个静态加载本机库并包含本机方法的 java
class。它看起来像这样:
public class NativeContainer {
static {
System.load("/path-to-lib");
}
public static native void nativeFunc(int n);
}
我还有一个 Scala
测试调用本机函数是这样的:
class TestJni extends FunSpec {
describe("JNI test") {
NativeContainer.nativeFunc(5);
}
}
当我 运行 通过 sbt
测试一次时,一切正常。然而,在接下来的每个 运行 我得到:
[error] Could not run test intrinsics.TestJni: java.lang.UnsatisfiedLinkError: Native Library /path-to-lib already loaded in another classloader
加载库以避免这种情况的正确方法是什么?重新启动 sbt
有效,但我一直在寻找更灵活的解决方案。
我不使用任何库或插件来粘合 sbt
和 JNI
。
这是完整的堆栈跟踪:
[debug] Running TaskDef(TestJni, org.scalatest.tools.Framework$$anon@1d29c60d, false, [SuiteSelector]) java.lang.UnsatisfiedLinkError: Native Library /path-to-lib/libNativeContainer.dylib already loaded in another classloader
at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1907)
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1824)
at java.lang.Runtime.load0(Runtime.java:809)
at java.lang.System.load(System.java:1086)
at NativeContainer.<clinit>(NativeContainer.java:5)
at TestJni$$anonfun.apply$mcV$sp(TestJni.scala:16)
at org.scalatest.SuperEngine.registerNestedBranch(Engine.scala:613)
at org.scalatest.FunSpecLike$class.describe(FunSpecLike.scala:357)
at org.scalatest.FunSpec.describe(FunSpec.scala:1626)
at TestJni.<init>(TestJni.scala:7)
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 org.scalatest.tools.Framework$ScalaTestTask.execute(Framework.scala:646)
at sbt.TestRunner.runTest(TestFramework.scala:76)
at sbt.TestRunner.run(TestFramework.scala:85)
at sbt.TestFramework$$anon$$anonfun$$init$$$anonfun$apply.apply(TestFramework.scala:197)
at sbt.TestFramework$$anon$$anonfun$$init$$$anonfun$apply.apply(TestFramework.scala:197)
at sbt.TestFramework$.sbt$TestFramework$$withContextLoader(TestFramework.scala:185)
at sbt.TestFramework$$anon$$anonfun$$init$.apply(TestFramework.scala:197)
at sbt.TestFramework$$anon$$anonfun$$init$.apply(TestFramework.scala:197)
at sbt.TestFunction.apply(TestFramework.scala:202)
at sbt.Tests$.sbt$Tests$$processRunnable(Tests.scala:239)
at sbt.Tests$$anonfun$makeSerial.apply(Tests.scala:245)
at sbt.Tests$$anonfun$makeSerial.apply(Tests.scala:245)
at sbt.std.Transform$$anon$$anonfun$apply.apply(System.scala:44)
at sbt.std.Transform$$anon$$anonfun$apply.apply(System.scala:44)
at sbt.std.Transform$$anon.work(System.scala:63)
at sbt.Execute$$anonfun$submit$$anonfun$apply.apply(Execute.scala:226)
at sbt.Execute$$anonfun$submit$$anonfun$apply.apply(Execute.scala:226)
at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:17)
at sbt.Execute.work(Execute.scala:235)
at sbt.Execute$$anonfun$submit.apply(Execute.scala:226)
at sbt.Execute$$anonfun$submit.apply(Execute.scala:226)
at sbt.ConcurrentRestrictions$$anon$$anonfun.apply(ConcurrentRestrictions.scala:159)
at sbt.CompletionService$$anon.call(CompletionService.scala:28)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745) [error] Could not run test TestJni: java.lang.UnsatisfiedLinkError: Native Library /path-to-lib/libNativeContainer.dylib already loaded in another classloader
我猜看起来问题是,默认情况下,sbt 运行s 每轮测试都在同一个 JVM 但不同的 ClassLoader 中进行,但是 JNI 库只能是 linked 一次,而不是在多个 ClassLoader 中多次。
sbt 有一个设置...
fork := true
...这导致测试 运行 在新的 JVM 中而不是在新的 ClassLoader 下的 sbt 的 JVM 中。 (参见 the docs。)在此设置下,您的 类 每个 JVM 将只加载一次(因为它们可能在生产场景中),而不会非法尝试将 link JNI 库乘以不同的类加载器。这应该可以解决您的问题。