#include "adc_sweep/shm_stack.hpp" #include "adc_sweep/sweep_core.hpp" #include #include #include #include #include #include #include #include #include #include #include using adc_sweep::BinarySweepParser; using adc_sweep::ShmHeader; using adc_sweep::ShmSweepStackWriter; using adc_sweep::SweepFinalizer; namespace { int g_fail = 0; void expect(bool cond, const std::string& msg) { if (!cond) { ++g_fail; std::cerr << "[FAIL] " << msg << "\n"; } } void test_u32_to_i32() { expect(adc_sweep::u32_to_i32(0x00000000U) == 0, "u32_to_i32 zero"); expect(adc_sweep::u32_to_i32(0x7FFFFFFFU) == 2147483647, "u32_to_i32 max pos"); expect(adc_sweep::u32_to_i32(0x80000000U) == static_cast(0x80000000U), "u32_to_i32 min neg"); expect(adc_sweep::u32_to_i32(0xFFFFFFFFU) == -1, "u32_to_i32 -1"); } void append_word(std::vector& out, uint16_t w) { out.push_back(static_cast(w & 0xFFU)); out.push_back(static_cast((w >> 8U) & 0xFFU)); } void test_parser_modes_and_resync() { BinarySweepParser p; int emitted = 0; std::vector bytes; append_word(bytes, 0x1234); // garbage append_word(bytes, 0x5678); // garbage append_word(bytes, 0xFFFF); append_word(bytes, 0xFFFF); append_word(bytes, 0xFFFF); append_word(bytes, static_cast((2U << 8U) | 0x0AU)); append_word(bytes, 10); // step append_word(bytes, 0x0000); // hi append_word(bytes, 0x0001); // lo => 1 append_word(bytes, 0x000A); append_word(bytes, 11); append_word(bytes, 0xFFFF); append_word(bytes, 0xFFFE); // -2 append_word(bytes, 0x000A); // legacy start emits previous sweep append_word(bytes, 0xFFFF); append_word(bytes, 0xFFFF); append_word(bytes, 3); append_word(bytes, 0x0A0A); p.feed(bytes.data(), bytes.size(), [&](const std::vector& xs, const std::vector& ys, uint32_t ch_mask, int32_t ch_primary) { ++emitted; expect(xs.size() == 2, "parser emitted 2 points"); expect(ys.size() == 2, "parser emitted 2 values"); expect(xs[0] == 10 && ys[0] == 1, "parser point0"); expect(xs[1] == 11 && ys[1] == -2, "parser point1"); expect(ch_primary == 2, "parser primary ch"); expect((ch_mask & (1U << 2U)) != 0, "parser ch mask"); }); p.flush([&](const std::vector& xs, const std::vector&, uint32_t, int32_t ch_primary) { ++emitted; expect(xs.empty(), "legacy sweep after start has no points in this stream"); expect(ch_primary == 3, "legacy primary ch"); }); // flush doesn't emit empty sweep by implementation, so emitted should be 1 expect(emitted == 1, "parser emitted exactly one completed sweep"); } void test_finalize_stats_and_fill() { SweepFinalizer f(8, 10.0F, false); std::vector xs{0, 2, 4}; std::vector ys{1, 3, 5}; auto out = f.finalize(xs, ys, (1U << 2U), 2); expect(out.has_value(), "finalize returns sweep"); expect(out->sweep.size() == 5, "width=max_x+1 when no fancy"); expect(std::isnan(out->sweep[1]), "gap is NaN without fancy"); expect(out->meta.ch_primary == 2, "meta ch primary"); SweepFinalizer ff(8, 10.0F, true); auto out2 = ff.finalize(xs, ys, (1U << 2U), 2); expect(out2.has_value(), "fancy finalize returns sweep"); expect(!std::isnan(out2->sweep[1]), "gap filled in fancy"); SweepFinalizer fi(8, 10.0F, false); std::vector x2{0, 1}; std::vector y2{1, 2}; auto out3 = fi.finalize(x2, y2, 0, 0); expect(out3.has_value(), "invert finalize"); expect(out3->sweep[0] < 0 && out3->sweep[1] < 0, "inversion applied when mean < threshold"); } void test_replay_acm9_integration() { int fd = -1; const char* candidates[] = {"acm_9", "../acm_9", "../../acm_9"}; for (const char* p : candidates) { fd = ::open(p, O_RDONLY); if (fd >= 0) { break; } } expect(fd >= 0, "acm_9 exists for integration test"); if (fd < 0) { return; } std::vector data(1 << 16); BinarySweepParser p; SweepFinalizer f(1000, 10.0F, false); int n_sweeps = 0; while (true) { const ssize_t n = ::read(fd, data.data(), data.size()); if (n <= 0) { break; } p.feed(data.data(), static_cast(n), [&](const std::vector& xs, const std::vector& ys, uint32_t ch_mask, int32_t ch_primary) { auto out = f.finalize(xs, ys, ch_mask, ch_primary); if (out.has_value()) { ++n_sweeps; expect(!out->sweep.empty(), "replay sweep non-empty"); } }); } p.flush([&](const std::vector& xs, const std::vector& ys, uint32_t ch_mask, int32_t ch_primary) { auto out = f.finalize(xs, ys, ch_mask, ch_primary); if (out.has_value()) { ++n_sweeps; } }); ::close(fd); expect(n_sweeps > 0, "replay acm_9 produced sweeps"); } void test_shm_publish_and_read() { const std::string shm_name = "/adc_sweep_test_stack"; { ShmSweepStackWriter w(shm_name, 16, 32); std::string err; if (!w.open_or_create(err)) { if (err.find("Permission denied") != std::string::npos) { std::cerr << "[SKIP] shm_open is not permitted in this environment\n"; return; } expect(false, "shm open/create"); return; } for (int i = 0; i < 128; ++i) { adc_sweep::SweepResult s; s.sweep.assign(32, static_cast(i)); s.meta.sweep_idx = static_cast(i + 1); s.meta.ch_primary = 2; s.meta.ch_mask = (1U << 2U); s.meta.n_valid = 32.0F; s.meta.min = static_cast(i); s.meta.max = static_cast(i); s.meta.mean = static_cast(i); s.meta.std = 0.0F; s.meta.dt_ms = 1.0F; s.meta.ts_mono_ns = static_cast(i); expect(w.publish(s, err), "shm publish"); } } const int fd = ::shm_open(shm_name.c_str(), O_RDONLY, 0); expect(fd >= 0, "shm open readonly"); if (fd < 0) { return; } const size_t map_size = sizeof(ShmHeader) + 16 * ((sizeof(adc_sweep::SweepSlotHeader) + 32 * sizeof(float) + 63) & ~63U); void* mapped = ::mmap(nullptr, map_size, PROT_READ, MAP_SHARED, fd, 0); expect(mapped != MAP_FAILED, "mmap readonly"); if (mapped != MAP_FAILED) { auto snap = adc_sweep::read_latest_snapshot(mapped, 16, 32, static_cast((sizeof(adc_sweep::SweepSlotHeader) + 32 * sizeof(float) + 63) & ~63U)); expect(snap.has_value(), "snapshot exists"); if (snap.has_value()) { expect(snap->seq >= 128, "snapshot seq latest"); expect(!snap->sweep.empty(), "snapshot sweep present"); } ::munmap(mapped, map_size); } ::close(fd); ::shm_unlink(shm_name.c_str()); } } // namespace int main() { test_u32_to_i32(); test_parser_modes_and_resync(); test_finalize_stats_and_fill(); test_replay_acm9_integration(); test_shm_publish_and_read(); if (g_fail == 0) { std::cout << "All tests passed\n"; return 0; } std::cerr << g_fail << " test(s) failed\n"; return 1; }