Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .codacy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
# - services/ws-modules/wasi-{comm1,data1}/src/coverage.rs: each guest's minicov coverage dump, whose unsafe
# Codacy flags for audit and cannot suppress per line. Isolated into these one-function files so the exclude
# stays minimal; rationale in each file's header. The rest of each guest stays analyzed.
# - services/ws-test-server/src/bin/cov-server.rs: the wasm-agent-cov launcher reads its marker-file path from
# argv (args_os); Codacy's Rust security rule flags args_os-into-a-file-operation and cannot suppress per line.
# A false positive -- the path comes from the trusted mise task -- and taking it as an argument beats hardcoding
# one. Already this one minimal file; rationale lives in its header. Still covered by clippy + DeepSource Rust.
# - utilities/wasm-cov-wrapper/src/main.rs: a RUSTC_WORKSPACE_WRAPPER that reads its own argv (args_os) and forwards
# it to rustc; Codacy's Rust security rule flags args_os-into-a-subprocess as a command-injection shape and cannot
# suppress per line. It is a false positive -- the args are cargo's own trusted rustc invocation -- and forwarding
Expand All @@ -39,6 +43,7 @@ exclude_paths:
- "services/ws-modules/dotnet-data1/Program.cs"
- "services/ws-modules/wasi-comm1/src/coverage.rs"
- "services/ws-modules/wasi-data1/src/coverage.rs"
- "services/ws-test-server/src/bin/cov-server.rs"
- "services/ws-web-runner/mingw-shim/msvc_crt_alloc.c"
- "services/ws-web-runner/mingw-shim/msvc_crt_locale_cache.c"
- "utilities/wasm-cov-wrapper/src/main.rs"
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/coverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ jobs:
timeout-minutes: 10
run: mise run wasm-cov

# The browser ws-wasm-agent has no native tests, so it is invisible to the cargo-llvm-cov run above.
# This runs its wasm-bindgen tests in the runner's headless Chrome against an in-process ws-server and folds
# the agent lib's coverage into the same lcov.info. CHROMEWEBDRIVER is the GitHub image's chromedriver that
# matches its preinstalled Chrome; the task falls back to the mise-pinned http:chromedriver when it is unset.
- name: Merge ws-wasm-agent coverage into lcov.info
timeout-minutes: 20
run: |
export CHROMEDRIVER="${CHROMEWEBDRIVER:+$CHROMEWEBDRIVER/chromedriver}"
mise run wasm-agent-cov

- name: Collect Python coverage
timeout-minutes: 15
env:
Expand Down
110 changes: 110 additions & 0 deletions .mise/config.coverage.toml
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,113 @@ shell = "bash -euo pipefail -c"
[tasks.pytest-cov.env]
COVERAGE_RCFILE = "{{ config_root }}/config/coverage.toml"
UV_PYTHON = "{% if os() == 'windows' %}{{ vars.py3_win }}{% else %}{{ vars.py3_unix }}{% endif %}"

