fwt_software/tests/test_testrunner.cpp

111 lines
3.7 KiB
C++

#include <doctest/doctest.h>
#include "fgc/TestRunner.h"
#include "fgc/mock/MockImuSource.h"
#include "fgc/mock/MockMotorController.h"
#include <chrono>
#include <thread>
using namespace fgc;
namespace {
// A tiny, fast profile so a full run completes in well under a second.
TestProfile fastProfile() {
TestProfile p;
p.name = "test";
p.params["reps"] = 2;
p.params["home_begin_timeout_ms"] = 80;
p.params["home_timeout_ms"] = 2000;
p.params["settle_timeout_ms"] = 2000;
p.params["encoder_diag_reps"] = 1;
p.params["hold_window_ms"] = 50;
p.params["balance_steps"] = 3;
p.params["imu_sample_window_ms"] = 80;
p.params["imu_drift_window_ms"] = 80;
p.text["backlash_step_counts"] = "500";
return p;
}
Geometry unitGeometry() {
Geometry g;
g.yaw.counts_per_deg = 1000; g.yaw.zero_count = 0; g.yaw.min_deg = -90; g.yaw.max_deg = 90;
g.pitch.counts_per_deg = 1000; g.pitch.zero_count = 0; g.pitch.min_deg = 0; g.pitch.max_deg = 60;
return g;
}
std::optional<TestReport> runToCompletion(TestRunner& tr, int max_ms = 8000) {
REQUIRE(tr.start());
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(max_ms);
while (std::chrono::steady_clock::now() < deadline) {
if (auto rep = tr.takeReport()) return rep;
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
return std::nullopt;
}
} // namespace
TEST_CASE("resolveTestSelection maps subsystem/test tokens") {
uint32_t m = 0;
std::string err;
CHECK(resolveTestSelection("", "", m, err));
CHECK(m == T_ALL);
CHECK(resolveTestSelection("gimbal", "", m, err));
CHECK(m == T_GIMBAL_ALL);
CHECK(resolveTestSelection("gimbal", "homing", m, err));
CHECK(m == T_GIMBAL_HOMING);
CHECK(resolveTestSelection("host", "disk", m, err));
CHECK(m == T_HOST_DISK);
CHECK_FALSE(resolveTestSelection("bogus", "", m, err));
CHECK_FALSE(resolveTestSelection("gimbal", "bogus", m, err));
}
TEST_CASE("TestRunner host-only run completes and produces sections") {
MockMotorController motor;
TestRunner tr(motor, nullptr, unitGeometry(), T_HOST_ALL, fastProfile(), ".");
auto rep = runToCompletion(tr);
REQUIRE(rep.has_value());
CHECK_FALSE(tr.running());
CHECK(rep->find("host_memory", "ram_avail_mb") != nullptr);
CHECK(rep->find("host_disk", "free_gb") != nullptr);
}
TEST_CASE("TestRunner full run with mocks completes with all selected sections") {
MockMotorController motor;
MockImuSource imu;
imu.start();
TestRunner tr(motor, &imu, unitGeometry(), T_ALL, fastProfile(), ".");
auto rep = runToCompletion(tr);
REQUIRE(rep.has_value());
auto has = [&](const char* id) {
for (const auto& s : rep->sections) if (s.id == id) return true;
return false;
};
CHECK(has("homing"));
CHECK(has("encoder"));
CHECK(has("friction"));
CHECK(has("backlash"));
CHECK(has("balance"));
CHECK(has("imu_config"));
CHECK(has("imu_health"));
CHECK(has("imu_drift"));
CHECK(has("host_thermal"));
}
TEST_CASE("TestRunner is cancellable") {
MockMotorController motor;
MockImuSource imu;
imu.start();
TestProfile p = fastProfile();
p.params["imu_drift_window_ms"] = 5000; // long enough to cancel mid-run
TestRunner tr(motor, &imu, unitGeometry(), T_ALL, p, ".");
REQUIRE(tr.start());
std::this_thread::sleep_for(std::chrono::milliseconds(50));
tr.cancel();
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(6);
while (tr.running() && std::chrono::steady_clock::now() < deadline)
std::this_thread::sleep_for(std::chrono::milliseconds(20));
CHECK_FALSE(tr.running());
}