proto: freeze the wire contract at 1.0.0
Schemas for the whole v1 surface: 38 methods, 9 events, 25 named types and the
JSON-RPC envelope, with x-privileged / x-transports / x-deadlineMs / x-errors
annotations that both generators emit as data rather than prose.
Four generators over one IR (contracts/codegen/schema_ir.py), so the C++ structs,
the TypeScript types and the OpenRPC document cannot disagree about what the
contract says:
gen_cpp.py -> core/generated/velox_proto.{hpp,cpp}
gen_ts.py -> extension/src/shared/protocol/
gen_openrpc.py -> contracts/openrpc.json
gen_cpp_conformance.py -> tests/conformance/cpp/fixture_dispatcher.hpp
Inbound parsing never throws: parse<T>() returns std::expected<T, ParseError> and
nlohmann's throwing ADL from_json is deliberately not emitted. Schema constraints
(minimum, maxLength, pattern, ...) become real runtime checks in both languages —
the daemon does not trust the extension and the extension does not trust the
daemon.
59 golden fixtures: a success case per method, 12 error cases, 9 events. Replayed
by tests/conformance/ against both the generated C++ and a live server over both
transports. tools/mockd serves the same fixtures with unhappy-path flags so the
GUI and EXT lanes never wait for veloxd.
run.sh also proves capture.offer fails open: with a daemon answering slower than
750 ms the client gives up and lets Firefox take the download.
core/generated/ is libveloxproto, a separate target from libveloxcore, which
still never sees JSON — see docs/adr/0009.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_012fgjnqFCS5h5L7gZTZo3rV
This commit is contained in:
Executable
+111
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# The conformance suite. This is the command CI runs on every lane's PR.
|
||||
#
|
||||
# ./tests/conformance/run.sh static + C++ + TS against a mockd it starts
|
||||
# ./tests/conformance/run.sh --uds PATH --ws-port N against an already-running daemon
|
||||
#
|
||||
# Three runners, one set of fixtures:
|
||||
# 1. check_contract.py schemas, fixtures and committed generated code agree
|
||||
# 2. cpp/ the generated C++ parses, serialises and dispatches every fixture
|
||||
# 3. ts/replay.ts a live server answers every fixture over both transports
|
||||
#
|
||||
# Plus one scenario that cannot be shown against a healthy server: with the daemon
|
||||
# answering slower than capture.offer's 750 ms deadline, the client must give up and let
|
||||
# Firefox take the download. That is the fail-open guarantee, and it is checked here.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
HERE="$REPO/tests/conformance"
|
||||
WORK="$(mktemp -d)"
|
||||
EXTERNAL_UDS=""
|
||||
EXTERNAL_WS=""
|
||||
MOCKD_PID=""
|
||||
SLOW_PID=""
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--uds) EXTERNAL_UDS="$2"; shift 2 ;;
|
||||
--ws-port) EXTERNAL_WS="$2"; shift 2 ;;
|
||||
-h|--help) sed -n '2,20p' "$0"; exit 0 ;;
|
||||
*) echo "run.sh: unknown option $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Kill the server and anything it spawned. `kill $!` alone would only reap the subshell
|
||||
# wrapper and leave the node process holding the port, which then breaks the next run.
|
||||
stop() {
|
||||
local pid="$1"
|
||||
[ -n "$pid" ] || return 0
|
||||
pkill -P "$pid" 2>/dev/null || true
|
||||
kill "$pid" 2>/dev/null || true
|
||||
wait "$pid" 2>/dev/null || true
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
stop "$MOCKD_PID"
|
||||
stop "$SLOW_PID"
|
||||
rm -rf "$WORK"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
step() { printf '\n=== %s ===\n' "$1"; }
|
||||
|
||||
# ---------------------------------------------------------------- 1. static
|
||||
step "static conformance (schemas, fixtures, generated code)"
|
||||
python3 "$HERE/check_contract.py"
|
||||
|
||||
# ------------------------------------------------------------------- 2. C++
|
||||
step "generated C++ (parse, serialise, dispatch)"
|
||||
CXX="${CXX:-g++}"
|
||||
"$CXX" -std=c++23 -Wall -Wextra -Wpedantic -Werror \
|
||||
-I"$REPO/core/generated" -I"$HERE/cpp" \
|
||||
"$HERE/cpp/conformance_main.cpp" "$REPO/core/generated/velox_proto.cpp" \
|
||||
-o "$WORK/conformance_cpp"
|
||||
"$WORK/conformance_cpp" "$REPO"
|
||||
|
||||
# -------------------------------------------------------------------- 3. TS
|
||||
step "generated TypeScript against a live server"
|
||||
if [ -z "$EXTERNAL_UDS" ] && [ -z "$EXTERNAL_WS" ]; then
|
||||
( cd "$REPO/tools/mockd" && npm install --silent --no-audit --no-fund )
|
||||
UDS="$WORK/velox.sock"
|
||||
WS_PORT=52080
|
||||
( cd "$REPO/tools/mockd" && exec ./node_modules/.bin/tsx src/index.ts \
|
||||
--uds "$UDS" --ws-port "$WS_PORT" --allowed-root "$WORK" ) >"$WORK/mockd.log" 2>&1 &
|
||||
MOCKD_PID=$!
|
||||
# Wait for the socket rather than sleeping a guessed amount.
|
||||
for _ in $(seq 1 50); do
|
||||
[ -S "$UDS" ] && node -e "require('net').connect('$UDS').on('connect',function(){this.end();process.exit(0)}).on('error',()=>process.exit(1))" 2>/dev/null && break
|
||||
sleep 0.2
|
||||
done
|
||||
node -e "require('net').connect('$UDS').on('connect',function(){this.end();process.exit(0)}).on('error',()=>process.exit(1))" 2>/dev/null \
|
||||
|| { echo "mockd did not start:"; cat "$WORK/mockd.log"; exit 1; }
|
||||
else
|
||||
UDS="$EXTERNAL_UDS"
|
||||
WS_PORT="$EXTERNAL_WS"
|
||||
fi
|
||||
|
||||
( cd "$HERE/ts" && npm install --silent --no-audit --no-fund )
|
||||
|
||||
TS_ARGS=()
|
||||
[ -n "$UDS" ] && TS_ARGS+=(--uds "$UDS")
|
||||
[ -n "$WS_PORT" ] && TS_ARGS+=(--ws-port "$WS_PORT")
|
||||
( cd "$HERE/ts" && ./node_modules/.bin/tsx replay.ts "${TS_ARGS[@]}" )
|
||||
|
||||
# ------------------------------------------------- 4. capture fails open
|
||||
step "capture.offer fails open when the daemon is too slow"
|
||||
if [ -z "$EXTERNAL_UDS" ]; then
|
||||
SLOW_UDS="$WORK/slow.sock"
|
||||
( cd "$REPO/tools/mockd" && exec ./node_modules/.bin/tsx src/index.ts \
|
||||
--uds "$SLOW_UDS" --no-ws --slow 2000 ) >"$WORK/slow.log" 2>&1 &
|
||||
SLOW_PID=$!
|
||||
for _ in $(seq 1 50); do [ -S "$SLOW_UDS" ] && break; sleep 0.2; done
|
||||
[ -S "$SLOW_UDS" ] || { echo "slow mockd did not start:"; cat "$WORK/slow.log"; exit 1; }
|
||||
( cd "$HERE/ts" && ./node_modules/.bin/tsx replay.ts --uds "$SLOW_UDS" \
|
||||
--only capture.offer.timeout --include-requires )
|
||||
else
|
||||
echo "skipped: needs a deliberately slow server, which run.sh only arranges for mockd"
|
||||
fi
|
||||
|
||||
printf '\n=== conformance: all runners passed ===\n'
|
||||
Reference in New Issue
Block a user