[tasks.wasm-agent-cov]
depends = ["build-wasm-cov-wrapper"]
description = "Coverage for the browser ws-wasm-agent: run its wasm-bindgen tests headless, emit lcov into lcov.info"
# The agent is a browser wasm client with no native tests, so its lib is invisible to the cargo-llvm-cov run.
# This drives its wasm-bindgen tests (tests/client.rs) in a real headless Chrome and folds the result into the
# same lcov.info the other coverage tasks feed. Two pieces make it work:
# 1. A live backend. tests/client.rs covers the offline paths on its own, but the connected paths (onopen,
# the connect-ack dispatch, the online send/flush/keepalive) need a server, so we start the in-process
# et-ws-test-server (its cov-server bin) on the fixed port the tests dial, wait for its readiness marker,
# and kill it after. It is built without the coverage wrapper, so it stays uninstrumented.
# 2. Instrumentation. wasm-bindgen-test 0.3 has built-in coverage under --cfg wasm_bindgen_unstable_test_coverage
# (it pulls minicov and exports __wbgtest_cov_dump, which the test runner POSTs back and writes to
# LLVM_PROFILE_FILE). The workspace wrapper adds -Cinstrument-coverage --emit=llvm-ir to our crates only,
# and --features coverage links minicov's profiler runtime the same way the browser modules do.
# The .profraw -> lcov conversion mirrors wasm-cov (gut every .ll body to `unreachable`, keep the covmap, llc to a
# fixed x86_64 ELF object, then llvm-cov), with one addition: the test binary links several workspace crates and
# the agent's generic message helpers monomorphize into the test crate's object, so we compile every workspace
# .ll (not one) and keep only the agent lib's records. Gutting can leave an attribute group empty, which llc
# rejects, so emptied groups get a benign `nounwind`.
run = """
covdir=target/agent-cov
coreutils mkdir -p "$covdir"
coreutils rm -f "$covdir"/*.profraw "$covdir"/*.o "$covdir"/*.g.ll "$covdir/server-ready"
# Force the agent crate (and its wasm test binary) to recompile so this run emits a fresh instrumented .ll.
# Its covmap must match the .profraw we are about to capture, but cargo only re-emits `--emit=llvm-ir` when a
# crate actually compiles, so on a warm cache a stale or missing .ll would otherwise make llvm-cov drop the
# agent's functions (a bare `cargo test` cache hit yields only the generic helpers' covmap, which monomorphize
# into the test crate's object). Only the agent's .ll is needed; other workspace crates' .ll are dropped by the
# lib-only keep filter below whether fresh, stale, or absent.
rustup target add wasm32-unknown-unknown
cargo clean -p et-ws-wasm-agent --target wasm32-unknown-unknown

# Resolve the webdriver, honouring a CHROMEDRIVER already in the environment before the mise-pinned one.
# CI points CHROMEDRIVER at the runner's chromedriver (matched to its preinstalled Chrome); locally this falls
# back to the mise-managed http:chromedriver.
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
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Preserve the clean shutdown protocol on failure.

The EXIT trap directly kills the instrumented server, so failures before Line 224 prevent main() from returning and
can discard its native profile. Have the trap remove the readiness marker and reap the process, with kill only as a
bounded fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.mise/config.coverage.toml at line 208, Update the EXIT trap in the coverage
configuration to remove the readiness marker and reap the instrumented server
during shutdown, ensuring this cleanup runs even when setup fails before main
returns. Retain kill only as a bounded fallback after the normal
shutdown/reaping path, while preserving the existing failure cleanup behavior.

for _ in $(coreutils seq 1 100); do [ -s "$covdir/server-ready" ] && break; coreutils sleep 0.1; done
[ -s "$covdir/server-ready" ] || { echo "cov-server never became ready"; exit 1; }

# Export the instrumentation env for the coverage test build.
# Trailing-backslash line continuations are banned repo-wide, so each variable is set on its own line; no later
# step invokes cargo, so these staying set for the rest of the script is harmless.
runner="$(mise which wasm-bindgen-test-runner)"
export RUSTC_WORKSPACE_WRAPPER="{{ config_root }}/target/debug/int-wasm-cov-wrapper"
export RUSTFLAGS="--cfg wasm_bindgen_unstable_test_coverage"
export CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER="$runner"
export CHROMEDRIVER="$driver"
export LLVM_PROFILE_FILE="{{ config_root }}/$covdir/wasm-agent-%p.profraw"
cargo test -p et-ws-wasm-agent --features coverage --target wasm32-unknown-unknown --test client

# Remove the marker so cov-server returns from main and flushes its coverage, then reap it (a kill would drop it).
coreutils rm -f "$covdir/server-ready"
wait "$server_pid" 2>/dev/null || true
trap - EXIT

host="$(rustc +nightly -vV | goawk '/^host:/ { print $2 }')"
llbin="$(rustc +nightly --print sysroot)/lib/rustlib/$host/bin"
gut="$covdir/gut.awk"
coreutils cat > "$gut" <<'AWK'
/^target datalayout/ { next }
/^target triple/ { next }
/^define/ { print; print "start:"; print " unreachable"; print "}"; skip = 1; next }
skip && /^}/ { skip = 0; next }
skip { next }
{
gsub(/"target-cpu"="[^"]*"/, "")
gsub(/"target-features"="[^"]*"/, "")
if ($0 ~ /^attributes #/ && $0 ~ /\\{[[:space:]]*\\}/) sub(/\\{[[:space:]]*\\}/, "{ nounwind }")
print
}
AWK
"$llbin/llvm-profdata" merge -sparse -o "$covdir/agent.profdata" "$covdir"/wasm-agent-*.profraw
objs=()
for ll in target/wasm32-unknown-unknown/debug/deps/*.ll; do
name="$(coreutils basename "$ll" .ll)"
goawk -f "$gut" "$ll" > "$covdir/$name.g.ll"
"$llbin/llc" -filetype=obj -mtriple=x86_64-unknown-linux-gnu -o "$covdir/$name.o" "$covdir/$name.g.ll"
objs+=("-object" "$covdir/$name.o")
done
"$llbin/llvm-cov" export --format=lcov --instr-profile "$covdir/agent.profdata" "${objs[@]}" > "$covdir/all.lcov"
keep="$covdir/keep.awk"
coreutils cat > "$keep" <<'AWK'
{ buf = buf $0 ORS }
/^SF:/ { keep = index($0, want) > 0 }
/^end_of_record$/ { if (keep) printf "%s", buf; buf = ""; keep = 0 }
AWK
coreutils touch lcov.info
goawk -v want="ws-wasm-agent/src/" -f "$keep" "$covdir/all.lcov" >> lcov.info

# Fold cov-server's own native coverage in from the instrumented run above (its cov-server-*.profraw).
# The export object links its whole dep tree, so keep only the cov-server.rs launcher records.
csp="$covdir/cov-server"
"$llbin/llvm-profdata" merge -sparse -o "$csp.profdata" "$covdir"/cov-server-*.profraw
"$llbin/llvm-cov" export --format=lcov --instr-profile "$csp.profdata" -object target/debug/cov-server > "$csp.lcov"
goawk -v want="ws-test-server/src/bin/cov-server.rs" -f "$keep" "$csp.lcov" >> lcov.info

rpt="$covdir/report.txt"
"$llbin/llvm-cov" report --instr-profile "$covdir/agent.profdata" "${objs[@]}" > "$rpt" 2>/dev/null || true
rg "ws-wasm-agent/src/lib.rs|Filename|TOTAL" "$rpt" || true
"""
shell = "bash -euo pipefail -c"
35 changes: 24 additions & 11 deletions .mise/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,12 @@ cargo-binstall = "latest"
"cargo:cargo-expand" = { version = "latest", os = ["linux", "macos"] }
"cargo:open" = "latest"
"cargo:wasm-opt" = { version = "latest", os = ["linux", "macos"] }
"github:nextest-rs/nextest" = "latest"
taplo = "latest"
watchexec = "latest"
# Chromedriver disabled (commented out below).
# Only used by `test-ws-wasm-agent-chrome` / `ws-e2e-chrome`, neither of which run in the standard CI `test`
# flow. Disabling chromedriver lets us also drop the vfox backend (see `[settings] disable_backends` above)
# since chromedriver was the only [tools] entry that defaulted to vfox.
# "chromedriver" = { version = "146", os = ["linux", "macos"] }
cmake = "latest"
"conda:openssl" = "3"
conftest = "latest"
"github:nextest-rs/nextest" = "latest"
taplo = "latest"
watchexec = "latest"
# uutils coreutils: the Rust multicall binary, invoked as `coreutils <util>` in tasks.
# Pinned to 0.6.0 (NOT the latest 0.9.0) because uutils 0.7.0+ ships the Windows release as 85 per-utility
# .exes with no multicall coreutils.exe (see uutils/coreutils#11268). 0.6.0 keeps the multicall layout
Expand Down Expand Up @@ -243,8 +238,26 @@ url = "https://downloads.openobserve.ai/releases/openobserve/v0.70.3/openobserve
url = "https://downloads.openobserve.ai/releases/openobserve/v0.70.3/openobserve-v0.70.3-linux-amd64.tar.gz"
[tools."http:openobserve".platforms.macos-arm64]
url = "https://downloads.openobserve.ai/releases/openobserve/v0.70.3/openobserve-v0.70.3-darwin-arm64.tar.gz"
[tools."http:openobserve".platforms.macos-x64]
url = "https://downloads.openobserve.ai/releases/openobserve/v0.70.3/openobserve-v0.70.3-darwin-amd64.tar.gz"

