diff --git a/.mise/config.coverage.toml b/.mise/config.coverage.toml index c3c16aa..0af0867 100644 --- a/.mise/config.coverage.toml +++ b/.mise/config.coverage.toml @@ -52,24 +52,25 @@ eval "$(cargo llvm-cov show-env --sh)" # run/drive/worker paths reported 0% while its in-process initialize() was covered. Continuous mode (%c) memory-maps # the counter file so increments persist live and survive the kill. %c is incompatible with the merge-pool (%Nm) # specifier show-env emits, so use a per-process (%p) continuous file in the same profile dir -- `cargo llvm-cov -# report` globs every *.profraw there regardless of name. On Linux continuous mode additionally needs relocatable -# counters (`-Cllvm-args=-runtime-counter-relocation`). The wrapper flags env is CARGO_ENCODED_RUSTFLAGS format -# (ASCII unit separator 0x1f between flags), not space-separated -- a space append glues the llvm-arg onto the -# last existing flag and rustc rejects `--cfg=coverage_nightly -Cllvm-args=...` as one invalid --cfg value. +# report` globs every *.profraw there regardless of name. Branch coverage: `show-env` has no --branch flag (only +# the top-level `cargo llvm-cov` does), so the nightly `-Zcoverage-options=branch` is appended to the wrapper +# RUSTFLAGS directly; `report`'s lcov export emits the BRDA records on its own once the covmap carries branch +# regions. On Linux continuous mode additionally needs relocatable counters +# (`-Cllvm-args=-runtime-counter-relocation`). The wrapper flags env is CARGO_ENCODED_RUSTFLAGS format (ASCII +# unit separator 0x1f between flags), not space-separated -- a space append glues the llvm-arg onto the last +# existing flag and rustc rejects `--cfg=coverage_nightly -Cllvm-args=...` as one invalid --cfg value. goawk +# generates the separator byte at run time: bash dollar-quote hex is not a valid escape in this TOML basic +# string, and a TOML unicode escape renders as an invisible byte in the body, so it is constructed visibly. cov_dir="$(coreutils dirname "$LLVM_PROFILE_FILE")" export LLVM_PROFILE_FILE="$cov_dir/core-cont-%p%c.profraw" +us="$(goawk 'BEGIN { printf "%c", 31 }')" +flags="${__CARGO_LLVM_COV_RUSTC_WRAPPER_RUSTFLAGS:-}" +append_flag() { flags="${flags:+${flags}${us}}$1"; } +append_flag "-Zcoverage-options=branch" if [ "$(coreutils uname -s)" = "Linux" ]; then - reloc="-Cllvm-args=-runtime-counter-relocation" - flags="${__CARGO_LLVM_COV_RUSTC_WRAPPER_RUSTFLAGS:-}" - # Append the reloc flag with a TOML-injected unit separator (ASCII 31). - # Bash dollar-quote hex is not a valid TOML escape, so the separator has to be introduced by the - # TOML parser into the task body rather than written as a shell escape. - if [ -n "$flags" ]; then - export __CARGO_LLVM_COV_RUSTC_WRAPPER_RUSTFLAGS="${flags}\u001f${reloc}" - else - export __CARGO_LLVM_COV_RUSTC_WRAPPER_RUSTFLAGS="${reloc}" - fi + append_flag "-Cllvm-args=-runtime-counter-relocation" fi +export __CARGO_LLVM_COV_RUSTC_WRAPPER_RUSTFLAGS="$flags" # The --features flags compile each runner's coverage code, off by default so non-coverage builds omit it. # et-ws-web-runner/coverage adds the browser-module capture (shims + the __et_capture_coverage PUT); # et-ws-wasi-runner/coverage adds the guest `/cov` preopen. ET_TEST_COVERAGE (set by the coverage job) still @@ -206,9 +207,9 @@ cargo clean -p et-ws-wasm-agent --target wasm32-unknown-unknown driver="${CHROMEDRIVER:-$(mise which chromedriver)}" # Build + run cov-server itself instrumented so the launcher is not reported as untested. -# It uses native -Cinstrument-coverage and flushes its counters to this .profraw on the clean exit that removing -# the marker below triggers. -RUSTFLAGS="-Cinstrument-coverage" cargo build -q -p et-ws-test-server --bin cov-server +# It uses native -Cinstrument-coverage (with nightly branch coverage, matching the rest of the pipeline) and +# flushes its counters to this .profraw on the clean exit that removing the marker below triggers. +RUSTFLAGS="-Cinstrument-coverage -Zcoverage-options=branch" cargo build -q -p et-ws-test-server --bin cov-server LLVM_PROFILE_FILE="{{ config_root }}/$covdir/cov-server-%p.profraw" ./target/debug/cov-server "$covdir/server-ready" & server_pid=$! trap 'kill "$server_pid" 2>/dev/null || true' EXIT diff --git a/.mise/config.toml b/.mise/config.toml index b0bf7ae..c820daa 100644 --- a/.mise/config.toml +++ b/.mise/config.toml @@ -360,9 +360,13 @@ rpath_flag = "-C link-arg=-Wl,-rpath,{{ vars.conda_openssl }}/lib" # `python` pin), since the minor-version alias dir isn't created everywhere. pylib_flag = "-C link-arg=-Wl,-rpath,{{ vars.a_home }}/.local/share/mise/installs/python/3.13.14/lib" wasm_rustflags = "-C target-cpu=mvp -C target-feature=+mutable-globals,+sign-ext,+nontrapping-fptoint,+reference-types" +# Instrumentation flag pair (leaf var) shared by the wasm coverage builds. +# The nightly -Z flag adds branch regions to the covmap so the lcov exports carry BRDA records. `a_` prefix so +# mise's alphabetical [vars] render defines it before wasm_cov_rustflags uses it. +a_wasm_cov_instr = "-Cinstrument-coverage -Zcoverage-options=branch" # RUSTFLAGS for the wasm coverage builds: instrument + emit LLVM IR, no profiler runtime (minicov provides it). # Single codegen unit + LTO off so each crate emits one .ll the wasm-cov task can gut and feed to llc/llvm-cov. -wasm_cov_rustflags = "--emit=llvm-ir -Cinstrument-coverage -Ccodegen-units=1 -Clto=off -Zno-profiler-runtime" +wasm_cov_rustflags = "--emit=llvm-ir {{ vars.a_wasm_cov_instr }} -Ccodegen-units=1 -Clto=off -Zno-profiler-runtime" # Coverage fragments the wasm module build commands append unconditionally (empty unless ET_TEST_COVERAGE is set). # wasm_cov is an inline RUSTFLAGS env prefix; the nightly toolchain + conda clang come from the coverage [env]. # The *_feat vars turn on minicov: cargo `--features` for the WASI cdylibs, wasm-pack `-- --features` for browsers diff --git a/config/coverage.toml b/config/coverage.toml index f6c4b31..31118aa 100644 --- a/config/coverage.toml +++ b/config/coverage.toml @@ -7,6 +7,11 @@ # must be a real on-disk path so `coverage xml` can read source. [tool.coverage.run] +# Branch coverage must be enabled identically on every data source `coverage combine` merges. +# This rcfile covers the pytest runs (via COVERAGE_RCFILE); the Pyodide runs set branch=True on their Coverage() +# constructor directly because no COVERAGE_RCFILE env reaches the in-browser interpreter. A mismatch makes +# `coverage combine` refuse to merge arc data with line data for the same source files. +branch = true relative_files = true [tool.coverage.paths] diff --git a/services/ws-web-runner/src/shims/pycov.js b/services/ws-web-runner/src/shims/pycov.js index 7e9ee5d..0e3d873 100644 --- a/services/ws-web-runner/src/shims/pycov.js +++ b/services/ws-web-runner/src/shims/pycov.js @@ -15,7 +15,7 @@ if (globalThis.__ET_TEST_COVERAGE) { await pyodide.loadPackage("coverage"); pyodide.runPython(` import coverage as _et_cov_mod -_et_cov = _et_cov_mod.Coverage(data_file="/tmp/${pkg}.coverage", source=["${pkg}"]) +_et_cov = _et_cov_mod.Coverage(data_file="/tmp/${pkg}.coverage", source=["${pkg}"], branch=True) _et_cov.start() `); }, diff --git a/utilities/wasm-cov-wrapper/src/main.rs b/utilities/wasm-cov-wrapper/src/main.rs index 963e390..6a9bc8c 100644 --- a/utilities/wasm-cov-wrapper/src/main.rs +++ b/utilities/wasm-cov-wrapper/src/main.rs @@ -40,6 +40,7 @@ fn main() -> ExitCode { [ "--emit=llvm-ir", "-Cinstrument-coverage", + "-Zcoverage-options=branch", "-Ccodegen-units=1", "-Clto=off", "-Zno-profiler-runtime",