Skip to content

test: add in-tree cross-language interop smoke test#1871

Merged
kixelated merged 3 commits into
mainfrom
claude/clever-brattain-91e178
Jun 22, 2026
Merged

test: add in-tree cross-language interop smoke test#1871
kixelated merged 3 commits into
mainfrom
claude/clever-brattain-91e178

Conversation

@kixelated

Copy link
Copy Markdown
Collaborator

Summary

An in-tree cross-language interop smoke test under test/smoke/ that builds every client from this checkout and runs a publish × subscribe matrix against a local relay. It proves the code in the tree interoperates across implementations before anything is published.

This is the in-tree companion to moq-dev/smoke: that repo installs each client from its public registry (crates.io, PyPI, npm, ...) to catch packaging breakage in a release; this one builds from source (cargo/bun/uv/cc/gst) to catch interop regressions in the current commit. No apt/brew/npm/PyPI, no distribution-mechanism matrix. It lives under test/, wired through the existing mod test, next to where test/justfile already points at the standalone repo.

What's here

  • test/smoke/smoke.sh — the orchestrator. Same matrix engine as the upstream repo (mark_broken isolation, kill_tree, negative control), but every client builds from the workspace: cargo build for the relay/cli/libmoq/moq-gst, just py build (maturin editable into .venv) for Python, the bun workspace for JS.
  • Clients — rust, python, browser (vite + headless Chromium), native JS (node + bun), C (libmoq), GStreamer (moqsrc). Rust/python/browser publish; everyone subscribes.
  • just test smoke / smoke-full / smoke-negative (in test/justfile).
  • .github/workflows/smoke.yml — nightly + on-demand + on PRs that touch test/smoke/, running just test smoke-full inside nix develop on ubuntu-latest.
  • The two JS clients join the bun workspace (package.json + bun.lock) as private members, so @moq/* resolve to this checkout's source. The browser page reuses the in-repo workletInline() vite plugin since @moq/publish is consumed as source.

Why a few things look the way they do

  • GStreamer needs no extra CI setup: the nix devShell already ships gstreamer + base/good/bad plugins, so cargo build -p moq-gst and gst-launch-1.0 run against one consistent GStreamer inside nix develop.
  • Native-JS subscribers are judged by the received N bytes marker, not the exit code. The @moq/web-transport NAPI addon can segfault during bun's exit teardown after a frame has arrived, which would turn a real success into a signal exit. The data path (frame bytes arrived) is exactly our success criterion, so the harness reads the marker; the negative control confirms it still reports failure when there's genuinely no data.

Scope notes

  • No public API or wire changes. Pure additive test infrastructure (the only non-test change is registering two private bun-workspace members), so this targets main.
  • FFI trio (go/swift/kotlin) intentionally not added. moq-ffi's interop is already proven by the python client (moq-rs wraps the moq_ffi bindings), and each binding is built + tested by just go/swift/kt check. Adding them to the smoke would need toolchains absent from the devShell (go+uniffi-bindgen-go, java+gradle) and a macOS runner for swift — low marginal value. Easy to add later via mark_broken's per-client isolation.

Test plan

Verified locally on macOS via nix develop:

  • just test smoke-full21/21 PASS (3 publishers × 7 subscribers), exit 0
  • just test smoke-negative (incl. js-native-bun) — every subscriber correctly fails (negative pass)
  • Native-JS segfault tolerance stress-tested 30/30 after the marker fix
  • shfmt / shellcheck / biome / remark / actionlint / just --fmt all clean
  • just js check/build/test cleanly skip the new workspace members; no build artifacts leak into git

🤖 Generated with Claude Code

(Written by Claude)

Build every client from this checkout and run a publish x subscriber
matrix against a local relay, so an interop regression shows up before
anything is published.

The standalone moq-dev/smoke repo installs each client from its public
registry to catch packaging breakage in a release; this is its in-tree
companion, building from source (cargo/bun/uv/cc/gst) with no
distribution-mechanism matrix. It lives under test/, wired through the
existing `mod test`.

- test/smoke/smoke.sh: orchestrator. Each client builds from the
  workspace: cargo for relay/cli/libmoq/moq-gst, `just py build` for the
  maturin editable install, the bun workspace for JS.
- Clients: rust + python + browser publish; everyone subscribes (incl.
  native node/bun JS, C via libmoq, GStreamer moqsrc).
- `just test smoke` / `smoke-full` / `smoke-negative`; a nightly +
  dispatch + PR-on-harness-change workflow runs it inside `nix develop`.
- The two JS clients join the bun workspace so @moq/* resolve to source.
- Native-JS subscribers are judged by the "received N bytes" marker, not
  the exit code: the @moq/web-transport NAPI addon can segfault during
  bun's exit teardown after a frame has arrived, which would otherwise
  turn a real success into a signal exit.

Verified on macOS: full matrix 21/21 green, negative control passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 42f177f8-b4a3-43e9-a99d-f9819a65cc90

📥 Commits

Reviewing files that changed from the base of the PR and between c59e651 and 6a20125.

📒 Files selected for processing (3)
  • test/smoke/clients/js-native/subscribe.ts
  • test/smoke/clients/js/driver.ts
  • test/smoke/smoke.sh
🚧 Files skipped from review as they are similar to previous changes (3)
  • test/smoke/clients/js-native/subscribe.ts
  • test/smoke/clients/js/driver.ts
  • test/smoke/smoke.sh

Walkthrough

This PR introduces a complete in-tree cross-language MOQ interop smoke test framework. A Bash harness (test/smoke/smoke.sh) builds and orchestrates publisher/subscriber pairs across Rust, Python, C, GStreamer, browser JS (Vite + Playwright Chromium), and native JS (Node/Bun with a WebTransport polyfill) clients. A local relay configuration is generated per run. Three just targets (smoke, smoke-full, smoke-negative) expose the harness for local and CI use. Two new npm workspaces are registered in package.json. A GitHub Actions workflow runs the full matrix nightly, on demand, and for PRs touching test/smoke/** or harness prerequisites.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.93% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'test: add in-tree cross-language interop smoke test' accurately and concisely summarizes the main change: adding a comprehensive smoke test infrastructure for cross-language interoperability.
Description check ✅ Passed The description thoroughly relates to the changeset, explaining the purpose, structure, design decisions, and scope of the new in-tree smoke test infrastructure including clients, orchestrator, CI workflow, and testing approach.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/clever-brattain-91e178

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (2)
.github/workflows/smoke.yml (1)

15-17: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Broaden PR path filters to include smoke wiring contracts.

The smoke job depends on test/justfile targets and root package.json workspace entries, but PR triggers currently exclude both. Adding them improves PR-time regression detection.

Proposed update
   pull_request:
     paths:
       - "test/smoke/**"
+      - "test/justfile"
+      - "package.json"
       - ".github/workflows/smoke.yml"
🤖 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 @.github/workflows/smoke.yml around lines 15 - 17, The paths filter in the
smoke workflow currently only includes test/smoke/** and
.github/workflows/smoke.yml, but the smoke job also depends on test/justfile
targets and root package.json workspace entries. Add both "test/justfile" and
"package.json" to the paths list alongside the existing entries to ensure the
smoke job is triggered on PRs that modify these dependencies, enabling better
regression detection at PR-time.
test/smoke/clients/c/subscribe.c (1)

64-64: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider extracting the latency constant.

The magic number 1000 here matches MAX_LATENCY_MS in the Python client. A named constant would improve clarity and keep cross-language test parameters in sync.

+#define MAX_LATENCY_MS 1000
+
 // ... in on_catalog:
-            int32_t track = moq_consume_video_ordered((uint32_t)catalog, 0, 1000, on_frame, ud);
+            int32_t track = moq_consume_video_ordered((uint32_t)catalog, 0, MAX_LATENCY_MS, on_frame, ud);
🤖 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 `@test/smoke/clients/c/subscribe.c` at line 64, Extract the magic number 1000
from the moq_consume_video_ordered function call into a named constant called
MAX_LATENCY_MS to improve code clarity and maintain consistency with the Python
client's latency parameter. Define this constant at the top of the file and
replace the hardcoded 1000 with a reference to this named constant.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@test/smoke/clients/js-native/subscribe.ts`:
- Around line 35-39: The timeout value parsed on line 35 is not validated after
being calculated from Number.parseFloat. Add validation after the timeoutMs
assignment to check that the value is finite and positive using
Number.isFinite() and a comparison check. If validation fails, log an error
message indicating the invalid timeout value and call process.exit(2) to match
the existing error handling pattern for the role, url, and broadcast parameters
validated in the subsequent if statement.
- Around line 1-13: Add a module-level JSDoc documentation block at the very top
of the subscribe.ts file before any imports. Convert the existing plain comments
that describe the file's purpose and usage into a proper JSDoc block using the
/** ... `@module` */ format, ensuring it comes before the import statement for
parseArgs from "node:util". This block should document the module's purpose as
an entrypoint and describe its functionality.