# chromedriver from Google's Chrome for Testing CDN, for the headless-browser wasm-bindgen tests.
# The `os` list uses mise's os/arch compound syntax (mise >= 2026.4, jdx/mise#9088) to skip linux-arm64: Chrome
# for Testing publishes no linux-arm64 chromedriver, and a bare `linux` entry would hard-fail `mise install` on
# the linux-arm64 lanes ("No URL for platform linux-arm64"). `linux/x64` matches only that arch; bare `macos`
# matches both. Immutable per-version URLs mean no checksum -- the same direct-upstream posture as
# http:openobserve. mise auto-strips the single `chromedriver-<platform>/` archive dir, leaving the `chromedriver`
# binary at the install root. Consumers: test-ws-wasm-agent-chrome / ws-e2e-chrome and the wasm-agent-cov coverage
# task (which in CI drives Chrome via the runner's matched CHROMEWEBDRIVER instead). The major version must track
# the Chrome it drives (a >1-major skew makes chromedriver refuse the session); bump this pin when that moves.
[tools."http:chromedriver"]
bin = "chromedriver"
os = ["linux/x64", "macos"]
version = "150.0.7871.124"
[tools."http:chromedriver".platforms.linux-x64]
url = "https://storage.googleapis.com/chrome-for-testing-public/150.0.7871.124/linux64/chromedriver-linux64.zip"
[tools."http:chromedriver".platforms.macos-arm64]
url = "https://storage.googleapis.com/chrome-for-testing-public/150.0.7871.124/mac-arm64/chromedriver-mac-arm64.zip"
[tools."http:chromedriver".platforms.macos-x64]
url = "https://storage.googleapis.com/chrome-for-testing-public/150.0.7871.124/mac-x64/chromedriver-mac-x64.zip"

