fwt_software/tests/test_dumpparser.cpp

137 lines
5.3 KiB
C++

#include <doctest/doctest.h>
#include "fgc/DumpParser.h"
#include <algorithm>
#include <string>
using namespace fgc;
namespace {
// A realistic two-axis firmware DUMP block (the on-wire format from
// firmware/src/motor.cpp printDump): header, then per axis a state line and a
// TMC register line, bracketed by DUMP BEGIN / DUMP END.
const char* kDump =
"DUMP BEGIN build=11dd3ce-dirty uptime=11740067 mcusr=0x01 free_ram=1852\n"
"DUMP Y state=3 hsub=10 enabled=1 lim_neg=-82919 lim_pos=98687 hold_target=-90 "
"speed=50000 eeprom_restored=0 soft_margin=5000 hold_corr=7 hold_peak=430 hold_cumul=2810 "
"has_encoder=1\n"
"DUMP Y TMC GCONF=0x0000000C GSTAT=0x00000000 IOIN=0x30000008 TSTEP=0x000FFFFF "
"RAMPMODE=0x00000000 XACTUAL=0xFFFFFFA6 VACTUAL=0x00000000 XTARGET=0xFFFFFFA6 "
"SW_MODE=0x000008A0 RAMP_STAT=0x00001680 X_ENC=0xFFFFFF7F ENC_STATUS=0x00000002 "
"CHOPCONF=0x00410043 DRV_STATUS=0x80084000 PWM_SCALE=0x00000011 PWM_AUTO=0x003F0039\n"
"DUMP P state=3 hsub=10 enabled=1 lim_neg=495183 lim_pos=1069042 hold_target=500183 "
"speed=150000 eeprom_restored=0 has_encoder=1\n"
"DUMP P TMC GCONF=0x0000000C GSTAT=0x00000000 IOIN=0x30000008 TSTEP=0x000FFFFF "
"RAMPMODE=0x00000000 XACTUAL=0x0007A1C8 VACTUAL=0x00000000 XTARGET=0x0007A1C8 "
"SW_MODE=0x000008A3 RAMP_STAT=0x00001688 X_ENC=0x0007A231 ENC_STATUS=0x00000002 "
"CHOPCONF=0x00410043 DRV_STATUS=0x80084000 PWM_SCALE=0x00010011 PWM_AUTO=0x0021003C\n"
"DUMP END\n";
bool has(const std::vector<std::string>& v, const std::string& s) {
return std::find(v.begin(), v.end(), s) != v.end();
}
const DumpAxis* axis(const DumpData& d, char a) {
for (const auto& ax : d.axes)
if (ax.axis == a) return &ax;
return nullptr;
}
} // namespace
TEST_CASE("parseDump decodes the header") {
DumpData d = parseDump(kDump);
REQUIRE(d.valid);
CHECK(d.build == "11dd3ce-dirty");
CHECK(d.uptime_ms == 11740067);
CHECK(d.mcusr == 0x01u);
CHECK(d.free_ram == 1852);
CHECK(has(d.reset_flags, "PORF (power-on)"));
REQUIRE(d.axes.size() == 2);
}
TEST_CASE("parseDump decodes the per-axis state line") {
DumpData d = parseDump(kDump);
const DumpAxis* y = axis(d, 'Y');
REQUIRE(y != nullptr);
CHECK(y->state_name == "READY"); // state=3
CHECK(y->enabled);
CHECK(y->has_encoder);
CHECK_FALSE(y->eeprom_restored);
CHECK(y->lim_neg == -82919);
CHECK(y->lim_pos == 98687);
CHECK(y->hold_target == -90);
CHECK(y->speed == 50000);
CHECK(y->hsub == 10);
CHECK(y->soft_margin == 5000);
CHECK(y->hold_corrections == 7);
CHECK(y->hold_peak_dev == 430);
CHECK(y->hold_cumulative_dev == 2810);
const DumpAxis* p = axis(d, 'P');
REQUIRE(p != nullptr);
CHECK(p->lim_neg == 495183);
CHECK(p->lim_pos == 1069042);
}
TEST_CASE("parseDump keeps all 16 TMC registers (regression: TMC-line whitespace)") {
// The "DUMP Y TMC ..." line was once misclassified due to a leading-space
// slice, dropping every register. Guard that all of them survive.
DumpData d = parseDump(kDump);
const DumpAxis* y = axis(d, 'Y');
REQUIRE(y != nullptr);
for (const char* r : {"GCONF", "GSTAT", "IOIN", "TSTEP", "RAMPMODE", "XACTUAL",
"VACTUAL", "XTARGET", "SW_MODE", "RAMP_STAT", "X_ENC",
"ENC_STATUS", "CHOPCONF", "DRV_STATUS", "PWM_SCALE", "PWM_AUTO"}) {
CHECK_MESSAGE(y->regs.count(r) == 1, "missing register ", r);
}
CHECK(y->regs.at("RAMP_STAT") == "0x00001680");
CHECK(y->regs.at("DRV_STATUS") == "0x80084000");
}
TEST_CASE("parseDump decodes status-register bit flags") {
DumpData d = parseDump(kDump);
const DumpAxis* y = axis(d, 'Y');
REQUIRE(y != nullptr);
// DRV_STATUS = 0x80084000 -> bit31 stst, bit14 stealth; CS_ACTUAL=(>>16)&0x1F=8.
CHECK(y->drv_status == 0x80084000u);
CHECK(has(y->drv_flags, "stst"));
CHECK(has(y->drv_flags, "stealth"));
CHECK(y->cs_actual == 8);
CHECK(y->sg_result == 0);
// GSTAT = 0 -> no flags (placeholder "-").
CHECK(has(y->gstat_flags, "-"));
// RAMP_STAT = 0x1680 -> bit7 event_pos_reached, bit9 position_reached, bit10 vzero.
CHECK(y->ramp_stat == 0x00001680u);
CHECK(has(y->ramp_flags, "event_pos_reached"));
CHECK(has(y->ramp_flags, "position_reached"));
CHECK(has(y->ramp_flags, "vzero"));
}
TEST_CASE("parseDump tolerates a corrupted/merged DUMP END (best-effort)") {
// The host detects DUMP END anywhere in a line; a clean header still parses
// even when later content is mangled — but a block with no BEGIN/END is invalid.
CHECK_FALSE(parseDump("").valid);
CHECK_FALSE(parseDump("garbage with no markers\n").valid);
CHECK_FALSE(parseDump("DUMP Y state=3\nno end marker\n").valid);
}
TEST_CASE("formatDump renders valid and invalid dumps") {
auto bad = formatDump(DumpData{});
REQUIRE(bad.size() == 1);
CHECK(bad[0].find("no firmware dump") != std::string::npos);
auto good = formatDump(parseDump(kDump));
std::string joined;
for (const auto& l : good) joined += l + "\n";
CHECK(joined.find("build=11dd3ce-dirty") != std::string::npos);
CHECK(joined.find("[Y]") != std::string::npos);
CHECK(joined.find("[P]") != std::string::npos);
CHECK(joined.find("RAMP_STAT") != std::string::npos);
}