111 lines
3.7 KiB
C++
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());
|
|
}
|