[tools."http:augeas"]
bin = "augtool"
Expand Down Expand Up @@ -1228,7 +1241,7 @@ description = "Run both the ws-server and ws-wasm-agent using Firefox"
[tasks.test-ws-wasm-agent-chrome]
description = "Run headless Chrome tests for the WebSocket WASM client"
dir = "services/ws-wasm-agent"
run = "env CHROMEDRIVER=\"$(mise where chromedriver)/bin/chromedriver\" wasm-pack test --headless --chrome"
run = "env CHROMEDRIVER=\"$(mise which chromedriver)\" wasm-pack test --headless --chrome"

[tasks.ws-e2e-chrome]
depends = ["test-ws-wasm-agent-chrome", "ws-server"]
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion config/conftest/policy/mise/mise.rego
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ allowed_os_scoped_tool := {
# action-validator (aqua) has no Windows build, so it is os-scoped off Windows.
# config.windows.toml installs cargo:action-validator there instead (cargo-quickinstall msvc prebuilt).
"action-validator",
"chromedriver",
"http:chromedriver",
"pipx",
"pipx:torch",
"npm:pnpm",
Expand Down
1 change: 1 addition & 0 deletions services/ws-test-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ et-modules-service.workspace = true
et-storage-service.workspace = true
et-test-helpers.workspace = true
et-ws-service.workspace = true
fs-err.workspace = true
futures-util.workspace = true
serde_json.workspace = true
tempfile.workspace = true
Expand Down
38 changes: 38 additions & 0 deletions services/ws-test-server/src/bin/cov-server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! Long-lived in-process ws-server for the `wasm-agent-cov` coverage task.
//!
//! Starts the same hub the integration tests use on a fixed port (the one the browser wasm-agent tests connect
//! to), writes a readiness marker to the file named by the first CLI argument once the server is accepting, then
//! stays up until the task deletes that marker -- its stop signal. Returning from `main` (rather than being
//! killed) lets the coverage-instrumented build flush its counters on a clean exit; a SIGKILL would drop them.
//!
//! Excluded from Codacy analysis (`.codacy.yaml`): Codacy's Rust security rule flags `args_os()` flowing into a
//! file operation as a path-traversal shape and cannot suppress it per line. That is a false positive here -- the
//! single argument is the marker path the trusted `wasm-agent-cov` mise task passes -- and taking that path as an
//! argument (rather than hardcoding one) is the point. The crate exposes this as its own minimal file so the path
//! exclude stays narrow; it is still covered by clippy and `DeepSource`'s Rust analyzer.

use std::error::Error;
use std::path::PathBuf;
use std::time::Duration;

use fs_err as fs;

/// Fixed port the browser wasm-agent tests dial (`ws://127.0.0.1:8080/ws`), matching the ws-server port the
/// existing `web.rs` end-to-end test and `ws-e2e-chrome` use.
const COV_SERVER_PORT: u16 = 8080;

fn main() -> Result<(), Box<dyn Error>> {
let ready_path = std::env::args_os()
.nth(1)
.map(PathBuf::from)
.ok_or("usage: cov-server <ready-file>")?;

let server = et_ws_test_server::start_on(COV_SERVER_PORT);
fs::write(&ready_path, server.ws_url.as_bytes())?;

// Stay up until the task removes the readiness marker, then fall through to a clean exit so coverage flushes.
while ready_path.exists() {
std::thread::sleep(Duration::from_millis(100));
}
Ok(())
}
11 changes: 9 additions & 2 deletions services/ws-test-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,18 @@ pub struct TestServer {
/// Serves modules from the default module paths (same as production).
#[must_use]
pub fn start() -> TestServer {
start_on(et_test_helpers::reserve_port())
}

/// Start an in-process ws-server bound to a specific `port` with a temporary storage directory.
///
/// Like [`start`], but for callers that must know the port ahead of time (e.g. a fixed-port launcher a separate
/// process connects to). Panics if the port is already in use.
#[must_use]
pub fn start_on(port: u16) -> TestServer {
let storage_dir = TempDir::new().unwrap();
let storage_path = storage_dir.path().to_path_buf();

let port = et_test_helpers::reserve_port();

let storage_config = StorageConfig::new(storage_path);
let modules_config = ModulesConfig::default();
let addr = format!("127.0.0.1:{port}");
Expand Down
6 changes: 6 additions & 0 deletions services/ws-wasm-agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,11 @@ web-sys = { workspace = true, features = [
wasm-bindgen-futures.workspace = true
wasm-bindgen-test.workspace = true

# Coverage instrumentation, off by default and enabled only by the `wasm-agent-cov` mise task's test build.
# Forwards to et-web/coverage, which links minicov's profiler runtime (`__llvm_profile_runtime`) that the
# instrumented workspace crates reference -- the same mechanism the browser ws-modules use for coverage.
[features]
coverage = ["et-web/coverage"]

[lints]
workspace = true
Loading
Loading