We have been adding Unit tests to our robot software, mostly following the examples in WPILIB and the unit testing docs. Everything was fine, but today we added a test for checking a command, and it spins up an entire robot in simulation. When we run the test in VSCode as part of the build, it works perfectly. However, we also use Githib Actions to run a build whenever we push code to our main branch, and with the new test the action times out. We see the output from the new test as passed, and even from older tests that run after it, but when the whole test task should end and the action should move on to the next task, it just hangs until the timeout.
I added some code at the end of the “AfterEach” section to dump all of the threads running, and in addition to the Junit threads, it showed the robot threads still running, such as “robot main”, “thread 0”, “DataLogDS”, “NTListener”, and “MotorSafety”. My thought is that the action is waiting for these threads to exit, but that is just a guess. Is there something else that we should be doing at the end to quite out of it? The AfterEach already does a robot.endCompetition, robot.close, an interrupt call on the competition thread as well as all the other things in the example. Any ideas as to what I am missing?
By the way, the example unit tests in WPILIB show calls to HAL.initialize, but it looks to me like the WPILIB code does that already. Is the call really necessary?
Yes, your unit test code must call HAL.initialize() before calling any HAL simulation functions. The WPILib code that calls HAL.initialize() is only called in the top level RobotBase class. You should be writing unit tests that do not instantiate the entire robot, but rather specific pieces of testable code, as RobotBase (e.g. TimedRobot) is not designed to ever exit.
Given that we shouldn’t spin up a whole robot for unit testing, and the recommended way to create command now is to use composition and lambdas, etc., what would be the recommended way to test those commands? Is there a simulated scheduler or a way to use the scheduler without the rest of the robot?