initial commit

This commit is contained in:
awe
2026-02-20 16:44:26 +03:00
commit afc595bc51
11 changed files with 1374 additions and 0 deletions

237
tests/test_sweep_core.cpp Normal file
View 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;
}