#!/usr/bin/env bash # Host tests for keyrelay's pure logic modules. # # tests/run_tests.sh build and run every suite # tests/run_tests.sh clean remove the build directory # # The modules under test (src/keystate.h, src/proto.h, src/status.h, src/text.h, # src/mouse.h, src/output.h, src/ws_codec.h, src/sha1.h, src/tls_pem.h, and the pure # policy halves of src/tls_session.h and src/conn.h) have no Arduino or # ESP-IDF # dependency by design, so the exact code that runs on the board is compiled and # exercised here before anything is flashed. # # A C++ compiler is not always installed on a workstation, so the runner picks one: # $CXX if you set it, it wins # g++ on PATH native, fastest # a local container image podman + the first image that has g++ (see images # below), with this repo bind-mounted read-write into # /work -- no system packages installed for a test run set -uo pipefail here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" root="$(dirname "$here")" out="$here/build" if [[ "${1:-}" == "clean" ]]; then rm -rf "$out" echo "cleaned $out" exit 0 fi # Candidate images, tried in order, that already exist locally. Add your own here. images=( "localhost/ai-sandbox:ubuntu-9a08b1d500b0" "docker.io/library/gcc:14" ) suites=(test_keystate test_proto test_text test_mouse test_output test_ws_codec test_tls_pem test_tls_session test_conn) build_and_run() { local cxx="$1" local prefix=("$cxx") local flags=(-std=gnu++17 -O1 -g -Wall -Wextra -Wno-unused-function -Isrc -Itests) mkdir -p "$out" if [[ -n "${SANITIZE:-}" ]]; then flags+=(-fsanitize=address,undefined -fno-omit-frame-pointer) echo "sanitizers: on" fi local failed=0 suite for suite in "${suites[@]}"; do if ! "${prefix[@]}" "${flags[@]}" "tests/$suite.cpp" -o "$out/$suite" \ 2>"$out/$suite.log"; then echo "BUILD FAILED: $suite" cat "$out/$suite.log" failed=1 continue fi [[ -s "$out/$suite.log" ]] && cat "$out/$suite.log" "$out/$suite" || failed=1 done # The Python side (the CLI client's WebSocket codec, and its key table against # the one embedded in the controller page) needs only stdlib. if command -v python3 >/dev/null 2>&1; then python3 tests/test_relayctl.py || failed=1 else echo "python3 not found: skipped tests/test_relayctl.py" fi return $failed } # The controller page is a C++ raw string, so no compiler looks at its JavaScript and # a typo there fails in somebody's browser instead of in a build. Node runs it -- on # the host, deliberately, because the container that supplies g++ need not have node. run_page_suite() { if command -v node >/dev/null 2>&1; then node "$here/test_page.js" || return 1 else echo "node not found: skipped tests/test_page.js" fi return 0 } if [[ "${1:-}" == "--inside" ]]; then cd "$root" || exit 2 # The container has g++ but not necessarily node; the page suite skips itself. build_and_run "${CXX:-g++}" rc=$? if [[ -z "${KR_QUIET_RESULT:-}" ]]; then if ((rc)); then echo "RESULT: FAILED"; else echo "RESULT: all suites passed"; fi fi exit $rc fi if [[ -n "${CXX:-}" ]]; then echo "compiler: \$CXX = $CXX" (cd "$root" && build_and_run "$CXX") rc=$? (cd "$root" && run_page_suite) || rc=1 if ((rc)); then echo "RESULT: FAILED"; else echo "RESULT: all suites passed"; fi exit $rc fi if command -v g++ >/dev/null 2>&1; then echo "compiler: $(command -v g++)" (cd "$root" && build_and_run g++) rc=$? (cd "$root" && run_page_suite) || rc=1 if ((rc)); then echo "RESULT: FAILED"; else echo "RESULT: all suites passed"; fi exit $rc fi if command -v podman >/dev/null 2>&1; then for img in "${images[@]}"; do if ! timeout 60 podman image exists "$img" 2>/dev/null; then continue fi if ! timeout 60 podman run --rm --entrypoint sh "$img" -c 'command -v g++' \ >/dev/null 2>&1; then continue fi echo "compiler: g++ inside $img (repo mounted at /work)" # :z relabels for SELinux; the build output lands in tests/build as usual. # KR_QUIET_RESULT: the host prints the one summary, after its own page suite. podman run --rm --entrypoint bash -v "$root:/work:z" \ -e "SANITIZE=${SANITIZE:-}" -e KR_QUIET_RESULT=1 \ "$img" /work/tests/run_tests.sh --inside rc=$? (cd "$root" && run_page_suite) || rc=1 if ((rc)); then echo "RESULT: FAILED"; exit 1; fi echo "RESULT: all suites passed" exit 0 done fi cat >&2 <