In `@test/smoke/clients/js/driver.ts`:
- Line 27: The timeoutMs assignment accepts non-finite values from
Number.parseFloat without validation, which can cause hangs or unexpected
failures. After parsing the timeout value with Number.parseFloat, validate that
the resulting number is finite using Number.isFinite before multiplying by 1000.
If the parsed value is not a valid finite number, throw an error with a clear
message indicating invalid timeout input to provide appropriate usage feedback.
- Around line 1-7: The driver.ts file currently has plain-text comments at the
top instead of a module-level JSDoc block. Convert the existing top-of-file
comments (the lines explaining the script's purpose and describing how to drive
Chromium against the vite-built page, including the usage examples) into a JSDoc
documentation block using the /** ... `@module` */ format. Preserve all the
existing comment content but wrap it in proper JSDoc syntax with the `@module` tag
at the beginning, making this an official module-level documentation block as
required for entrypoint files.

In `@test/smoke/clients/js/src/main.ts`:
- Around line 1-2: Replace the plain comment lines at the top of the main.ts
entrypoint file with a proper JSDoc documentation block that includes the
`@module` tag. Convert the existing comments into a /** ... `@module` */ block
format to comply with the documentation guidelines for JS entrypoints. This
ensures the module has proper documentation that identifies it as an entrypoint
module.

