Skip to content

test(moq-net): cover the announce loop's demand/linger state machine - #2429

Merged
kixelated merged 2 commits into
mainfrom
claude/lite-announce-linger-tests
Jul 21, 2026
Merged

test(moq-net): cover the announce loop's demand/linger state machine#2429
kixelated merged 2 commits into
mainfrom
claude/lite-announce-linger-tests

Conversation

@kixelated

@kixelated kixelated commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

What

Adds direct regression coverage for the demand/linger state machine in run_announce (rs/moq-net/src/lite/publisher.rs), introduced by #2424 and now on main. Previously only the lower-level pieces were tested (broadcast::Demand edges, the origin reselect gate, the RouteCost codec); the announce loop's hysteresis itself (Op::Idle, Op::Linger, COST_LINGER, the fused linger sleep, idle_at cleared on Op::Route) had none. Flagged by CodeRabbit on #2424.

Tests only, no behavior change, so this targets main.

How

A minimal in-memory harness: run_announce runs against a sink session whose send stream captures bytes (decoded back into AnnounceOk / AnnounceBroadcast messages via a cursor, so each step asserts exactly the wire messages it caused) and whose recv stream stays pending forever. Tokio time is paused, so the 5s linger is deterministic; under start_paused the 1ms settle() is a true quiescence barrier, since auto-advance only fires once the runtime is idle.

Six tests:

  • drain_defers_the_cold_restore: draining demand while zero is advertised sends nothing, and stays quiet inside the window.
  • demand_return_cancels_the_restore: demand returning cancels the restore, and the next drain starts a fresh window rather than inheriting the old deadline.
  • linger_expiry_restores_the_cold_cost: an expired linger sends exactly one ANNOUNCE_RESTART with the cold cost, then settles.
  • route_change_supersedes_the_linger: a route change mid-linger restarts with the new chain and clears the pending restore.
  • staggered_lingers_restore_independently: two broadcasts with staggered drains each restore on their own deadline.
  • demand_reprices_warm_immediately: the mirror edge, a viewer arriving on a cold advertisement re-prices to zero with no hysteresis.

Verification

Every test was mutation-checked so none passes for the wrong reason:

Mutation Caught by
COST_LINGER = 0 drain / cancel / route-change
deadline scan .min().max() staggered (only)
cancel arm stops clearing idle_at cancel (only)
outgoing_cost ignores demand livelocks the loop, caught as a paused-time hang

Two of these came out of reviewing the first pass, which had real holes worth calling out:

  • The cancellation test didn't test cancellation. Asserting only silence can't distinguish a cleared deadline from one that fires and re-prices to the same zero cost, taking the unchanged-route arm without writing — both are quiet. It now drains a second time inside the window, so an uncancelled deadline sends the restart four seconds early. Confirmed: the old assertion passed under that mutation, the new one fails.
  • Two tests could pass on a panicked announce task, since a dead task also writes nothing and the trailing assert_quiet() would swallow it (tokio::spawn parks the panic in the handle). Harness::assert_idle now checks the JoinHandle alongside the wire.

Cross-package sync

Test-only change in rs/moq-net; no wire, API, or doc surface touched, so no other rows apply.

🤖 Generated with Claude Code

(written by Fable 5)

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

The lite-06 route-cost hysteresis in run_announce (defer the cold-cost
restore by COST_LINGER after demand drains, cancel it when demand
returns, supersede it on a route change) had no direct regression
coverage; only the lower-level pieces (broadcast::Demand edges, the
origin reselect gate, the RouteCost codec) were tested.

Add an in-memory harness that runs run_announce against a sink session
(writes captured for decoding, reads pending forever) and asserts the
exact wire messages under paused tokio time:

- draining demand while zero is advertised sends nothing immediately
- demand returning within the linger cancels the restore silently
- an expired linger sends exactly one ANNOUNCE_RESTART with the cold cost
- a route change mid-linger supersedes the pending restore
- a viewer arriving on a cold advertisement re-prices to zero immediately

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kixelated
kixelated force-pushed the claude/lite-announce-linger-tests branch from 4a4b9a8 to d83ec2b Compare July 21, 2026 03:06
@kixelated
kixelated changed the base branch from dev to main July 21, 2026 03:06
…ry deadlines

Review of the first pass found two ways the suite could pass for the
wrong reason:

- demand_return_cancels_the_restore asserted only silence, which a
  broken cancel also produces: an uncancelled linger fires while demand
  is back, recomputes the same zero cost, and hits the unchanged-route
  arm without writing. It now drains a second time inside the window, so
  a stale deadline sends the restart four seconds early.
- The trailing assert_quiet in the expiry and route-change tests would
  pass if the announce task had panicked, since a dead task writes
  nothing. Harness::assert_idle now checks the JoinHandle too.

Also adds staggered_lingers_restore_independently, covering the deadline
scan across two entries (a single lingering broadcast can't tell min from
max), and renames the terse const V to VERSION.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kixelated
kixelated enabled auto-merge (squash) July 21, 2026 03:22
@coderabbitai

coderabbitai Bot commented Jul 21, 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: c5f26c71-836b-4335-9375-80dcea743d9f

📥 Commits

Reviewing files that changed from the base of the PR and between c74e583 and 034b120.

📒 Files selected for processing (1)
  • rs/moq-net/src/lite/publisher.rs

Walkthrough

Adds a test module for the publisher announce loop using fake WebTransport sessions and streams that capture and decode emitted messages. A harness creates broadcasts with configurable viewer demand and runs the loop with paused Tokio time. Tests cover deferred and canceled cold-cost restoration, independent linger deadlines, one-shot expiry behavior, route-change supersession, and immediate repricing when demand arrives.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: new tests for the announce loop's demand/linger state machine.
Description check ✅ Passed The description directly matches the changeset and explains the new test-only coverage in detail.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/lite-announce-linger-tests

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.

@kixelated
kixelated merged commit 152c969 into main Jul 21, 2026
2 checks passed
@kixelated
kixelated deleted the claude/lite-announce-linger-tests branch July 21, 2026 03:32
This was referenced Jul 21, 2026
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