mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-28 02:11:43 +00:00
[tools] Translate unit tests to catch2 (#9006)
This commit is contained in:
@@ -53,7 +53,7 @@ cc_test(
|
||||
],
|
||||
deps = [
|
||||
":sysid-lib",
|
||||
"//thirdparty/googletest",
|
||||
"//thirdparty/catch2",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <gtest/gtest.h>
|
||||
#include <catch2/catch_session.hpp>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
int ret = RUN_ALL_TESTS();
|
||||
return ret;
|
||||
return Catch::Session().run(argc, argv);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
|
||||
#include "wpi/sysid/analysis/AnalysisType.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
|
||||
#include "wpi/sysid/analysis/FeedbackAnalysis.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#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));
|
||||
}
|
||||
|
||||
@@ -9,8 +9,12 @@
|
||||
#include <bitset>
|
||||
#include <cmath>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_message.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#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<const double> expectedGains,
|
||||
std::span<const double> actualGains,
|
||||
std::span<const double> tolerances) {
|
||||
bool FitIsBad(std::span<const double> expectedGains,
|
||||
std::span<const double> actualGains,
|
||||
std::span<const double> 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<const double> expectedGains,
|
||||
std::span<const double> actualGains) {
|
||||
std::ostringstream result;
|
||||
|
||||
result << "\n";
|
||||
for (size_t i = 0; i < expectedGains.size(); ++i) {
|
||||
@@ -158,7 +167,7 @@ testing::AssertionResult FitIsBad(std::span<const double> expectedGains,
|
||||
result << " diff " << std::abs(expectedGains[i] - actualGains[i]) << "\n";
|
||||
}
|
||||
|
||||
return result;
|
||||
return result.str();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,12 +182,13 @@ void ExpectArrayNear(std::span<const double> expected,
|
||||
std::span<const double> 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;
|
||||
|
||||
@@ -6,14 +6,15 @@
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#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<sysid::PreparedData> 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<sysid::PreparedData> 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<sysid::PreparedData>& data) {
|
||||
@@ -59,7 +60,7 @@ void FillStepVoltageData(std::vector<sysid::PreparedData>& data) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FilterTest, StepTrim) {
|
||||
TEST_CASE("FilterTest StepTrim", "[sysid]") {
|
||||
{
|
||||
std::vector<sysid::PreparedData> 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<int>((Samples - 1) / 2)) * h.value()),
|
||||
filter.Calculate(f(i * h.value())),
|
||||
std::pow(h.value(), Samples - Derivative));
|
||||
CHECK(dfdx((i - static_cast<int>((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>(
|
||||
|
||||
@@ -4,39 +4,75 @@
|
||||
|
||||
#include "wpi/sysid/analysis/OLS.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#ifndef NDEBUG
|
||||
#ifndef _WIN32
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
TEST(OLSTest, TwoVariablesTwoPoints) {
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#ifndef _WIN32
|
||||
namespace {
|
||||
|
||||
template <typename F>
|
||||
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
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
|
||||
#include "wpi/sysid/analysis/TrackwidthAnalysis.hpp"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ cc_test(
|
||||
deps = [
|
||||
":wpical_lib",
|
||||
"//shared/bazel/thirdparty/ceres",
|
||||
"//thirdparty/googletest",
|
||||
"//thirdparty/catch2",
|
||||
"@bazel_tools//tools/cpp/runfiles",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 <gtest/gtest.h>
|
||||
#include <catch2/catch_session.hpp>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
return Catch::Session().run(argc, argv);
|
||||
}
|
||||
|
||||
@@ -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 <chrono>
|
||||
#include <cstdlib>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <thread>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#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<wpical::CameraModel> 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<wpical::CameraModel>();
|
||||
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<wpical::CameraModel>();
|
||||
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<wpical::CameraModel>();
|
||||
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<wpical::CameraModel>();
|
||||
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<wpical::CameraModel>();
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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 <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_message.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <mrcal_wrapper.h>
|
||||
|
||||
#include "path_lookup.hpp"
|
||||
@@ -148,7 +153,7 @@ std::vector<double> 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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user