In `@test/smoke/clients/js/vite.config.ts`:
- Around line 1-12: Add JSDoc documentation to this vite configuration file.
First, add a module-level documentation block at the very top of the file
(before any imports) using the `@module` tag to document the purpose of this
file. Then, add a doc comment directly above the `export default
defineConfig(...)` statement to document the exported configuration object.
Ensure both doc comments follow the project's JSDoc conventions and clearly
describe their respective purposes.

In `@test/smoke/README.md`:
- Around line 74-82: The fenced code block starting with smoke.sh is missing a
language tag, which violates markdownlint rule MD040. Add the language tag
"text" to the opening triple backticks of this code block by changing ``` to
```text to specify that it is plain text content.

In `@test/smoke/smoke.sh`:
- Around line 60-63: Add numeric validation for the TIMEOUT and SMOKE_PORT
variables immediately after they are assigned from command-line arguments (the
--timeout and --port options). For each variable, check if the assigned value
contains only digits and is within a valid range, and if validation fails,
output a clear error message and exit the script. This prevents unclear errors
later when these invalid values are passed to the timeout command or used in sed
replacements.

---

Nitpick comments:
In @.github/workflows/smoke.yml:
- Around line 15-17: The paths filter in the smoke workflow currently only
includes test/smoke/** and .github/workflows/smoke.yml, but the smoke job also
depends on test/justfile targets and root package.json workspace entries. Add
both "test/justfile" and "package.json" to the paths list alongside the existing
entries to ensure the smoke job is triggered on PRs that modify these
dependencies, enabling better regression detection at PR-time.

In `@test/smoke/clients/c/subscribe.c`:
- Line 64: Extract the magic number 1000 from the moq_consume_video_ordered
function call into a named constant called MAX_LATENCY_MS to improve code
clarity and maintain consistency with the Python client's latency parameter.
Define this constant at the top of the file and replace the hardcoded 1000 with
a reference to this named constant.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aa4e332d-bf5b-44a6-a6a0-bf0f0268f6e3

📥 Commits

Reviewing files that changed from the base of the PR and between 4c67357 and a2c789d.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • .github/workflows/smoke.yml
  • package.json
  • test/justfile
  • test/smoke/README.md
  • test/smoke/clients/c/subscribe.c
  • test/smoke/clients/js-native/package.json
  • test/smoke/clients/js-native/subscribe.ts
  • test/smoke/clients/js/driver.ts
  • test/smoke/clients/js/index.html
  • test/smoke/clients/js/package.json
  • test/smoke/clients/js/src/main.ts
  • test/smoke/clients/js/src/setup.ts
  • test/smoke/clients/js/vite.config.ts
  • test/smoke/clients/python/smoke.py
  • test/smoke/smoke.sh
  • test/smoke/smoke.toml

Comment thread test/smoke/clients/js-native/subscribe.ts Outdated
Comment thread test/smoke/clients/js-native/subscribe.ts
Comment thread test/smoke/clients/js/driver.ts Outdated
Comment thread test/smoke/clients/js/driver.ts Outdated
Comment thread test/smoke/clients/js/src/main.ts Outdated
Comment thread test/smoke/clients/js/vite.config.ts
Comment thread test/smoke/README.md Outdated
Comment thread test/smoke/smoke.sh
kixelated and others added 2 commits June 22, 2026 11:32
- Add @module doc blocks to the JS entrypoints (driver, subscribe, main,
  vite.config) per the js/ entrypoint convention.
- Trigger the smoke workflow on test/justfile + package.json changes too,
  since the job depends on the recipe and the workspace membership.
- Tag the README layout code block as `text`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reject a non-finite/non-positive --timeout in the JS clients and a
non-numeric --timeout / SMOKE_PORT in smoke.sh, so a fat-fingered value
fails with a clear usage error instead of silent degenerate behavior (an
instant-or-never timeout, a cryptic bind failure). The clients are
documented for standalone debugging where these are hand-typed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kixelated kixelated enabled auto-merge (squash) June 22, 2026 18:41
@kixelated kixelated merged commit 96203f3 into main Jun 22, 2026
2 checks passed
@kixelated kixelated deleted the claude/clever-brattain-91e178 branch June 22, 2026 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant