diff --git a/.github/workflows/pr-build-check.yml b/.github/workflows/pr-build-check.yml index eb2a932..99fd848 100644 --- a/.github/workflows/pr-build-check.yml +++ b/.github/workflows/pr-build-check.yml @@ -38,3 +38,11 @@ jobs: export PATH=/opt/$PLATFORM/bin:$PATH cmake -H. -Bbuild -DCMAKE_C_COMPILER=${TOOLCHAIN}-gcc -DCMAKE_BUILD_TYPE=Release cmake --build build + + test-extraction-pipeline: + name: Test sensor extraction pipeline + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run end-to-end pipeline smoke test + run: tools/test_pipeline.sh diff --git a/docs/sensor-driver-extraction.md b/docs/sensor-driver-extraction.md index b700e21..20bbd3f 100644 --- a/docs/sensor-driver-extraction.md +++ b/docs/sensor-driver-extraction.md @@ -312,6 +312,19 @@ sensor: `_linear_init` and `_post_init_exposure_prime`, plus a comment block summarising the most-frequently-written runtime registers (the AE/AGC hot list). +The output is **standalone-buildable** — it includes a small "SDK stubs" +block (typedef for `VI_PIPE`, a no-op `sensor_write_register`) so that +`gcc -fsyntax-only` and `gcc -c` succeed without the vendor headers. +Delete that block and replace it with `#include "hi_comm_video.h"` / +`#include "hi_sns_ctrl.h"` plus the vendor's bus-aware +`sensor_write_register` to integrate into a HiSilicon SDK build. + +`tools/test_pipeline.sh` runs the full segment → generate → compile flow +end-to-end on a synthetic trace and is wired into CI +(`pr-build-check.yml::test-extraction-pipeline`), so a regression in +any of the three Python scripts that breaks the generator output is +caught at PR time. + ```bash python3 tools/trace_to_driver.py tools/dumps/cap.log.segments.json \ --sensor sc2315e \ diff --git a/tools/test_pipeline.sh b/tools/test_pipeline.sh new file mode 100755 index 0000000..0951504 --- /dev/null +++ b/tools/test_pipeline.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# End-to-end smoke test for the post-processing pipeline. +# +# Synthesises a minimal trace, runs: +# trace_segment.py -> trace_to_driver.py -> gcc -fsyntax-only +# and verifies each step succeeds. Designed for CI: no hardware needed, +# no external deps beyond python3 and gcc. + +set -euo pipefail + +cd "$(dirname "$0")/.." + +tmp=$(mktemp -d) +trap "rm -rf $tmp" EXIT + +# Minimal synthetic trace: probe + init (reset + a few writes + stream-on) +# + runtime (one register written enough times to trigger the runtime +# heuristic at threshold >= 3). +cat > "$tmp/sample.log" <<'TRACE' +[100] child 101 created +sensor_i2c_change_addr(0x60); +sensor_write_register(0x100, 0x0); +usleep(10000) +sensor_write_register(0x3034, 0x81); +sensor_write_register(0x3039, 0xa6); +sensor_write_register(0x320e, 0x4); +sensor_write_register(0x100, 0x1); +sensor_write_register(0x3e02, 0x80); +sensor_write_register(0x5781, 0x60); +sensor_write_register(0x5781, 0x60); +sensor_write_register(0x5781, 0x60); +sensor_write_register(0x5781, 0x60); +TRACE + +echo "== trace_segment.py ==" +python3 tools/trace_segment.py "$tmp/sample.log" --out "$tmp/segments.json" +test -s "$tmp/segments.json" || { echo "segments.json empty"; exit 1; } +python3 - "$tmp/segments.json" <<'PY' +import json, sys +d = json.load(open(sys.argv[1])) +phases = d["phases"] +assert phases.get("init"), "init phase missing" +assert phases.get("runtime"), "runtime phase missing" +assert d["summary"]["init"] >= 3, f"init too short: {d['summary']}" +assert d["summary"]["runtime"] >= 3, f"runtime too short: {d['summary']}" +print(f" phases: {d['summary']}") +PY + +echo "== trace_to_driver.py ==" +python3 tools/trace_to_driver.py "$tmp/segments.json" \ + --sensor testsensor --out "$tmp/driver.c" +test -s "$tmp/driver.c" || { echo "driver.c empty"; exit 1; } +grep -q '^void testsensor_linear_init' "$tmp/driver.c" \ + || { echo "linear_init function not emitted"; exit 1; } + +echo "== gcc -fsyntax-only ==" +gcc -Wall -Wextra -fsyntax-only "$tmp/driver.c" + +echo "== gcc -c (full compile) ==" +gcc -Wall -Wextra -c "$tmp/driver.c" -o "$tmp/driver.o" +test -s "$tmp/driver.o" || { echo "object empty"; exit 1; } + +echo "== trace_diff.py self-diff (must be 100%) ==" +python3 tools/trace_diff.py "$tmp/driver.c" "$tmp/driver.c" \ + --gen-scope testsensor_linear_init \ + --ref-scope testsensor_linear_init | tee "$tmp/diff.out" +grep -q '100.0%)' "$tmp/diff.out" \ + || { echo "self-diff not 100%"; exit 1; } + +echo "OK: pipeline test passed" diff --git a/tools/trace_to_driver.py b/tools/trace_to_driver.py index 69c8b6e..7408353 100644 --- a/tools/trace_to_driver.py +++ b/tools/trace_to_driver.py @@ -22,15 +22,30 @@ * Runtime AE/AGC writes are emitted as a comment block, not C code - * the surrounding logic (gain math, exposure scaling) is not derivable * from a register trace alone. + * + * To integrate into a HiSilicon SDK build: + * - Replace the SDK-stubs block with #include "hi_comm_video.h" and + * #include "hi_sns_ctrl.h" + * - Replace the sensor_write_register stub with the vendor's bus-aware + * implementation, typically declared in _sensor_ctl.c + * + * As shipped, this file passes `gcc -fsyntax-only` standalone. */ -#include "hi_comm_video.h" -#include "hi_sns_ctrl.h" - -extern void {sensor}_write_register(VI_PIPE ViPipe, HI_U32 addr, HI_U32 data); +#include + +/* --- SDK stubs (delete when integrating into a vendor SDK) --- */ +typedef int VI_PIPE; +static inline void sensor_write_register(unsigned int addr, unsigned int val) +{{ + (void)addr; + (void)val; +}} +/* --- end SDK stubs --- */ """ FN_TEMPLATE = """ void {sensor}_{suffix}(VI_PIPE ViPipe) {{ + (void)ViPipe; /* implicit pipe ID in the vendor SDK; void-cast for the standalone scaffold */ {body}}} """