initial commit
This commit is contained in:
237
tests/test_sweep_core.cpp
Normal file
237
tests/test_sweep_core.cpp
Normal file
@ -0,0 +1,237 @@
|
||||
#include "adc_sweep/shm_stack.hpp"
|
||||
#include "adc_sweep/sweep_core.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
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<int32_t>(0x80000000U), "u32_to_i32 min neg");
|
||||
expect(adc_sweep::u32_to_i32(0xFFFFFFFFU) == -1, "u32_to_i32 -1");
|
||||
}
|
||||
|
||||
void append_word(std::vector<uint8_t>& out, uint16_t w) {
|
||||
out.push_back(static_cast<uint8_t>(w & 0xFFU));
|
||||
out.push_back(static_cast<uint8_t>((w >> 8U) & 0xFFU));
|
||||
}
|
||||
|
||||
void test_parser_modes_and_resync() {
|
||||
BinarySweepParser p;
|
||||
int emitted = 0;
|
||||
|
||||
std::vector<uint8_t> 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<uint16_t>((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<int>& xs,
|
||||
const std::vector<int32_t>& 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<int>& xs,
|
||||
const std::vector<int32_t>&,
|
||||
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<int> xs{0, 2, 4};
|
||||
std::vector<int32_t> 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<int> x2{0, 1};
|
||||
std::vector<int32_t> 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<uint8_t> 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<size_t>(n), [&](const std::vector<int>& xs,
|
||||
const std::vector<int32_t>& 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<int>& xs,
|
||||
const std::vector<int32_t>& 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<float>(i));
|
||||
s.meta.sweep_idx = static_cast<uint32_t>(i + 1);
|
||||
s.meta.ch_primary = 2;
|
||||
s.meta.ch_mask = (1U << 2U);
|
||||
s.meta.n_valid = 32.0F;
|
||||
s.meta.min = static_cast<float>(i);
|
||||
s.meta.max = static_cast<float>(i);
|
||||
s.meta.mean = static_cast<float>(i);
|
||||
s.meta.std = 0.0F;
|
||||
s.meta.dt_ms = 1.0F;
|
||||
s.meta.ts_mono_ns = static_cast<uint64_t>(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<uint32_t>((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;
|
||||
}
|
||||
Reference in New Issue
Block a user