Unit Testing: java.io.IOException: wpiHaljni

I’m attempting to write some unit tests, which I can do for single commands with a Mock subsystem. However, I’d also like to run some via the top level (ie command based template) robot (I can live without simulating subsystems). This seems to have extensive dependencies on JNI code which fails with the following errors:

java.io.IOException: ntcorejni could not be loaded from path or an embedded resource.
attempted to load for platform /windows/x86-64/

or

java.io.IOException: wpiHaljni could not be loaded from path or an embedded resource.
attempted to load for platform /windows/x86-64/

I see that the WIPILib has an out of process simulator which can be executed via

.\gradlew simulateJava

However running tests after this doesn’t seem to fix the issue. Given there is a Sim option I’m hoping its possible to leverage that for unit testing purposes in some way.

So the question is has anyone worked out how to do this, or can point me in the direction of any docs that explain how to get this working? I’m open to any alternatives that will make unit testing easier.

Thanks

Are you attempting to run from vscodes or intellijs test runner? Neither of those work correctly, as they don’t have an easy way to set up jni dependencies. The gradle test task does already work correctly however, but can only run all tests at one.

We are using VSCode.

The gradle test task does already work correctly however, but can only run all tests at one.

I’m not sure what you mean by this. I’m able to get the test runner in VSCode working fine for ‘standard’ tests. Are you suggesting that the gradle test task will Sim out the JNI dependencies? Ie if I write a test that calls robot and run it via the test task it will work?

Yes. The test task properly configures the jni dependencies.

Thanks I’ve verified this does work. Gets me a little closer.

If we test outside the robot class. How far can we get with the other classes ie can we test using the Command Scheduler? I’d like at avoid mocking out too much of the WPILib code. If there are any docs on how teams can approach unit testing it would be much appreciated thanks.

There’s currently no documentation on this, but you can find examples of unit tests for the command-based framework here (these are the unit tests for the framework itself). The HAL layer has built-in mocking for hardware interfaces exposed via the edu.wpi.first.hal.sim package.

Thanks for the prompt replies everyone - I’ll check them out…

Ok so I tried to write a test that utilizes the scheduler, which is something I would have hoped was pure java. However, like most of the code in WPILIb it has a hard dependency on the static HAL class. So I’d like to know how you are able to run a test using the scheduler via the IDE. It works fine when running from the command line, but that’s a terrible experience.

I’m looking into how I can mock out the statics (ie PowerMock - but shouldn’t have to) but would prefer other options if available. I see there are SIMs but I don’t see them being used in the tests cited other than the DS sim, but not sure how that’s related. So basically I need to know if there a way to make use of the scheduler is a unit test that can be run from the supported IDEs?

Thanks

From VS Code, you can configure the test runner to point to the JNI paths, but its not portable across systems, and saves to a file that does get committed to CI. So, if sharing between systems you would have to manually change this every time.

This shows how you can configure things, and what you need to do is set jvm args for java.library.path to point to the jniExtractDir in the build folder, and then set the working directory to that folder as well.

This is the only way to do it from VS Code, but has not been tested and is not supported by us. There is no supported way to get test debugging to work, just the test command in vscode, which just runs gradle.

Thanks for the prompt reply and I’m sure that makes a lot of sense to a seasoned Java dev, which Im not. I get I have to add the java.test.config to my setting.json file. However, I’m not familiar enough with Java to know the correct args to specify. Are you able to post an example config to get this working?

I can try to do so this weekend. I’ve never actually done it, so I’m just assuming it will work.

Thanks I appreciate it. In the meantime I’ll explore if the PowerMock option will work.

I have a quick, ugly alternative; just put the necessary native files in your root directory. Here, you can see that we’ve placed ntcorejni.dll and wpiHalji.dll in there, and that allows the libraries to be loaded: https://github.com/Team488/SeriouslyCommonLib

  • Pros:
    • Quick
    • Works
  • Cons:
    • Checking in libraries isn’t really best practice
    • If people are running tests on multiple platforms, they have to replace those files locally, which is annoying at best.

So, that’s what we’re doing until we figure out a more graceful alternative.

