From 912938b434be7a507efbd72bfd4c4a9e45fc34d5 Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Sun, 21 Jun 2026 19:24:30 -0700 Subject: [PATCH] [tools] Translate unit tests to catch2 (#9006) --- tools/sysid/BUILD.bazel | 2 +- tools/sysid/CMakeLists.txt | 4 +- tools/sysid/build.gradle | 5 +- tools/sysid/src/test/native/cpp/Main.cpp | 6 +- .../native/cpp/analysis/AnalysisTypeTest.cpp | 12 +-- .../cpp/analysis/FeedbackAnalysisTest.cpp | 75 ++++++++-------- .../cpp/analysis/FeedforwardAnalysisTest.cpp | 46 ++++++---- .../test/native/cpp/analysis/FilterTest.cpp | 33 +++---- .../src/test/native/cpp/analysis/OLSTest.cpp | 64 +++++++++++--- .../cpp/analysis/TrackwidthAnalysisTest.cpp | 7 +- tools/wpical/BUILD.bazel | 2 +- tools/wpical/CMakeLists.txt | 3 +- tools/wpical/build.gradle | 7 +- tools/wpical/src/test/native/cpp/main.cpp | 5 +- .../src/test/native/cpp/test_calibrate.cpp | 86 +++++++++---------- .../test/native/cpp/test_result_is_exact.cpp | 19 ++-- 16 files changed, 217 insertions(+), 159 deletions(-) diff --git a/tools/sysid/BUILD.bazel b/tools/sysid/BUILD.bazel index 27f682ee9a..ff203c9120 100644 --- a/tools/sysid/BUILD.bazel +++ b/tools/sysid/BUILD.bazel @@ -53,7 +53,7 @@ cc_test( ], deps = [ ":sysid-lib", - "//thirdparty/googletest", + "//thirdparty/catch2", ], ) diff --git a/tools/sysid/CMakeLists.txt b/tools/sysid/CMakeLists.txt index 389f8d0614..10f807174b 100644 --- a/tools/sysid/CMakeLists.txt +++ b/tools/sysid/CMakeLists.txt @@ -34,7 +34,7 @@ elseif(APPLE) endif() if(WITH_TESTS) - wpilib_add_test(sysid src/test/native/cpp) + wpilib_add_test_catch2(sysid src/test/native/cpp) wpilib_link_macos_gui(sysid_test) target_sources(sysid_test PRIVATE ${sysid_src}) target_compile_definitions(sysid_test PRIVATE RUNNING_SYSID_TESTS) @@ -42,5 +42,5 @@ if(WITH_TESTS) target_compile_options(sysid_test PRIVATE /utf-8) endif() target_include_directories(sysid_test PRIVATE src/main/native/cpp src/main/native/include) - target_link_libraries(sysid_test wpimath libglass datalog googletest) + target_link_libraries(sysid_test wpimath libglass datalog) endif() diff --git a/tools/sysid/build.gradle b/tools/sysid/build.gradle index a8d3ec9887..126039b7fb 100644 --- a/tools/sysid/build.gradle +++ b/tools/sysid/build.gradle @@ -17,6 +17,7 @@ if (OperatingSystem.current().isWindows()) { ext { nativeName = 'sysid' + nativeTestSuiteName = "${nativeName}Catch2Test" } apply from: "${rootDir}/shared/resources.gradle" @@ -120,7 +121,7 @@ model { } } testSuites { - "${nativeName}Test"(GoogleTestTestSuiteSpec) { + "${nativeTestSuiteName}"(GoogleTestTestSuiteSpec) { for (NativeComponentSpec c : $.components) { if (c.name == nativeName) { testing c @@ -157,7 +158,7 @@ model { } } - lib project: ':thirdparty:googletest', library: 'googletest', linkage: 'static' + lib project: ':thirdparty:catch2', library: 'catch2', linkage: 'static' it.cppCompiler.define("RUNNING_SYSID_TESTS") } } diff --git a/tools/sysid/src/test/native/cpp/Main.cpp b/tools/sysid/src/test/native/cpp/Main.cpp index e993c1f14e..a2c05f2d23 100644 --- a/tools/sysid/src/test/native/cpp/Main.cpp +++ b/tools/sysid/src/test/native/cpp/Main.cpp @@ -2,10 +2,8 @@ // Open Source Software; you can modify and/or share it under the terms of // the WPILib BSD license file in the root directory of this project. -#include +#include int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - int ret = RUN_ALL_TESTS(); - return ret; + return Catch::Session().run(argc, argv); } diff --git a/tools/sysid/src/test/native/cpp/analysis/AnalysisTypeTest.cpp b/tools/sysid/src/test/native/cpp/analysis/AnalysisTypeTest.cpp index 5100cd1137..8bb30cc391 100644 --- a/tools/sysid/src/test/native/cpp/analysis/AnalysisTypeTest.cpp +++ b/tools/sysid/src/test/native/cpp/analysis/AnalysisTypeTest.cpp @@ -4,11 +4,11 @@ #include "wpi/sysid/analysis/AnalysisType.hpp" -#include +#include -TEST(AnalysisTypeTest, FromName) { - EXPECT_EQ(sysid::analysis::kElevator, sysid::analysis::FromName("Elevator")); - EXPECT_EQ(sysid::analysis::kArm, sysid::analysis::FromName("Arm")); - EXPECT_EQ(sysid::analysis::kSimple, sysid::analysis::FromName("Simple")); - EXPECT_EQ(sysid::analysis::kSimple, sysid::analysis::FromName("Random")); +TEST_CASE("AnalysisTypeTest FromName", "[sysid]") { + CHECK(sysid::analysis::kElevator == sysid::analysis::FromName("Elevator")); + CHECK(sysid::analysis::kArm == sysid::analysis::FromName("Arm")); + CHECK(sysid::analysis::kSimple == sysid::analysis::FromName("Simple")); + CHECK(sysid::analysis::kSimple == sysid::analysis::FromName("Random")); } diff --git a/tools/sysid/src/test/native/cpp/analysis/FeedbackAnalysisTest.cpp b/tools/sysid/src/test/native/cpp/analysis/FeedbackAnalysisTest.cpp index d857f949f0..963e5b26fc 100644 --- a/tools/sysid/src/test/native/cpp/analysis/FeedbackAnalysisTest.cpp +++ b/tools/sysid/src/test/native/cpp/analysis/FeedbackAnalysisTest.cpp @@ -4,11 +4,12 @@ #include "wpi/sysid/analysis/FeedbackAnalysis.hpp" -#include +#include +#include #include "wpi/sysid/analysis/FeedbackControllerPreset.hpp" -TEST(FeedbackAnalysisTest, VelocitySystem1) { +TEST_CASE("FeedbackAnalysisTest VelocitySystem1", "[sysid]") { auto Kv = 3.060; auto Ka = 0.327; @@ -17,11 +18,11 @@ TEST(FeedbackAnalysisTest, VelocitySystem1) { auto [Kp, Kd] = sysid::CalculateVelocityFeedbackGains( sysid::presets::kDefault, params, Kv, Ka); - EXPECT_NEAR(Kp, 2.11, 0.05); - EXPECT_NEAR(Kd, 0.00, 0.05); + CHECK(Kp == Catch::Approx(2.11).margin(0.05)); + CHECK(Kd == Catch::Approx(0.00).margin(0.05)); } -TEST(FeedbackAnalysisTest, VelocitySystem2) { +TEST_CASE("FeedbackAnalysisTest VelocitySystem2", "[sysid]") { auto Kv = 0.0693; auto Ka = 0.1170; @@ -30,11 +31,11 @@ TEST(FeedbackAnalysisTest, VelocitySystem2) { auto [Kp, Kd] = sysid::CalculateVelocityFeedbackGains( sysid::presets::kDefault, params, Kv, Ka); - EXPECT_NEAR(Kp, 3.11, 0.05); - EXPECT_NEAR(Kd, 0.00, 0.05); + CHECK(Kp == Catch::Approx(3.11).margin(0.05)); + CHECK(Kd == Catch::Approx(0.00).margin(0.05)); } -TEST(FeedbackAnalysisTest, VelocitySystemWithSmallKa) { +TEST_CASE("FeedbackAnalysisTest VelocitySystemWithSmallKa", "[sysid]") { auto Kv = 3.060; auto Ka = 0.0; @@ -43,11 +44,11 @@ TEST(FeedbackAnalysisTest, VelocitySystemWithSmallKa) { auto [Kp, Kd] = sysid::CalculateVelocityFeedbackGains( sysid::presets::kDefault, params, Kv, Ka); - EXPECT_NEAR(Kp, 0.00, 0.05); - EXPECT_NEAR(Kd, 0.00, 0.05); + CHECK(Kp == Catch::Approx(0.00).margin(0.05)); + CHECK(Kd == Catch::Approx(0.00).margin(0.05)); } -TEST(FeedbackAnalysisTest, VelocityConversion) { +TEST_CASE("FeedbackAnalysisTest VelocityConversion", "[sysid]") { auto Kv = 0.0693; auto Ka = 0.1170; @@ -58,11 +59,11 @@ TEST(FeedbackAnalysisTest, VelocityConversion) { // This should have the same Kp as the test above, but scaled by a factor of 3 // * 1023. - EXPECT_NEAR(Kp, 3.11 / (3 * 1023), 0.005); - EXPECT_NEAR(Kd, 0.00, 0.05); + CHECK(Kp == Catch::Approx(3.11 / (3 * 1023)).margin(0.005)); + CHECK(Kd == Catch::Approx(0.00).margin(0.05)); } -TEST(FeedbackAnalysisTest, VelocityCTRE) { +TEST_CASE("FeedbackAnalysisTest VelocityCTRE", "[sysid]") { auto Kv = 1.97; auto Ka = 0.179; @@ -71,11 +72,11 @@ TEST(FeedbackAnalysisTest, VelocityCTRE) { auto [Kp, Kd] = sysid::CalculateVelocityFeedbackGains(sysid::presets::kCTREv5, params, Kv, Ka); - EXPECT_NEAR(Kp, 259.21276731541178, 0.00005); - EXPECT_NEAR(Kd, 0.00, 0.05); + CHECK(Kp == Catch::Approx(259.21276731541178).margin(0.00005)); + CHECK(Kd == Catch::Approx(0.00).margin(0.05)); } -TEST(FeedbackAnalysisTest, VelocityCTREConversion) { +TEST_CASE("FeedbackAnalysisTest VelocityCTREConversion", "[sysid]") { auto Kv = 1.97; auto Ka = 0.179; @@ -86,11 +87,11 @@ TEST(FeedbackAnalysisTest, VelocityCTREConversion) { // This should have the same Kp as the test above, but scaled by a factor // of 3. - EXPECT_NEAR(Kp, 259.21276731541178 / 3, 0.00005); - EXPECT_NEAR(Kd, 0.00, 0.05); + CHECK(Kp == Catch::Approx(259.21276731541178 / 3).margin(0.00005)); + CHECK(Kd == Catch::Approx(0.00).margin(0.05)); } -TEST(FeedbackAnalysisTest, VelocityREV) { +TEST_CASE("FeedbackAnalysisTest VelocityREV", "[sysid]") { auto Kv = 1.97; auto Ka = 0.179; @@ -99,11 +100,11 @@ TEST(FeedbackAnalysisTest, VelocityREV) { auto [Kp, Kd] = sysid::CalculateVelocityFeedbackGains( sysid::presets::kREVNEOBuiltIn, params, Kv, Ka); - EXPECT_NEAR(Kp, 0.00241, 0.005); - EXPECT_NEAR(Kd, 0.00, 0.05); + CHECK(Kp == Catch::Approx(0.00241).margin(0.005)); + CHECK(Kd == Catch::Approx(0.00).margin(0.05)); } -TEST(FeedbackAnalysisTest, VelocityREVConversion) { +TEST_CASE("FeedbackAnalysisTest VelocityREVConversion", "[sysid]") { auto Kv = 1.97; auto Ka = 0.179; @@ -114,11 +115,11 @@ TEST(FeedbackAnalysisTest, VelocityREVConversion) { // This should have the same Kp as the test above, but scaled by a factor // of 3. - EXPECT_NEAR(Kp, 0.00241 / 3, 0.005); - EXPECT_NEAR(Kd, 0.00, 0.05); + CHECK(Kp == Catch::Approx(0.00241 / 3).margin(0.005)); + CHECK(Kd == Catch::Approx(0.00).margin(0.05)); } -TEST(FeedbackAnalysisTest, Position) { +TEST_CASE("FeedbackAnalysisTest Position", "[sysid]") { auto Kv = 3.060; auto Ka = 0.327; @@ -127,11 +128,11 @@ TEST(FeedbackAnalysisTest, Position) { auto [Kp, Kd] = sysid::CalculatePositionFeedbackGains( sysid::presets::kDefault, params, Kv, Ka); - EXPECT_NEAR(Kp, 6.41, 0.05); - EXPECT_NEAR(Kd, 2.48, 0.05); + CHECK(Kp == Catch::Approx(6.41).margin(0.05)); + CHECK(Kd == Catch::Approx(2.48).margin(0.05)); } -TEST(FeedbackAnalysisTest, PositionWithSmallKa) { +TEST_CASE("FeedbackAnalysisTest PositionWithSmallKa", "[sysid]") { auto Kv = 3.060; auto Ka = 1e-10; @@ -140,11 +141,11 @@ TEST(FeedbackAnalysisTest, PositionWithSmallKa) { auto [Kp, Kd] = sysid::CalculatePositionFeedbackGains( sysid::presets::kDefault, params, Kv, Ka); - EXPECT_NEAR(Kp, 19.97, 0.05); - EXPECT_NEAR(Kd, 0.00, 0.05); + CHECK(Kp == Catch::Approx(19.97).margin(0.05)); + CHECK(Kd == Catch::Approx(0.00).margin(0.05)); } -TEST(FeedbackAnalysisTest, PositionWithLatencyCompensation) { +TEST_CASE("FeedbackAnalysisTest PositionWithLatencyCompensation", "[sysid]") { auto Kv = 3.060; auto Ka = 0.327; @@ -154,11 +155,11 @@ TEST(FeedbackAnalysisTest, PositionWithLatencyCompensation) { preset.measurementDelay = 10_ms; auto [Kp, Kd] = sysid::CalculatePositionFeedbackGains(preset, params, Kv, Ka); - EXPECT_NEAR(Kp, 5.92, 0.05); - EXPECT_NEAR(Kd, 2.12, 0.05); + CHECK(Kp == Catch::Approx(5.92).margin(0.05)); + CHECK(Kd == Catch::Approx(2.12).margin(0.05)); } -TEST(FeedbackAnalysisTest, PositionREV) { +TEST_CASE("FeedbackAnalysisTest PositionREV", "[sysid]") { auto Kv = 3.060; auto Ka = 0.327; @@ -167,6 +168,6 @@ TEST(FeedbackAnalysisTest, PositionREV) { auto [Kp, Kd] = sysid::CalculatePositionFeedbackGains( sysid::presets::kREVNEOBuiltIn, params, Kv, Ka); - EXPECT_NEAR(Kp, 0.30202, 0.05); - EXPECT_NEAR(Kd, 48.518, 0.05); + CHECK(Kp == Catch::Approx(0.30202).margin(0.05)); + CHECK(Kd == Catch::Approx(48.518).margin(0.05)); } diff --git a/tools/sysid/src/test/native/cpp/analysis/FeedforwardAnalysisTest.cpp b/tools/sysid/src/test/native/cpp/analysis/FeedforwardAnalysisTest.cpp index e7d239960e..11ada81b62 100644 --- a/tools/sysid/src/test/native/cpp/analysis/FeedforwardAnalysisTest.cpp +++ b/tools/sysid/src/test/native/cpp/analysis/FeedforwardAnalysisTest.cpp @@ -9,8 +9,12 @@ #include #include #include +#include +#include -#include +#include +#include +#include #include "wpi/sysid/analysis/AnalysisManager.hpp" #include "wpi/sysid/analysis/AnalysisType.hpp" @@ -113,30 +117,35 @@ sysid::Storage CollectData(Model& model, std::bitset<4> movements) { } /** - * Asserts success if the gains contain NaNs or are too far from their expected + * Returns true if the gains contain NaNs or are too far from their expected * values. * * @param expectedGains The expected feedforward gains. * @param actualGains The calculated feedforward gains. * @param tolerances The tolerances for the coefficient comparisons. */ -testing::AssertionResult FitIsBad(std::span expectedGains, - std::span actualGains, - std::span tolerances) { +bool FitIsBad(std::span expectedGains, + std::span actualGains, + std::span tolerances) { // Check for NaN for (const auto& coeff : actualGains) { if (std::isnan(coeff)) { - return testing::AssertionSuccess(); + return true; } } for (size_t i = 0; i < expectedGains.size(); ++i) { if (std::abs(expectedGains[i] - actualGains[i]) >= tolerances[i]) { - return testing::AssertionSuccess(); + return true; } } - auto result = testing::AssertionFailure(); + return false; +} + +std::string DescribeFit(std::span expectedGains, + std::span actualGains) { + std::ostringstream result; result << "\n"; for (size_t i = 0; i < expectedGains.size(); ++i) { @@ -158,7 +167,7 @@ testing::AssertionResult FitIsBad(std::span expectedGains, result << " diff " << std::abs(expectedGains[i] - actualGains[i]) << "\n"; } - return result; + return result.str(); } /** @@ -173,12 +182,13 @@ void ExpectArrayNear(std::span expected, std::span tolerances) { // Check size const size_t size = expected.size(); - EXPECT_EQ(size, actual.size()); - EXPECT_EQ(size, tolerances.size()); + REQUIRE(size == actual.size()); + REQUIRE(size == tolerances.size()); // Check elements for (size_t i = 0; i < size; ++i) { - EXPECT_NEAR(expected[i], actual[i], tolerances[i]) << "where i = " << i; + UNSCOPED_INFO("i = " << i); + CHECK(expected[i] == Catch::Approx(actual[i]).margin(tolerances[i])); } } @@ -205,14 +215,18 @@ void RunTests(Model& model, const sysid::AnalysisType& type, // doesn't match auto ff = sysid::CalculateFeedforwardGains(CollectData(model, movements), type, false); - EXPECT_TRUE(FitIsBad(expectedGains, ff.coeffs, tolerances)); + bool fitIsBad = FitIsBad(expectedGains, ff.coeffs, tolerances); + if (!fitIsBad) { + UNSCOPED_INFO(DescribeFit(expectedGains, ff.coeffs)); + } + CHECK(fitIsBad); } } } } // namespace -TEST(FeedforwardAnalysisTest, Arm) { +TEST_CASE("FeedforwardAnalysisTest Arm", "[sysid]") { { constexpr double Ks = 1.01; constexpr double Kv = 3.060; @@ -242,7 +256,7 @@ TEST(FeedforwardAnalysisTest, Arm) { } } -TEST(FeedforwardAnalysisTest, Elevator) { +TEST_CASE("FeedforwardAnalysisTest Elevator", "[sysid]") { { constexpr double Ks = 1.01; constexpr double Kv = 3.060; @@ -268,7 +282,7 @@ TEST(FeedforwardAnalysisTest, Elevator) { } } -TEST(FeedforwardAnalysisTest, Simple) { +TEST_CASE("FeedforwardAnalysisTest Simple", "[sysid]") { { constexpr double Ks = 1.01; constexpr double Kv = 3.060; diff --git a/tools/sysid/src/test/native/cpp/analysis/FilterTest.cpp b/tools/sysid/src/test/native/cpp/analysis/FilterTest.cpp index ce59564ece..5418ac7fe4 100644 --- a/tools/sysid/src/test/native/cpp/analysis/FilterTest.cpp +++ b/tools/sysid/src/test/native/cpp/analysis/FilterTest.cpp @@ -6,14 +6,15 @@ #include #include -#include +#include +#include #include "wpi/sysid/analysis/AnalysisManager.hpp" #include "wpi/sysid/analysis/FeedforwardAnalysis.hpp" #include "wpi/sysid/analysis/FilteringUtils.hpp" #include "wpi/sysid/analysis/Storage.hpp" -TEST(FilterTest, MedianFilter) { +TEST_CASE("FilterTest MedianFilter", "[sysid]") { std::vector testData{ sysid::PreparedData{0_s, 0, 0, 0}, sysid::PreparedData{0_s, 0, 0, 1}, sysid::PreparedData{0_s, 0, 0, 10}, sysid::PreparedData{0_s, 0, 0, 5}, @@ -28,10 +29,10 @@ TEST(FilterTest, MedianFilter) { sysid::PreparedData{0_s, 0, 0, 6}, sysid::PreparedData{0_s, 0, 0, 5}}; sysid::ApplyMedianFilter(&testData, 3); - EXPECT_EQ(expectedData, testData); + CHECK(expectedData == testData); } -TEST(FilterTest, NoiseFloor) { +TEST_CASE("FilterTest NoiseFloor", "[sysid]") { std::vector testData = { {0_s, 1, 2, 3, 5_ms, 0, 0}, {1_s, 1, 2, 3, 5_ms, 1, 0}, {2_s, 1, 2, 3, 5_ms, 2, 0}, {3_s, 1, 2, 3, 5_ms, 5, 0}, @@ -40,7 +41,7 @@ TEST(FilterTest, NoiseFloor) { {8_s, 1, 2, 3, 5_ms, 0.01, 0}, {9_s, 1, 2, 3, 5_ms, 0, 0}}; double noiseFloor = GetNoiseFloor(testData, 2, [](auto&& pt) { return pt.acceleration; }); - EXPECT_NEAR(0.953, noiseFloor, 0.001); + CHECK(0.953 == Catch::Approx(noiseFloor).margin(0.001)); } void FillStepVoltageData(std::vector& data) { @@ -59,7 +60,7 @@ void FillStepVoltageData(std::vector& data) { } } -TEST(FilterTest, StepTrim) { +TEST_CASE("FilterTest StepTrim", "[sysid]") { { std::vector forwardTestData = { {0_s, 1, 0, 0, 1_s, 0}, {0_s, 1, 0, 0, 1_s, 0.25}, @@ -80,8 +81,8 @@ TEST(FilterTest, StepTrim) { maxTime); minTime = tempMinTime; - EXPECT_EQ(3, settings.stepTestDuration.value()); - EXPECT_EQ(2, minTime.value()); + CHECK(3 == settings.stepTestDuration.value()); + CHECK(2 == minTime.value()); } { @@ -104,8 +105,8 @@ TEST(FilterTest, StepTrim) { maxTime); minTime = tempMinTime; - EXPECT_EQ(3, settings.stepTestDuration.value()); - EXPECT_EQ(2, minTime.value()); + CHECK(3 == settings.stepTestDuration.value()); + CHECK(2 == minTime.value()); } { @@ -130,8 +131,8 @@ TEST(FilterTest, StepTrim) { // Expect trimming to reject the erroneous peak negative accel, // correctly picking up the max positive accel instead. - EXPECT_EQ(4, settings.stepTestDuration.value()); - EXPECT_EQ(2, minTime.value()); + CHECK(4 == settings.stepTestDuration.value()); + CHECK(2 == minTime.value()); } } @@ -153,16 +154,16 @@ void AssertCentralResults(F&& f, DfDx&& dfdx, wpi::units::second_t h, // half the window size in the past. // The order of accuracy is O(h^(N - d)) where N is number of stencil // points and d is order of derivative - EXPECT_NEAR(dfdx((i - static_cast((Samples - 1) / 2)) * h.value()), - filter.Calculate(f(i * h.value())), - std::pow(h.value(), Samples - Derivative)); + CHECK(dfdx((i - static_cast((Samples - 1) / 2)) * h.value()) == + Catch::Approx(filter.Calculate(f(i * h.value()))) + .margin(std::pow(h.value(), Samples - Derivative))); } } /** * Test central finite difference. */ -TEST(LinearFilterOutputTest, CentralFiniteDifference) { +TEST_CASE("LinearFilterOutputTest CentralFiniteDifference", "[sysid]") { constexpr auto h = 5_ms; AssertCentralResults<1, 3>( diff --git a/tools/sysid/src/test/native/cpp/analysis/OLSTest.cpp b/tools/sysid/src/test/native/cpp/analysis/OLSTest.cpp index f4d9e33062..ed3396d891 100644 --- a/tools/sysid/src/test/native/cpp/analysis/OLSTest.cpp +++ b/tools/sysid/src/test/native/cpp/analysis/OLSTest.cpp @@ -4,39 +4,75 @@ #include "wpi/sysid/analysis/OLS.hpp" -#include +#ifndef NDEBUG +#ifndef _WIN32 +#include +#include -TEST(OLSTest, TwoVariablesTwoPoints) { +#include +#include +#endif +#endif + +#include +#include + +#ifndef NDEBUG +#ifndef _WIN32 +namespace { + +template +bool Dies(F&& f) { + pid_t pid = fork(); + REQUIRE(pid >= 0); + + if (pid == 0) { + std::signal(SIGABRT, SIG_DFL); + std::freopen("/dev/null", "w", stderr); + f(); + _exit(0); + } + + int status = 0; + REQUIRE(waitpid(pid, &status, 0) == pid); + return WIFSIGNALED(status) || (WIFEXITED(status) && WEXITSTATUS(status) != 0); +} + +} // namespace +#endif +#endif + +TEST_CASE("OLSTest TwoVariablesTwoPoints", "[sysid]") { // (1, 3) and (2, 5). Should produce y = 2x + 1. Eigen::MatrixXd X{{1.0, 1.0}, {1.0, 2.0}}; Eigen::VectorXd y{{3.0}, {5.0}}; auto [coeffs, rSquared, rmse] = sysid::OLS(X, y); - EXPECT_EQ(coeffs.size(), 2u); + CHECK(coeffs.size() == 2u); - EXPECT_NEAR(coeffs[0], 1.0, 1e-12); - EXPECT_NEAR(coeffs[1], 2.0, 1e-12); - EXPECT_DOUBLE_EQ(rSquared, 1.0); + CHECK(coeffs[0] == Catch::Approx(1.0).margin(1e-12)); + CHECK(coeffs[1] == Catch::Approx(2.0).margin(1e-12)); + CHECK(rSquared == Catch::Approx(1.0).margin(1e-12)); } -TEST(OLSTest, TwoVariablesFivePoints) { +TEST_CASE("OLSTest TwoVariablesFivePoints", "[sysid]") { // (2, 4), (3, 5), (5, 7), (7, 10), (9, 15) // Should produce 1.518x + 0.305. Eigen::MatrixXd X{{1, 2}, {1, 3}, {1, 5}, {1, 7}, {1, 9}}; Eigen::VectorXd y{{4}, {5}, {7}, {10}, {15}}; auto [coeffs, rSquared, rmse] = sysid::OLS(X, y); - EXPECT_EQ(coeffs.size(), 2u); + CHECK(coeffs.size() == 2u); - EXPECT_NEAR(coeffs[0], 0.30487804878048774, 1e-12); - EXPECT_NEAR(coeffs[1], 1.5182926829268293, 1e-12); - EXPECT_DOUBLE_EQ(rSquared, 0.91906029466386019); + CHECK(coeffs[0] == Catch::Approx(0.30487804878048774).margin(1e-12)); + CHECK(coeffs[1] == Catch::Approx(1.5182926829268293).margin(1e-12)); + CHECK(rSquared == Catch::Approx(0.91906029466386019).margin(1e-12)); } -#ifndef NDEBUG -TEST(OLSTest, MalformedData) { +#if !defined(NDEBUG) && !defined(_WIN32) +TEST_CASE("OLSTest MalformedData", "[sysid]") { Eigen::MatrixXd X{{1, 2}, {1, 3}, {1, 4}}; Eigen::VectorXd y{{4}, {5}}; - EXPECT_DEATH(sysid::OLS(X, y), ""); + CHECK(Dies([&] { sysid::OLS(X, y); })); } #endif diff --git a/tools/sysid/src/test/native/cpp/analysis/TrackwidthAnalysisTest.cpp b/tools/sysid/src/test/native/cpp/analysis/TrackwidthAnalysisTest.cpp index 77b7e38084..dd1cf2abcf 100644 --- a/tools/sysid/src/test/native/cpp/analysis/TrackwidthAnalysisTest.cpp +++ b/tools/sysid/src/test/native/cpp/analysis/TrackwidthAnalysisTest.cpp @@ -4,9 +4,10 @@ #include "wpi/sysid/analysis/TrackwidthAnalysis.hpp" -#include +#include +#include -TEST(TrackwidthAnalysisTest, Calculate) { +TEST_CASE("TrackwidthAnalysisTest Calculate", "[sysid]") { double result = sysid::CalculateTrackwidth(-0.5386, 0.5386, 90_deg); - EXPECT_NEAR(result, 0.6858, 1E-4); + CHECK(result == Catch::Approx(0.6858).margin(1E-4)); } diff --git a/tools/wpical/BUILD.bazel b/tools/wpical/BUILD.bazel index b3db07aead..300ede3b75 100644 --- a/tools/wpical/BUILD.bazel +++ b/tools/wpical/BUILD.bazel @@ -236,7 +236,7 @@ cc_test( deps = [ ":wpical_lib", "//shared/bazel/thirdparty/ceres", - "//thirdparty/googletest", + "//thirdparty/catch2", "@bazel_tools//tools/cpp/runfiles", ], ) diff --git a/tools/wpical/CMakeLists.txt b/tools/wpical/CMakeLists.txt index 39d5abf7c5..2d59ec061c 100644 --- a/tools/wpical/CMakeLists.txt +++ b/tools/wpical/CMakeLists.txt @@ -106,7 +106,7 @@ elseif(APPLE) endif() if(WITH_TESTS) - wpilib_add_test(wpical src/test/native/cpp) + wpilib_add_test_catch2(wpical src/test/native/cpp) wpilib_link_macos_gui(wpical_test) target_sources(wpical_test PRIVATE ${wpical_src} ${wpical_thirdparty_src}) target_compile_definitions(wpical_test PRIVATE RUNNING_WPICAL_TESTS) @@ -126,7 +126,6 @@ if(WITH_TESTS) ) target_link_libraries( wpical_test - googletest apriltag wpimath ${OpenCV_LIBS} diff --git a/tools/wpical/build.gradle b/tools/wpical/build.gradle index 3bb11173f0..f0cff763d6 100644 --- a/tools/wpical/build.gradle +++ b/tools/wpical/build.gradle @@ -19,8 +19,9 @@ if (OperatingSystem.current().isWindows()) { ext { nativeName = 'wpical' useCpp = true + nativeTestSuiteName = "${nativeName}Catch2Test" sharedCvConfigs = [ - wpicalTest: []] + (nativeTestSuiteName): []] staticCvConfigs = [] } @@ -192,7 +193,7 @@ model { } } testSuites { - "${nativeName}Test"(GoogleTestTestSuiteSpec) { + "${nativeTestSuiteName}"(GoogleTestTestSuiteSpec) { for(NativeComponentSpec c : $.components) { if (c.name == nativeName) { testing c @@ -237,7 +238,7 @@ model { } else { it.cppCompiler.define('GLOG_DEPRECATED', '[[deprecated]]') } - lib project: ':thirdparty:googletest', library: 'googletest', linkage: 'static' + lib project: ':thirdparty:catch2', library: 'catch2', linkage: 'static' it.cppCompiler.define("RUNNING_WPICAL_TESTS") if (it.targetPlatform.operatingSystem.isWindows()) { it.linker.args << 'Gdi32.lib' << 'Shell32.lib' << 'd3d11.lib' << 'd3dcompiler.lib' diff --git a/tools/wpical/src/test/native/cpp/main.cpp b/tools/wpical/src/test/native/cpp/main.cpp index a2b90c5913..a2c05f2d23 100644 --- a/tools/wpical/src/test/native/cpp/main.cpp +++ b/tools/wpical/src/test/native/cpp/main.cpp @@ -2,9 +2,8 @@ // Open Source Software; you can modify and/or share it under the terms of // the WPILib BSD license file in the root directory of this project. -#include +#include int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + return Catch::Session().run(argc, argv); } diff --git a/tools/wpical/src/test/native/cpp/test_calibrate.cpp b/tools/wpical/src/test/native/cpp/test_calibrate.cpp index fd7cf873da..4e5f8b2570 100644 --- a/tools/wpical/src/test/native/cpp/test_calibrate.cpp +++ b/tools/wpical/src/test/native/cpp/test_calibrate.cpp @@ -2,21 +2,26 @@ // Open Source Software; you can modify and/or share it under the terms of // the WPILib BSD license file in the root directory of this project. +#include +#include #include #include +#include +#include -#include +#include #include "cameracalibration.hpp" #include "fieldcalibration.hpp" #include "path_lookup.hpp" #include "wpi/apriltag/AprilTagFieldLayout.hpp" #include "wpi/apriltag/AprilTagFields.hpp" -#include "wpi/util/MemoryBuffer.hpp" #include "wpi/util/fs.hpp" #include "wpi/util/json.hpp" #include "wpi/util/raw_ostream.hpp" +namespace { + const std::string projectRootPath = PROJECT_ROOT_PATH; const char* const tmpdir_c_str = std::getenv("TEST_TMPDIR"); @@ -36,21 +41,34 @@ const std::string fileSuffix = ".mp4"; const std::string videoLocation = "/fieldvideo"; #endif -TEST(CameraCalibrationTest, Typical) { +wpical::CameraModel GetCameraModel() { + static std::optional cameraModel; + if (cameraModel) { + return *cameraModel; + } + auto path = LookupPath(projectRootPath + "/testcalibration" + fileSuffix); auto calibrator = wpical::CameraCalibrator(4, 0.709, 0.551, 12, 8, path); while (!calibrator.IsFinished()) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } auto ret = calibrator.GetCameraModel(); - EXPECT_NE(ret, std::nullopt); + REQUIRE(ret != std::nullopt); + cameraModel = *ret; + return *cameraModel; +} + +} // namespace + +TEST_CASE("CameraCalibrationTest Typical", "[wpical]") { + auto ret = GetCameraModel(); std::error_code ec; wpi::util::raw_fd_ostream output_file(calSavePath + "/cameracalibration.json", ec, fs::OF_Text); - wpi::util::json(ret.value()).marshal(output_file, true, 4); + wpi::util::json(ret).marshal(output_file, true, 4); } -TEST(CameraCalibrationTest, Atypical) { +TEST_CASE("CameraCalibrationTest Atypical", "[wpical]") { auto path = LookupPath(projectRootPath + videoLocation + "/short" + fileSuffix); auto calibrator = wpical::CameraCalibrator(4, 0.709, 0.551, 12, 8, path); @@ -58,83 +76,63 @@ TEST(CameraCalibrationTest, Atypical) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } auto ret = calibrator.GetCameraModel(); - EXPECT_EQ(ret, std::nullopt); + CHECK(ret == std::nullopt); } -TEST(FieldCalibrationTest, Typical) { - auto buffer = - wpi::util::MemoryBuffer::GetFile(calSavePath + "/cameracalibration.json"); - auto buf = buffer.value()->GetCharBuffer(); - auto model = wpi::util::json::parse_or_throw({buf.data(), buf.size()}) - .get(); +TEST_CASE("FieldCalibrationTest Typical", "[wpical]") { + auto model = GetCameraModel(); auto ret = wpical::calibrate(LookupPath(projectRootPath + videoLocation), model, wpi::apriltag::AprilTagFieldLayout::LoadField( wpi::apriltag::AprilTagField::k2024Crescendo), 3, false); - EXPECT_NE(ret, std::nullopt); + REQUIRE(ret != std::nullopt); } -TEST(FieldCalibrationTest, Atypical_Bad_Camera_Model) { +TEST_CASE("FieldCalibrationTest Atypical_Bad_Camera_Model", "[wpical]") { wpical::CameraModel model{}; auto ret = wpical::calibrate(LookupPath(projectRootPath + videoLocation), model, wpi::apriltag::AprilTagFieldLayout::LoadField( wpi::apriltag::AprilTagField::k2024Crescendo), 3, false); - EXPECT_EQ(ret, std::nullopt); + CHECK(ret == std::nullopt); } -TEST(FieldCalibrationTest, Atypical_Bad_Field_Layout) { - auto buffer = - wpi::util::MemoryBuffer::GetFile(calSavePath + "/cameracalibration.json"); - auto buf = buffer.value()->GetCharBuffer(); - auto model = wpi::util::json::parse_or_throw({buf.data(), buf.size()}) - .get(); +TEST_CASE("FieldCalibrationTest Atypical_Bad_Field_Layout", "[wpical]") { + auto model = GetCameraModel(); auto ret = wpical::calibrate(LookupPath(projectRootPath + videoLocation), model, wpi::apriltag::AprilTagFieldLayout{}, 3, false); - EXPECT_EQ(ret, std::nullopt); + CHECK(ret == std::nullopt); } -TEST(FieldCalibrationTest, Atypical_Bad_Input_Directory) { - auto buffer = - wpi::util::MemoryBuffer::GetFile(calSavePath + "/cameracalibration.json"); - auto buf = buffer.value()->GetCharBuffer(); - auto model = wpi::util::json::parse_or_throw({buf.data(), buf.size()}) - .get(); +TEST_CASE("FieldCalibrationTest Atypical_Bad_Input_Directory", "[wpical]") { + auto model = GetCameraModel(); auto ret = wpical::calibrate(LookupPath(projectRootPath), model, wpi::apriltag::AprilTagFieldLayout::LoadField( wpi::apriltag::AprilTagField::k2024Crescendo), 3, false); - EXPECT_EQ(ret, std::nullopt); + CHECK(ret == std::nullopt); } -TEST(FieldCalibrationTest, Atypical_Bad_Pinned_Tag) { - auto buffer = - wpi::util::MemoryBuffer::GetFile(calSavePath + "/cameracalibration.json"); - auto buf = buffer.value()->GetCharBuffer(); - auto model = wpi::util::json::parse_or_throw({buf.data(), buf.size()}) - .get(); +TEST_CASE("FieldCalibrationTest Atypical_Bad_Pinned_Tag", "[wpical]") { + auto model = GetCameraModel(); auto ret = wpical::calibrate(LookupPath(projectRootPath + videoLocation), model, wpi::apriltag::AprilTagFieldLayout::LoadField( wpi::apriltag::AprilTagField::k2024Crescendo), 42, false); - EXPECT_EQ(ret, std::nullopt); + CHECK(ret == std::nullopt); } -TEST(FieldCalibrationTest, Atypical_Bad_Pinned_Tag_Negative) { - auto buffer = - wpi::util::MemoryBuffer::GetFile(calSavePath + "/cameracalibration.json"); - auto buf = buffer.value()->GetCharBuffer(); - auto model = wpi::util::json::parse_or_throw({buf.data(), buf.size()}) - .get(); +TEST_CASE("FieldCalibrationTest Atypical_Bad_Pinned_Tag_Negative", "[wpical]") { + auto model = GetCameraModel(); auto ret = wpical::calibrate(LookupPath(projectRootPath + videoLocation), model, wpi::apriltag::AprilTagFieldLayout::LoadField( wpi::apriltag::AprilTagField::k2024Crescendo), -1, false); - EXPECT_EQ(ret, std::nullopt); + CHECK(ret == std::nullopt); } diff --git a/tools/wpical/src/test/native/cpp/test_result_is_exact.cpp b/tools/wpical/src/test/native/cpp/test_result_is_exact.cpp index 444ab7a79b..0823efcc10 100644 --- a/tools/wpical/src/test/native/cpp/test_result_is_exact.cpp +++ b/tools/wpical/src/test/native/cpp/test_result_is_exact.cpp @@ -2,15 +2,20 @@ // Open Source Software; you can modify and/or share it under the terms of // the WPILib BSD license file in the root directory of this project. +#include #include +#include #include #include #include #include +#include #include #include -#include +#include +#include +#include #include #include "path_lookup.hpp" @@ -148,7 +153,7 @@ std::vector calibrate(const std::string& fname, cv::Size boardSize, const std::string projectRootPath = PROJECT_ROOT_PATH; -TEST(MrcalResultExactlyMatchesTest, lifecam_1280) { +TEST_CASE("MrcalResultExactlyMatchesTest lifecam_1280", "[wpical]") { auto calculated_intrinsics{ calibrate(LookupPath(projectRootPath + "/lifecam_1280p_10x10.vnl"), {10, 10}, {1280, 720})}; @@ -163,8 +168,12 @@ TEST(MrcalResultExactlyMatchesTest, lifecam_1280) { 0.1489878273, -1.348622726, 0.002839630852, 0.001135629909, 2.560627057, -0.03170208336, 0.0695788644, -0.09547554864}; - for (int i = 0; i < 12; i++) { - EXPECT_NEAR(mrcal_cli_groundtruth_intrinsics[i], calculated_intrinsics[i], - 1e-6); + REQUIRE(calculated_intrinsics.size() == + mrcal_cli_groundtruth_intrinsics.size()); + + for (size_t i = 0; i < mrcal_cli_groundtruth_intrinsics.size(); i++) { + UNSCOPED_INFO("i = " << i); + CHECK(mrcal_cli_groundtruth_intrinsics[i] == + Catch::Approx(calculated_intrinsics[i]).margin(1e-6)); } }