From 927ccc6cedb7b6fb08e23913f4ea56ab734240c2 Mon Sep 17 00:00:00 2001 From: Zac Gaetano Date: Sun, 3 May 2026 12:14:25 +0000 Subject: [PATCH 1/2] ci+test: forgejo workflow, browser WHEP player, TESTING.md (M4 part 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three artifacts that close out the easier half of the M4 milestone: 1. .forgejo/workflows/test.yml — CI on every push and PR. Three jobs: - lint-and-vet: go vet + go build (~30s) - test: go test -race -short ./... + a no-race coverage pass that uploads coverage.out as an artifact - webrtc-smoke: TestIntegration_FiveViewerFanout and the rest of the WebRTC subsystem tests in isolation, so a failure on the egress path stays readable in the log. Pinned to Go 1.24 to match go.mod. The forge has a forgejo-runner sibling container; this YAML uses GitHub Actions syntax which Forgejo Actions accepts unchanged. 2. test/whep-player.html — self-contained browser WHEP subscriber for manual smoke testing. RTCPeerConnection (recvonly V+A) + fetch() POST/DELETE/PATCH against /api/v3/whep/:id, ICE/PC state pills, inbound-bitrate sampling at 1 Hz, codec hint pulled from the answer SDP, JWT token field, ?url=&token= shareable query string. No external deps; works from file:// or any static host. 3. test/TESTING.md — short doc that ties together the in-process race tests, the browser player, and the existing Pion CLI helper at test/whep-client/. Notes the latency p95 gate as a follow-up. Latency gate (FFmpeg drawtext frame counter + decode-side pixel sampling, p95 < 300ms RTMP / < 200ms SRT) is queued for a separate PR — it's a several-hundred-line addition in its own right and shouldn't block CI from landing. Co-Authored-By: Claude Opus 4.7 --- .forgejo/workflows/test.yml | 100 ++++++++++ .gitignore | 2 + test/TESTING.md | 59 ++++++ test/whep-player.html | 354 ++++++++++++++++++++++++++++++++++++ 4 files changed, 515 insertions(+) create mode 100644 .forgejo/workflows/test.yml create mode 100644 test/TESTING.md create mode 100644 test/whep-player.html diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml new file mode 100644 index 0000000..928896f --- /dev/null +++ b/.forgejo/workflows/test.yml @@ -0,0 +1,100 @@ +# Forgejo Actions CI for Datarhei — Dragon Fork. +# +# Mirrors the upstream go-tests.yml shape (GitHub Actions syntax), +# but pinned to Go 1.24 to match go.mod and adds the M3 race-detector +# pass. The forgejo-runner picks this up automatically. +# +# Triggered on every push and pull request. Two jobs: +# - lint-and-vet: cheap, fast feedback (~30s) +# - test: full test suite with -race, ~3 minutes including +# the integration tests in app/webrtc that bind UDP +# sockets and run a real Pion handshake. + +name: ci + +on: + push: + branches: + - main + - 'm[0-9]*-*' + - 'fix/**' + pull_request: + +jobs: + lint-and-vet: + name: vet + build + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.24' + cache: true + + - name: go vet + run: go vet ./... + + - name: go build + run: go build ./... + + test: + name: race tests + runs-on: ubuntu-22.04 + needs: lint-and-vet + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.24' + cache: true + + # Integration tests need ephemeral UDP ports above 32768; the + # default sysctl on ubuntu runners covers this, so no extra + # setup is required. + + - name: go test -race -short + run: go test -race -short -count=1 ./... + env: + # The integration tests start Pion peers; tighten the timeout + # so a flaky network-bound test never sits the whole job. + GORACE: 'halt_on_error=1' + + - name: go test (coverage, no race) + # Race detector + coverage in one pass slows things meaningfully; + # do them separately. This step's purpose is the coverage.out + # artifact, not a second correctness signal. + run: go test -coverprofile=coverage.out -covermode=atomic -count=1 ./... + + - name: Upload coverage artifact + uses: actions/upload-artifact@v4 + if: success() || failure() + with: + name: coverage-go-${{ github.sha }} + path: coverage.out + if-no-files-found: warn + retention-days: 14 + + # --- WebRTC subsystem-only smoke --------------------------------- + # The 5-viewer fanout test catches the largest class of regressions + # for the egress path. Promoted to its own job so a failure on the + # WebRTC side reads cleanly in the actions log instead of being + # buried among ~80 packages of unrelated Core tests. + webrtc-smoke: + name: WebRTC smoke (5-viewer fanout) + runs-on: ubuntu-22.04 + needs: lint-and-vet + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.24' + cache: true + + - name: WebRTC integration tests (race) + run: | + go test -race -count=1 -v \ + -run 'TestIntegration_|TestSubsystem_TeardownHookFiresOnProcessStop|TestHandler_' \ + ./app/webrtc/... ./core/webrtc/... diff --git a/.gitignore b/.gitignore index d95f155..2cc4e41 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,8 @@ !/test/publish.sh !/test/whep-client/ !/test/whep-client/** +!/test/whep-player.html +!/test/TESTING.md *.ts *.ts.tmp diff --git a/test/TESTING.md b/test/TESTING.md new file mode 100644 index 0000000..f161750 --- /dev/null +++ b/test/TESTING.md @@ -0,0 +1,59 @@ +# Testing the WebRTC egress path + +## In-process (CI) + +```sh +go test -race -count=1 ./app/webrtc/... ./core/webrtc/... +``` + +The integration tests under `app/webrtc/` allocate UDP ports on +loopback, spin up an Echo handler, attach a Pion subscriber, and +spray synthetic RTP into the registered Source. `TestIntegration_FiveViewerFanout` +covers the 5-concurrent-viewer acceptance path from the M3 design. + +## Manual / browser + +`whep-player.html` is a self-contained WHEP subscriber a human can +point at any live deploy. Open it directly in a browser: + +``` +file:///path/to/datarhei-dragonfork-core/test/whep-player.html +``` + +…or copy it onto a static host (no server-side dependency). It accepts +the WHEP URL and an optional bearer token (the deploy uses Core's +JWT, so paste an `access_token` from `POST /api/login`). It POSTs an +SDP offer with a recvonly video + audio transceiver, applies the +answer, and renders the stream in `