test(udp-server): [#2149] add focused UDP server tests - #2174
test(udp-server): [#2149] add focused UDP server tests#2174josecelano wants to merge 68 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #2174 +/- ##
===========================================
+ Coverage 84.66% 84.88% +0.21%
===========================================
Files 351 351
Lines 29657 29960 +303
Branches 29657 29960 +303
===========================================
+ Hits 25110 25431 +321
+ Misses 4194 4175 -19
- Partials 353 354 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Pull request overview
Adds deterministic, focused test coverage and supporting documentation for the udp-server package as part of #2149, including clarified request-buffer eviction policy and improved test-design guidance.
Changes:
- Added unit tests for request-buffer capacity/eviction/drop cleanup, bound-socket metadata invariants, handler parse-failure routing, and event/error classification adapters.
- Documented the request-buffer eviction policy via a package-local ADR and expanded issue-local coverage/performance evidence.
- Updated repository test-writing/review guidance to require prose-first AAA verification and clearer test-level coverage attribution.
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/udp-server/src/server/request_buffer.rs | Clarifies eviction-policy docs and adds deterministic unit tests for capacity, eviction, and drop cleanup. |
| packages/udp-server/src/server/bound_socket.rs | Adds unit tests for port-zero binding and URL/service-binding consistency. |
| packages/udp-server/src/handlers/mod.rs | Adds a focused dispatcher test asserting parse-error routing keeps transaction_id and returns no request kind. |
| packages/udp-server/src/event.rs | Adds unit tests for stable ErrorKind classification and request-kind metric/display mappings. |
| packages/udp-server/src/error.rs | Adds unit tests for sendable/unsendable parse-error metadata conversion and invalid-request wrapping. |
| packages/udp-server/docs/adrs/index.md | Introduces package-local ADR index for udp-server decisions. |
| packages/udp-server/docs/adrs/README.md | Documents scope/ownership of package-local ADRs. |
| packages/udp-server/docs/adrs/20260907152707_keep_oldest_first_udp_request_eviction.md | Records rationale/contract for bounded oldest-first request eviction. |
| docs/testing/refactoring-patterns/prose-first-arrange-act-assert-verification.md | Adds a documented test-refactoring pattern used by this work. |
| docs/testing/refactoring-patterns/README.md | Registers the new prose-first AAA refactoring pattern. |
| docs/templates/ISSUE.md | Strengthens test-producing workflow requirements (prose-first AAA + explicit design review). |
| docs/issues/open/2149-1347-add-focused-udp-server-package-tests/test-refactor-plans/request-buffer-tests.md | Records request-buffer test plan, decisions, and validation evidence. |
| docs/issues/open/2149-1347-add-focused-udp-server-package-tests/test-refactor-plans/handler-dispatch-tests.md | Records handler-dispatch test plan and its completion evidence. |
| docs/issues/open/2149-1347-add-focused-udp-server-package-tests/test-refactor-plans/event-tests.md | Records event classification/mapping test plan and completion evidence. |
| docs/issues/open/2149-1347-add-focused-udp-server-package-tests/test-refactor-plans/error-tests.md | Records parse-error adapter test plan and completion evidence. |
| docs/issues/open/2149-1347-add-focused-udp-server-package-tests/test-refactor-plans/bound-socket-tests.md | Records bound-socket test plan and completion evidence. |
| docs/issues/open/2149-1347-add-focused-udp-server-package-tests/test-refactor-plans/README.md | Introduces two-phase (cleanup then add tests) plan structure and links to plans. |
| docs/issues/open/2149-1347-add-focused-udp-server-package-tests/performance-evidence.md | Defines baseline performance measurement policy for hot-path changes. |
| docs/issues/open/2149-1347-add-focused-udp-server-package-tests/coverage-evidence.md | Adds test-level coverage attribution policy and interim per-file evidence. |
| docs/issues/open/2149-1347-add-focused-udp-server-package-tests/ISSUE.md | Updates scope, links, acceptance criteria, and progress log to reflect current increments. |
| docs/issues/open/1347-overhaul-packages-testing/EPIC.md | Tightens EPIC guidance on unit vs integration coverage attribution and boundary selection. |
| .vscode/settings.json | Disables format-on-save for Markdown in workspace settings. |
| .github/skills/dev/testing/write-unit-test/SKILL.md | Makes prose-first AAA verification mandatory and adds checklist item. |
| .github/skills/dev/task-reviews/review-task/SKILL.md | Requires reviewers to apply a concrete test-design checklist to changed tests. |
| .github/skills/dev/planning/create-issue/SKILL.md | Updates issue-planning guidance for test increments and prose-first AAA evidence. |
| .github/agents/task-reviewer.agent.md | Updates task-review agent instructions to enforce prose-first AAA evidence for changed tests. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
da2ce7
left a comment
There was a problem hiding this comment.
Reviewed 35294e1 (44 commits on develop 7abc30b). Gated on a dedicated build host at that exact head: cargo clippy --workspace --all-targets --all-features -- -D warnings clean (12.0s), cargo test -p torrust-tracker-udp-server --all-targets --all-features green (33.1s, 154 unit + 11 integration), cargo test --workspace --all-targets --all-features green (166.8s), doc tests green. Determinism is good: 180 repeated runs of the new tests (including 40 request-buffer runs, 20 of them under a 64-way CPU load, and 20 single-threaded) produced 0 failures.
The test design is thoughtful and the increments are readable. Two things need to change before this leaves draft.
Blocker — the branch does not pass the mandatory pre-commit gate. linter all exits 1 at this head, failing only on rustfmt, in two files this PR adds lines to. CI agrees: the Testing workflow's Unit (nightly) job fails at step Run Formatting-Checks (run 34354718522), which cancelled Unit (stable). I reproduced both diffs independently with the PATH-resolved nightly cargo-fmt --all -- --check (0.7s). Bisecting the series with rustfmt against rustfmt.toml (group_imports = "StdExternalCrate", imports_granularity = "Module") shows request_buffer.rs has been unformatted since 5fc5f916 — the 6th of 44 commits — and handlers/mod.rs since 2fecd514; develop itself is clean, so both were introduced here. Several review replies on this PR state that "the mandatory pre-commit gate passed", and the PR description lists linter all under Validation; that has not been true for most of the series. Worth checking why the local gate is reporting green — a cargo fmt that resolves through a $CARGO_HOME/bin shim can silently run a different rustfmt than the one CI uses. Both fixes are one-liners; inline comments below.
Major — the headline eviction test does not pin the policy the new ADR defines. it_should_evict_the_oldest_pending_task_when_the_buffer_is_full asserts that the oldest task was aborted and that the others were aborted after the buffer is dropped — but ActiveRequests::drop aborts everything it still holds, so "aborted during cleanup" is satisfied whether force_push evicted one task or fifty. I verified this rather than assuming it: mutating force_push so the eviction branch continues instead of breaks — i.e. aborting every still-active handle, destroying the oldest-first, exactly-one policy the ADR is being added to protect and killing up to 50 in-flight requests per admission — leaves the entire torrust-tracker-udp-server suite green, all three request-buffer tests included (worktree restored and re-verified clean afterwards). Since this PR adds an ADR specifically to record that this eviction shape is intentional, the accompanying test should be the thing that stops it from silently changing. Suggested fix: capture the retained tasks' liveness immediately after the Act and before dropping the buffer — assert they are still un-aborted at that point — so the test distinguishes "the oldest was evicted" from "the buffer was emptied".
Major — the branch no longer merges into develop. develop has advanced to f6b73e2 (PR #2178 merged). A merge of this head into that tip conflicts in .github/agents/task-reviewer.agent.md; .github/skills/dev/planning/create-issue/SKILL.md and .github/skills/dev/testing/write-unit-test/SKILL.md auto-merge but are now edited by both sides, so the combined result of the two rule sets is worth re-reading after the rebase rather than trusting the auto-merge.
Minor — the PR is wider than its title. Under a test(udp-server): title the diff also changes repository-wide process rules (.github/agents/task-reviewer.agent.md, three .github/skills/** files, docs/templates/ISSUE.md) and an editor setting (.vscode/settings.json). The individual commits are correctly typed (docs(agents), docs(issues), docs(testing)), so this is a PR-level scoping point: those rule changes affect every contributor and are easy to miss when reviewing something announced as UDP server tests. Splitting them out, or at least widening the title, would help. Relatedly, the .vscode change (disabling markdown format-on-save) is committed inside f20d39e3 docs(udp-server): approve request buffer reclamation test, where its subject gives no hint it is there.
Verified good, for the record: no executable production code changes — the request_buffer.rs non-test diff is entirely comments and doc comments, and the rewritten doc comment now matches what the code actually does, which the previous wording did not. Every new test drives real production code (From<RequestParseError> conversions, From<Error> for ErrorKind, UdpRequestKind display/label conversion, handle_packet, BoundSocket::bind/url/service_binding, ActiveRequests::force_push and its Drop); none of them merely exercise a test helper. Ports are all ephemeral (:0), the 1s TASK_COMPLETION_TIMEOUT is a failure bound rather than a sleep, and the probes above found no raciness. All 44 commits are signed with conventional subjects, the package-local ADR follows the existing console/tracker-client/docs/adrs/ layout, and every path its front matter references resolves.
Happy to re-review once the formatting gate is green and the eviction test pins the policy.
|
|
||
| use crate::event as server_event; | ||
| use crate::testing::environment::EnvContainer; | ||
| use crate::{RawRequest, handlers::handle_packet}; |
There was a problem hiding this comment.
Blocker — rustfmt violation; this is one of the two lines failing linter all and CI's Run Formatting-Checks step at this head.
With imports_granularity = "Module" the formatter wants handle_packet on its own crate::handlers line and event as server_event folded into the crate::{...} group:
use crate::handlers::handle_packet;
use crate::testing::environment::EnvContainer;
use crate::{RawRequest, event as server_event};(That also removes the separate use crate::event as server_event; line above.) cargo fmt produces exactly this.
| use tokio::sync::oneshot; | ||
| use tokio::task::JoinHandle; | ||
|
|
||
| use ringbuf::traits::{Observer, Producer}; |
There was a problem hiding this comment.
Blocker — rustfmt violation; the second of the two lines failing linter all and CI's Run Formatting-Checks step.
group_imports = "StdExternalCrate" puts every external crate in one block, sorted, with no blank line splitting it. ringbuf sorts before tokio:
use ringbuf::traits::{Observer, Producer};
use tokio::sync::oneshot;
use tokio::task::JoinHandle;This file has been unformatted since 5fc5f916, so the fix is worth folding back into the series rather than added as a tail commit.
| .await; | ||
| } | ||
|
|
||
| async fn abort_and_join_retained_tasks(self) { |
There was a problem hiding this comment.
Major — this is where the eviction test loses its teeth. drop(self.active_requests) runs before the retained tasks are checked, and ActiveRequests::drop aborts every handle still in the buffer. So assert_was_aborted on the retained tasks passes no matter how many force_push evicted.
I confirmed this empirically: mutating force_push to continue instead of break in the finished == 0 branch — so it aborts every still-active handle rather than only the oldest, discarding up to 50 in-flight requests per admission — keeps all three request-buffer tests and the whole torrust-tracker-udp-server suite green.
That mutation is precisely the change the new ADR exists to forbid, so this test should be the thing that catches it. Checking the retained tasks' liveness right after the Act, before the buffer is dropped, would close the gap:
async fn assert_retained_tasks_are_still_active(&self) {
for task in &self.retained_tasks {
assert!(
!task.join_handle.is_finished(),
"only the oldest task should be evicted when capacity is exhausted"
);
}
}called between assert_oldest_task_was_aborted and abort_and_join_retained_tasks. It also makes the exactly-one part of the policy readable straight from the test.
| (UdpRequestKind::Scrape, "scrape"), | ||
| ]; | ||
|
|
||
| // Act and Assert |
There was a problem hiding this comment.
Nit — // Act and Assert over a loop of (input, expected) tuples sits awkwardly against the prose-first AAA rule this same PR adds to write-unit-test/SKILL.md, which calls a parameter bag evidence that the code has not yet expressed its specification, and against the checklist item requiring distinct // Arrange, // Act, // Assert sections.
The underlying coverage is fine and the loop is genuinely compact, so this is a judgment call rather than a defect — but since the PR is also the thing tightening the convention, it is worth either splitting into three named tests (it_should_label_a_connect_request, and so on) or noting in the plan why the table form is the better trade here.
| "editor.formatOnSave": true | ||
| }, | ||
| "[markdown]": { | ||
| "editor.formatOnSave": false |
There was a problem hiding this comment.
Minor — disabling markdown format-on-save is a shared editor default for everyone who opens this repo, and it arrives inside f20d39e3 docs(udp-server): approve request buffer reclamation test, whose subject gives no hint the change is there.
Given that markdown formatting is checked by linter all, turning off format-on-save makes it easier to commit markdown the gate will later reject. If the intent is to stop the editor fighting a specific formatter, a one-line comment or its own chore(vscode): commit would make the reasoning reviewable.
da2ce7
left a comment
There was a problem hiding this comment.
Re-reviewed f7b355d (46 commits on develop 7abc30b), covering the two commits pushed since my review at 35294e1: 550cf7af test(udp-server): clarify launcher startup cleanup and f7b355d7 docs(udp-server): approve port zero admission test. Gated on a dedicated build host at both heads.
The good news first, and it is real. The launcher refactor in 550cf7af is behaviour-preserving — every Arrange statement moved into UdpLauncherDependencies::new() unchanged and in the same order, the five values reach run_with_graceful_shutdown in the same argument positions, the policy is still Strict, and the visible Act is still the direct launcher call with no wrapper. More importantly the refactored test still has teeth, which I checked rather than assumed: removing running.abort(); drop(running.await); from the production BrokenPipe branch — so the spawned server task keeps holding the socket — makes it_should_release_the_socket_when_the_startup_notification_receiver_is_dropped fail (panic at launcher.rs:403), and the package goes 153 passed / 1 failed. The rename of tx_start/rx_halt to startup_notification_sender/halt_receiver genuinely reads better, and the dropped receiver stays visible in the test body where the causal state belongs. f7b355d7 is internally consistent too: R2 is marked IN_PROGRESS, not done, and no source-port-zero test exists in the tree yet — a grep of packages/udp-server/src finds only the production should_discard_request at launcher.rs:182/:247.
But the blocker is untouched, so this stays at changes-requested. Neither commit touches either rustfmt-failing file. At this head, cargo-fmt --all -- --check still exits 1 (0.7s) with byte-identical diffs in packages/udp-server/src/handlers/mod.rs:268 and packages/udp-server/src/server/request_buffer.rs:141, and linter all still exits 1 (23.7s) with rustfmt as the only failing linter — markdown, lychee, yaml, toml, cspell, clippy and shellcheck all pass. The two inline threads on those lines carry the exact one-line fixes and are still open.
New — a recorded validation result the gate contradicts. 550cf7af adds this row to the R1 plan: | R1 | DONE | `cargo fmt --all -- --check`, `cargo test …`, and `git diff --check` passed. |. That is not true at the commit that records it, nor here. I am flagging it as more than a stale document because the recorded command is itself the likely cause: the space form cargo fmt resolves through a $CARGO_HOME/bin shim before PATH, so it can silently run a different rustfmt than the binary form cargo-fmt that linter all and CI's Run Formatting-Checks both use. My round-1 note guessed at a shim; this row is evidence for it. Try cargo-fmt --all -- --check (with the dash) locally and see whether it disagrees with cargo fmt. Worth resolving before R2, because otherwise the same false green recurs — and f7b355d7 opens R2 by rewriting the row directly beneath this one, 31 minutes after the review, while the plan's own rule is to complete one increment's validation before starting the next. Inline comment below.
The other two round-1 items are also unchanged, and I re-verified both rather than inferring them from the diff:
- Eviction test.
request_buffer.rsis byte-identical to 35294e1, but I re-ran the mutation at this head anyway:force_push'sfinished == 0branchcontinue-ing instead ofbreak-ing — aborting every still-active handle rather than only the oldest, discarding up to 50 in-flight requests per admission — still leaves all three request-buffer tests and the wholetorrust-tracker-udp-serversuite green (154 unit + 11 integration). The mutation the new ADR exists to forbid is still invisible to the test written to protect it. Thread onrequest_buffer.rs:252has the suggested fix. - Mergeability.
git merge-tree --write-tree f6b73e29 f7b355d7still exits 1, conflicting in.github/agents/task-reviewer.agent.md;create-issue/SKILL.md,write-unit-test/SKILL.mdanddocs/templates/ISSUE.mdstill auto-merge while edited on both sides, so the combined rule text deserves a read after the rebase.
Everything else gates clean at this head, for the record: clippy --workspace --all-targets --all-features -D warnings (4.2s), cargo test -p torrust-tracker-udp-server --all-targets --all-features (12.7s, 154 unit + 11 integration), doc tests (1.0s), and the full workspace suite (123.5s). The changed launcher test is not flaky: 20 consecutive runs clean, and 20 more clean under a 64-way CPU load.
One thing to check that is not a finding against the change: no CI has run on either new head. gh run list returns nothing for 550cf7af or f7b355d7, and the commit has zero check suites — polled ~13 minutes after the first push and ~5 after the second, where the two previous pushes on this branch started their five workflows within about ten seconds. The newest CI evidence on the branch is still run 34354718522 at 35294e14: Unit (nightly) failed at step Run Formatting-Checks, cancelling Unit (stable) at Run All Linters. A workflow that never starts emits no check runs at all, so the checks page can look clean when nothing ran — worth a glance given this PR's history of a gate believed green while it was red.
Also still standing from round 1, unchanged and unreplied: the // Act and Assert nit at event.rs:344 and the .vscode/settings.json format-on-save minor. The PR description still lists linter all under Validation.
Happy to re-review as soon as the formatting gate is actually green — that one change closes the blocker and the plan's validation record together.
| | Increment | Status | Evidence | | ||
| | --- | --- | --- | | ||
| | Plan documentation | TODO | Run Markdown and spelling checks after maintainer review changes. | | ||
| | R1 | DONE | `cargo fmt --all -- --check`, `cargo test -p torrust-tracker-udp-server launcher::tests::it_should_release_the_socket_when_the_startup_notification_receiver_is_dropped`, and `git diff --check` passed. The prose-first review separates ordinary launcher construction from the visible dropped receiver state. | |
There was a problem hiding this comment.
Major — this row records a gate result that does not hold, at the commit that adds it and at the current head.
550cf7af writes cargo fmt --all -- --check ... passed for R1. At 550cf7af and at f7b355d7, on a clean checkout, cargo-fmt --all -- --check exits 1 in 0.7s with two diffs — handlers/mod.rs:268 and request_buffer.rs:141 — and linter all exits 1 with rustfmt as its only failing linter.
The reason this is worth more than a documentation correction is that the command as written is a plausible cause of the whole blocker. cargo fmt (space form) is a cargo subcommand, and cargo resolves subcommands through $CARGO_HOME/bin before PATH; cargo-fmt (dash form) is resolved by PATH like any other binary. In an environment with a shim installed, those two can be different rustfmt builds, and only the second is what linter all and CI's Run Formatting-Checks step actually invoke. My round-1 review raised that as a hypothesis; this row is the first direct evidence for it.
Concretely, worth trying locally:
$ cargo fmt --all -- --check ; echo "space form: $?"
$ cargo-fmt --all -- --check ; echo "dash form: $?"
$ command -v -a cargo-fmt rustfmtIf those two exit codes disagree, that explains every "the mandatory pre-commit gate passed" reply on this PR, and the fix is environmental rather than per-commit.
Secondary point on the same table: f7b355d7 rewrites the R2 row immediately below this one to IN_PROGRESS and marks R2 approved, without correcting this row. The plan's own rule is to complete one approved increment — "including prose-first comparison, focused validation, review, and its mapped commit point—before beginning the next item" — so R2 is being opened on the strength of an R1 validation record that did not pass.
| use crate::container::UdpTrackerServerContainer; | ||
| use crate::server::bound_socket::BoundSocket; | ||
|
|
||
| struct UdpLauncherDependencies { |
There was a problem hiding this comment.
Nit — the fixture is named for its construction, which is the one thing this repository's own pattern doc says not to do.
docs/testing/refactoring-patterns/scenario-fixtures-for-causal-initial-state.md, cited from the write-unit-test/SKILL.md this PR edits, says: "Name the fixture after the causal condition, not after construction operations", and gives ServerStartWithDuplicateRegistration as the model. Its Do Not Use When list includes "The setup has no meaningful causal condition beyond ordinary valid input" — which is precisely how the accompanying plan entry describes this type, as owning "only ordinary construction". UdpLauncherDependencies names the plumbing, and the SKILL's own wording calls "a parameter bag ... evidence that the code has not yet expressed its specification".
To be fair to the change, the important half is right: the causal condition — the dropped startup receiver — is deliberately left visible in the test body rather than buried in the fixture, which is exactly what the pattern asks for and is what makes the test read well now. And the extraction is a genuine improvement over 25 inline lines of bootstrap. So this is a naming and doc-consistency point, not a defect.
Either name the type for the state it yields — ValidLauncherReadyToStart, or similar — or record in the plan why a construction-named helper is the better trade for a fixture that establishes no causal condition. Since this PR is also the thing tightening the convention, the deviation is more visible here than it would be anywhere else.
Unrelated and not worth changing: bound_address alongside bound_socket looks like duplicated state, but bound_socket is moved into the Act, so capturing the address first is forced by ownership rather than a slip.
da2ce7
left a comment
There was a problem hiding this comment.
Round-3 review at 954d4d2054186272a123a0fbde943aefa124ebc3.
REQUEST_CHANGES — forced by the round-1 Blocker, which is now four commits old and untouched. Everything else in this round is good news: the new port-zero test is the strongest test on this branch.
Standing items
| Item | Where | Status | Evidence at this head |
|---|---|---|---|
| Blocker — rustfmt | handlers/mod.rs:273 |
OPEN | cargo-fmt --all -- --check exits 1, byte-identical diff; file untouched since 35294e14 |
| Blocker — rustfmt | request_buffer.rs:147 |
OPEN | same run, second diff; file untouched since 35294e14 |
| Major — eviction policy not pinned | request_buffer.rs:252 |
OPEN | mutation M1 (break -> continue, abort every in-flight handle) re-run at 9d966925: package still 155 passed; 0 failed |
Major — conflicts with develop |
.github/agents/task-reviewer.agent.md |
OPEN | git merge-tree --write-tree f6b73e29 954d4d20 exits 1, all three stages present; GitHub CONFLICTING/DIRTY |
| Major — false validation record | launcher-tests.md R1 row |
OPEN and repeated | see the inline comment on the R2 row below |
Nit — // Act and Assert |
event.rs:344 |
OPEN | file untouched since 35294e14 |
| Nit — fixture named for construction | launcher.rs:338 |
OPEN | UdpLauncherDependencies was restructured in 9d966925 (socket binding moved out, bind_address added) and now serves two tests, without a reply on the thread |
| Minor — markdown format-on-save | .vscode/settings.json:6 |
OPEN | file untouched since 35294e14 |
linter all at this head: rustfmt is the only failing linter (markdown, lychee, yaml, toml, cspell, clippy, shellcheck all pass, 23.6s). Package suite passes: 155 unit + 11 integration. Workspace suite, clippy -D warnings and doc tests pass. No CI has run on the last four heads (550cf7af, f7b355d7, 9d966925, 954d4d20 — checkSuites.totalCount 0); the newest CI on the branch is still the round-1 Testing failure at 35294e14.
9d966925 — the port-zero admission test
This one is right, and I checked it adversarially rather than by reading.
It drives the production path. The Act is a direct Launcher::should_discard_request(...) call — the same private associated function the receive loop calls at launcher.rs:181, with the same argument shape. No wrapper, no reimplementation. The production region of launcher.rs (lines 1-313) is byte-identical to the previous head, so the plan's non-goal of not changing admission behaviour holds.
The assertions are on observable effects, and all four have teeth. Four mutations were applied to the production code in a scratch worktree, each reverted, tree verified clean:
| Mutation | Test result |
|---|---|
M3a — delete the port-zero branch (if client_socket_addr.port() == 0 -> if false) |
caught: assertion failed: should_discard |
M3b — keep the discard decision, drop the UdpRequestDiscarded publication |
caught: request-discarded event should be published before the test deadline: Elapsed(()) |
M3d — publish UdpRequestBanned instead of UdpRequestDiscarded |
caught: variant mismatch |
| M3e — publish the discard event carrying a different client address | caught: client_socket_addr: 203.0.113.1:1 vs 203.0.113.1:0 |
So the test pins the decision, the publication, the exact variant, and the exact context payload — not internal state. The event assertion is genuine end-to-end: UdpTrackerServerContainer builds stats_event_sender from the same EventBus the test subscribes to via event_bus.receiver(), and the receiver is created before the Act. The one-second tokio::time::timeout is the right call and its comment earns its place: M3b fails diagnostically in 1.2s instead of hanging.
No flakes. 20 runs of the new test: 0 failures (2s). 20 runs under 64-way CPU contention: 0 failures (3s). 20 runs of the whole server::launcher::tests module, to catch interference with its neighbour: 0 failures (3s).
The R1 test survived the refactor with its teeth. 9d966925 moved socket binding out of the fixture and into the R1 test body. Mutation M2a (remove running.abort(); drop(running.await); from the production BrokenPipe branch) still fails that test, so the restructuring did not hollow it out.
Naming and placement are right. it_should_discard_a_request_when_its_source_port_is_zero follows the package's it_should_* convention, sample_udp_service_binding follows the established sample_* helper convention (13 such helpers in this package), and the test sits next to the seam it covers.
f7b355d7 and 954d4d20 — plan bookkeeping
954d4d20 moves R3 to IN_PROGRESS and records approval of a banned-IP admission test. That is consistent with the tree: no such test exists yet (launcher.rs has exactly two tests, neither about banning), the checklist leaves "R3 assessment completed" unchecked, and markdown/link/spell checks pass on the file. The R2 bookkeeping is consistent too — R2 DONE, the test exists — except for the validation row, which is the inline Major below.
Two nits on the new test follow. Neither blocks; the blocker is the formatting.
| | --- | --- | --- | | ||
| | Plan documentation | TODO | Run Markdown and spelling checks after maintainer review changes. | | ||
| | R1 | DONE | `cargo fmt --all -- --check`, `cargo test -p torrust-tracker-udp-server launcher::tests::it_should_release_the_socket_when_the_startup_notification_receiver_is_dropped`, and `git diff --check` passed. The prose-first review separates ordinary launcher construction from the visible dropped receiver state. | | ||
| | R2 | DONE | `cargo fmt --all -- --check`, `cargo test -p torrust-tracker-udp-server launcher::tests::it_should_discard_a_request_when_its_source_port_is_zero`, and `git diff --check` passed. The prose-first review retains the direct admission Act, causal source port, strict policy, and exact immediate event. | |
There was a problem hiding this comment.
Major — this row records cargo fmt --all -- --check as passing, and it does not pass. This is the second instance of the finding already open on the R1 row directly above.
At 9d966925, the commit that adds this row, and at the current head 954d4d20:
$ cargo-fmt --all -- --check
Diff in .../packages/udp-server/src/handlers/mod.rs:268:
Diff in .../packages/udp-server/src/server/request_buffer.rs:141:
exit=1 wall=0.7s
The other two commands in the row do hold — cargo test -p torrust-tracker-udp-server launcher::tests::it_should_discard_a_request_when_its_source_port_is_zero passes (I ran it 60 times across three probes), and git diff --check is clean. It is specifically the formatting claim that is false.
A correction to what I wrote on the R1 row. I suggested there that the space form cargo fmt might resolve through a $CARGO_HOME/bin shim to a different rustfmt than the binary form cargo-fmt that linter all and CI use. I tested that this round and it is not the mechanism, at least not in the reference environment: CARGO_HOME/bin holds no fmt shim there, and both forms fail identically with the same two diffs. So the record is false under either invocation, and the explanation lies elsewhere — a stale result, a different working tree, or the command not having been re-run. Worth finding out, because the same false-green has now been recorded twice in a row.
The plan's own rule is to complete an increment "including prose-first comparison, focused validation, review, and its mapped commit point—before beginning the next item". 954d4d20 opens R3 on the strength of an R2 validation record that did not pass. Fixing the two formatting lines fixes both rows at once.
| } | ||
|
|
||
| fn sample_udp_service_binding(bind_address: SocketAddr) -> ServiceBinding { | ||
| ServiceBinding::new(Protocol::UDP, SocketAddr::new(bind_address.ip(), 6969)) |
There was a problem hiding this comment.
Nit — the helper takes a bind_address but uses only its IP, replacing the port with the literal 6969. At the call site that reads as though the launcher's configured bind address matters to the contract, when in fact any address would do: the value is a pure fixture that the port-zero branch only clones into the event context, and the assertion compares it against itself.
Since the argument does no work, consider either dropping the parameter and building the binding from a constant, or keeping the parameter and using the address whole (ServiceBinding::new(Protocol::UDP, bind_address)), so the name and the value agree. Not a defect — the test is correct either way.
| &dependencies.udp_tracker_server_container, | ||
| &server_service_binding, | ||
| TEST_LOG_TARGET, | ||
| torrust_tracker_udp_core::ConnectionIdValidationPolicy::Strict, |
There was a problem hiding this comment.
Nit — the Strict argument is inert for this contract, and the plan's prose entry presents it as part of the specification ("The direct should_discard_request Act and strict-policy input remain visible").
The port-zero branch returns before ban_enforcement_active is computed, so passing Lenient here would produce exactly the same decision and the same event. Mirroring production's argument list is reasonable, but as written a reader can come away thinking the discard is strict-mode behaviour when it is in fact policy-independent — which is the more interesting property, and the one currently unspecified.
Cheapest fix is a half-line comment saying the guard runs ahead of policy. Better, if it fits the plan's scope rules: assert the same outcome for both policy values, which pins the independence instead of leaving it to inspection. The R3 entry approved in 954d4d20 explicitly asks to "keep validation-policy choice visible" for the banned-IP test, where the policy genuinely is causal — so the contrast between the two contracts is worth making explicit rather than implicit.
Summary
This draft tracks the incremental implementation of #2149 under the package-testing EPIC. Completed, reviewed increments add deterministic coverage for active-request capacity, oldest-first eviction and drop cleanup; UDP event/error classifications and parse-error routing metadata; and bound-socket port-zero and endpoint metadata contracts. The request-buffer eviction policy is documented in a package-local ADR. The next planned step is documentation reconciliation, followed by an approved two-phase review of handlers/mod.rs.
Scope
Validation
Related to #2149