feat: end-to-end suite — 18-test consolidation over the shared harness
- tests/end_to_end.rs: 1 MiB backpressure, deadline→timeout (ADR-049 §2), CF-006 witness (transport + token), odd-ID asserts + distinctness (ADR-047 §5), full CF-005 precedence chain (ServingConfig override, distinct-id token probe, fail-closed), R-01 concurrency ×3, listen FIFO + typed-error table, datagram batching + 7-byte chunk-split codec probe + reverse codec session + TCP/UDP concurrent, teardown matrix arms (close-with-pump / join copy counts / Drop-with-pump / failed-adopt), no-phantom-channel + R-02 data-plane-tracking asserts - tests/harness.rs: RegistrationMode::Timeout (hanging establisher, per-registration bound), wire_serving_identity (CF-005 (a) shape) - task doc: status completed, notes + summary Verified: cargo test (68), cargo test --features local (75), 3× repeat-run clean both configs, clippy -D warnings (all-targets, local, wasm32), fmt --check, wasm32 check
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
---
|
||||
id: tunnels/end-to-end-suite
|
||||
name: End-to-end suite — forward, reverse, listen, datagram, teardown matrix
|
||||
status: pending
|
||||
status: completed
|
||||
depends_on: [tunnels/consumer-session, tunnels/producer-listen, tunnels/local-socket-halves]
|
||||
scope: broad
|
||||
risk: medium
|
||||
@@ -75,6 +75,80 @@ topologies to unify.
|
||||
|
||||
> Agent fills during implementation.
|
||||
|
||||
### Implementation notes (2026-09-08)
|
||||
|
||||
The pre-existing per-task suites (36 integration tests across
|
||||
`producer_open_op`, `consumer_session`, `producer_listen`,
|
||||
`local_halves`) already carried much of the per-suite coverage; this
|
||||
task consolidated the remaining POC-validated behaviors they didn't
|
||||
cover into `tests/end_to_end.rs` (18 tests, the suites as enumerated)
|
||||
and extended `tests/harness.rs`:
|
||||
|
||||
- `RegistrationMode::Timeout(Duration)` — a hanging establisher with
|
||||
an explicit per-registration establishment timeout (the ADR-049 §2
|
||||
deadline-expiry probe; the wrapper maps expiry to reason `timeout`
|
||||
with no channel surviving).
|
||||
- `wire_serving_identity` — the `ServingConfig.identity` override
|
||||
probe shape (CF-005 (a)).
|
||||
|
||||
Gaps the suite filled (behaviors the per-task suites did not cover):
|
||||
1 MiB backpressure round-trip (both POCs' sizing probe); the
|
||||
deadline→`timeout` wire mapping; odd-ID allocation asserts (ADR-047 §5)
|
||||
plus distinctness; the FULL identity precedence chain — the
|
||||
`ServingConfig.identity` override and the token-over-transport
|
||||
precedence with a DISTINCT resolved id (the existing token test
|
||||
resolved to the same id as the transport identity, so it proved
|
||||
authorization but not precedence — the new `DistinctTokenProvider`
|
||||
probe proves precedence); reverse datagram sessions via the codec
|
||||
(pump-less `(0, 0, reaped)` join shape); 40-datagram bursts with
|
||||
batching (the pending-queue cover, both directions); the 7-byte
|
||||
chunk-split codec probe (100 datagrams); TCP+UDP interleaved on one
|
||||
connection; teardown matrix arms: close-with-pump (abort + reap both
|
||||
sides), join copy counts, Drop-with-pump (abort AND reap), and the
|
||||
failed-adopt no-leak arm; R-02's JoinHandle-tracks-the-data-plane
|
||||
assertion (alive-and-pumpable long after the open, completion only at
|
||||
both-direction EOF).
|
||||
|
||||
### Test-side findings (not product bugs — recorded for the record)
|
||||
|
||||
- **Parked ≠ adopted.** `teardown_channel` on a reverse-open'd but
|
||||
never-adopted channel id is `UnknownChannel` — the manager parks
|
||||
early arrivals for unseen ids (the adoption-race cover, ADR-047 §5)
|
||||
but does not install a channel entry until adopt. Cleanup for such
|
||||
ids is adopt-then-close (what the tests do). Worth knowing when an
|
||||
assembly layer errors between `open_reverse_channel` and `adopt`.
|
||||
- **`read(&mut Vec)` vs `read(buf)`** — an unpopulated `Vec` passed to
|
||||
`AsyncReadExt::read` has len 0 and reads nothing (instant EOF); the
|
||||
1 MiB loop needed a size-`expected` buffer with `&mut received[got..]`.
|
||||
- The empty-queue-vs-error posture from `tunnels/producer-listen`
|
||||
carries over verbatim: the queue's pop waits; mapping empty-pop to
|
||||
`resource_shortage` is the assembly closure's posture (producer.md's
|
||||
table), asserted here with a fail-fast closure.
|
||||
|
||||
## Summary
|
||||
|
||||
> Agent fills this on completion.
|
||||
> Agent fills this on completion.
|
||||
|
||||
### Summary
|
||||
|
||||
`tests/end_to_end.rs` (18 tests) consolidates the POC-validated
|
||||
behaviors into the crate's end-to-end suite over the shared duplex
|
||||
harness: forward (1 MiB backpressure, deadline→`timeout`, CF-006
|
||||
witness on transport + token paths), reverse (odd-ID asserts +
|
||||
distinctness, the full CF-005 precedence chain incl. the
|
||||
ServingConfig override and a distinct-id token probe, R-01 same-
|
||||
resource concurrency ×3), listen (FIFO always-before-take, the typed
|
||||
error table), datagram (batching survival, 7-byte chunk-split codec
|
||||
probe, reverse codec session, TCP+UDP concurrent), teardown matrix
|
||||
(close-with-pump / join copy counts / Drop-with-pump / failed-adopt),
|
||||
and the spec-conformance assertions (no phantom channel, no leaked
|
||||
session, R-02's data-plane-tracking pump handle).
|
||||
|
||||
Harness grew `RegistrationMode::Timeout` + `wire_serving_identity`.
|
||||
Existing suites untouched. Verification: `cargo test` green (68 tests
|
||||
incl. unit + doc), `cargo test --features local` green (75), 3×
|
||||
repeat-run clean (both feature configs), clippy `-D warnings` clean
|
||||
(all-targets, `--features local`, wasm32), `cargo fmt --check` clean,
|
||||
wasm32 check passes. No product-code changes were needed — the
|
||||
failures during development were all test-harness bugs (zero-len read
|
||||
buffer, teardown-on-parked-id), recorded above.
|
||||
+1140
File diff suppressed because it is too large
Load Diff
@@ -97,6 +97,11 @@ pub struct Topology {
|
||||
pub enum RegistrationMode {
|
||||
Dial(alktunnels::producer::DialFn),
|
||||
Listen(alktunnels::producer::AcceptFn),
|
||||
/// A hanging establisher registered with an explicit
|
||||
/// per-registration establishment timeout (the deadline-expiry
|
||||
/// probe — ADR-049 §2's bound override; the open fails with reason
|
||||
/// `timeout` and no channel survives).
|
||||
Timeout(std::time::Duration),
|
||||
}
|
||||
|
||||
pub async fn wire_with(
|
||||
@@ -214,6 +219,35 @@ pub async fn wire_with(
|
||||
)
|
||||
.expect("register tunnel listen openable");
|
||||
}
|
||||
RegistrationMode::Timeout(timeout) => {
|
||||
// The deadline-expiry probe's registration shape: a hanging
|
||||
// establisher (never resolves) with an explicit per-
|
||||
// registration timeout. The generic channel ops registered
|
||||
// above on the SAME registry are unaffected (per-op
|
||||
// timeout, ADR-049 §2).
|
||||
let core = alkcall::channels::operations::ChannelCore::new(
|
||||
producer_client.manager().clone(),
|
||||
alkcall::channels::policy::default_policy(),
|
||||
);
|
||||
core.register_openable_with_establisher(
|
||||
alktunnels::params::tunnel_open_spec(),
|
||||
Some(Arc::new(|_input: serde_json::Value, _auth| {
|
||||
Box::pin(async {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(3600)).await;
|
||||
unreachable!("hanging establisher must be timed out, not resolve")
|
||||
as Result<
|
||||
alkcall::channels::operations::Establishment,
|
||||
alkcall::channels::operations::EstablishmentError,
|
||||
>
|
||||
})
|
||||
})),
|
||||
alktunnels::producer::make_tunnel_pump_handler(),
|
||||
&producer_op_registry,
|
||||
AuthContext::anonymous(b"alk/tunnel"),
|
||||
Some(timeout),
|
||||
)
|
||||
.expect("register hanging establisher");
|
||||
}
|
||||
}
|
||||
let producer_client = Arc::new(producer_client);
|
||||
|
||||
@@ -244,6 +278,26 @@ pub async fn wire(registry: ResourceRegistry, dial: alktunnels::producer::DialFn
|
||||
.await
|
||||
}
|
||||
|
||||
/// A topology with the consumer's transport identity REPLACED by the
|
||||
/// serving-side override (`ServingConfig.identity` — CF-005 (a) probe
|
||||
/// shape) or stripped entirely (the fail-closed probe). The transport
|
||||
/// identity still rides the dialing connection; the override is what
|
||||
/// the serving dispatch resolves (the witness proves which one won).
|
||||
pub async fn wire_serving_identity(
|
||||
registry: ResourceRegistry,
|
||||
dial: alktunnels::producer::DialFn,
|
||||
override_identity: Option<Identity>,
|
||||
) -> Topology {
|
||||
wire_with(
|
||||
registry,
|
||||
RegistrationMode::Dial(dial),
|
||||
Some(consumer_identity()),
|
||||
override_identity,
|
||||
Arc::new(alkcall::core::auth::NoopIdentityProvider),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// The listen topology (shape 2): the producer's establisher pops
|
||||
/// accepted handles from the assembly-fed [`AcceptQueue`] instead of
|
||||
/// dialing. Same identity posture as [`wire`].
|
||||
|
||||
Reference in New Issue
Block a user