It would be great if unit testing of Robot were enabled out of the box / sample project, so that every student can get the benefit without arduous CD research.

Not this year, as it’s too late. It should not just be for teams with savvy mentors.

This is not an official recommendation. I’ve never touched gradleRIO or the vendor library setup so it’s quite possible I got that wrong. However, I’ve started using this in FRC code and it’s mostly stable with an occasional JVM crash in native code. Vendor libraries for desktop are obviously up to the vendors to provide.


To enable ./gradlew test , add this to your build.gradle file:

List<String> wpilibDesktopJni(String platform) {
    return ["edu.wpi.first.thirdparty.frc2020.opencv:opencv-jni:${wpi.opencvVersion}:${platform}@jar".toString(),
            "edu.wpi.first.hal:hal-jni:${wpi.wpilibVersion}:${platform}@jar".toString(),
            "edu.wpi.first.wpiutil:wpiutil-jni:${wpi.wpilibVersion}:${platform}@jar".toString(),
            "edu.wpi.first.ntcore:ntcore-jni:${wpi.wpilibVersion}:${platform}@jar".toString(),
            "edu.wpi.first.cscore:cscore-jni:${wpi.wpilibVersion}:${platform}@jar".toString()]
}

Then add this to the dependencies block:

testImplementation(wpilibDesktopJni(wpi.platforms.desktop))
testImplementation(wpi.deps.vendor.jni(wpi.platforms.desktop))

You’ll get desktop builds for the HAL, ntcore, cscore, and their dependents. No need to check in compiled libraries

1 Like

Note that vendor libraries are likely to break if this method is performed, which is the cause of that JVM crash in native code. They depend on having the cpp dependencies all in one location, which is the jniExtractDir in the build/tmp directory. That needs to be the working directory for unit tests in order for all vendor simulation dependencies to work correctly.

Also, ./gradlew test already works by default in gradlerio, and is already enabled to load all JNI libraries. It only fails when you try and run tests from the IDE, because that doesn’t actually use the test task and instead runs its own configuration.

It is enabled by default, just only when running the test task from gradle directly. If this isn’t working for you, please report this, as that is not the expected behavior. It is expected to work.

@RobBlair Couldn’t agree more! I’ve had to do a lot of work to get it ‘some what’ testable. I’m now using the technique @JohnGilb suggested (kudos for the suggestion btw!), which is working well :grinning:. I think we only have one Mac user, so its not a big issue. Though it could be a case of writing a small batch script that copies the platform specific files for a given user. That might be the quickest workaround. Alternatively extend the build pipeline to copy the relevant files from the jniExtractDir to a usable place. That removes the platform issue. Having said that if the suggestion @Thad_House had about adding the java.test.config works, then that might be the best option.

@Thad_House - while it may work from the command line aka Gradle, this really is a terrible dev experience. I know this can’t be improved for this season (unless the java.test.config works), but there really should be some more thought given to making this library more testable. Hardly any teams are unit testing their code, and given the experience I’ve had I can see why.

@RobBlair and @dotnetprofessional I think they might have added the capability to make unit tests runnable by default now. It looks like they added the feature I needed. Can you try adding the following to settings.json in the .vscode folder, and then try to run a test. Its working for me, at least on windows, but should work on other platforms too.

  "java.test.config": [
    {
        "name": "WPIlibUnitTests",
        "workingDirectory": "${workspaceFolder}/build/tmp/jniExtractDir",
        "vmargs": [ "-Djava.library.path=${workspaceFolder}/build/tmp/jniExtractDir" ],
        "env": { "LD_LIBRARY_PATH": "${workspaceFolder}/build/tmp/jniExtractDir" ,
          "DYLD_LIBRARY_PATH": "${workspaceFolder}/build/tmp/jniExtractDir" }
        
    },
  ],
  "java.test.defaultConfig": "WPIlibUnitTests"

Note this will require running a build once before this will work. I don’t have a great way around that, but that is simple enough to do.

4 Likes

@Thad_House great job! Yep that works great! Thanks for the effort. This should be enough to satisfy my testing needs.