task: ACME event-loop coverage — fake-directory integration test (U-1)

- tests/acme_event_loop.rs: five integration tests drive the crate's
  real ACME path and assert the spawned loop's logged events through a
  hand-rolled tracing test subscriber: AccountCacheStore (eager, pre-
  network), the Order warn via a plain-HTTP fake directory stub on an
  ephemeral 127.0.0.1 port, DeployedCachedCert from a pre-seeded
  PKCS#8-key-first PEM chain, CertCacheLoad/AccountCacheLoad errors +
  AccountCacheStore warn from an ENOTDIR-poisoned cache path, and
  CachedCertParse from a corrupt cached PEM. Event collection is
  timeout-bounded, never JoinHandle-bounded (rustls-acme 0.12.1's
  stream never terminates — verified against the vendored sources).
- src/server.rs: delete the unreachable debug!("ACME: state machine
  ended") line (work item 2: it cannot execute under the
  never-terminating stream).
- DirCache's deterministic cache file names (SHA256 over element+NUL*
  + directory URL, base64url-nopad) are recomputed in-test via the
  sha2 dependency + a new base64 dev-dependency.
- Explicitly not-covered (work item 4): DeployedNewCert, CertCacheStore
  ok+warn, NewCertParse — reachable only through a successful ACME
  order (a full fake CA), out of scope at this effort level.

Verification: cargo llvm-cov --all-features line coverage 99.21%
(from 98.26%), server.rs 96.08% -> 98.85% lines / 100% functions;
cargo test (93 passed), --all-features (123 passed), --features acme;
clippy (default + all-features, -D warnings); fmt; doc. The event-loop
suite is stable across five consecutive runs.
This commit is contained in:
2026-09-12 05:35:46 +00:00
parent efb1f2dbf0
commit f21b62c4b4
5 changed files with 541 additions and 11 deletions
+120 -9
View File
@@ -1,7 +1,7 @@
---
id: acme-event-loop-test
name: ACME event-loop coverage — fake-directory integration test (U-1)
status: pending
status: completed
depends_on: [coverage-cheap-closes]
scope: moderate
risk: medium
@@ -105,23 +105,23 @@ real integration test.
## Verification
- [ ] server.rs 94-136 covered under `cargo llvm-cov --all-features`
- [x] server.rs 94-136 covered under `cargo llvm-cov --all-features`
(line 135 either deleted or explicitly accepted as uncovered,
per work item 2's second consequence)
- [ ] The test performs no real network I/O (binds localhost only)
- [ ] `cargo test --features acme`, `--all-features` green; default
- [x] The test performs no real network I/O (binds localhost only)
- [x] `cargo test --features acme`, `--all-features` green; default
build unaffected
- [ ] clippy/fmt/doc green
- [x] clippy/fmt/doc green
## Acceptance Criteria
- [ ] Every reachable arm per work item 4 is executed by a test
- [x] Every reachable arm per work item 4 is executed by a test
(`Order` warn, `AccountCacheStore`, the Load/Parse error arms,
`DeployedCachedCert`/`CertCacheStore`); `DeployedNewCert`/
`NewCertParse` are explicitly marked not-covered (full fake CA
needed — out of scope)
- [ ] No test awaits the event loop's `JoinHandle` (it never resolves)
- [ ] The ACME feature's runtime surface is no longer
- [x] No test awaits the event loop's `JoinHandle` (it never resolves)
- [x] The ACME feature's runtime surface is no longer
refactor-fragile
## References
@@ -141,6 +141,117 @@ real integration test.
> Agent fills this during implementation.
Work notes (2026-09-12, verified against the vendored rustls-acme
0.12.1 sources before implementation):
- **Scope correction to work item 4 (verified, mechanically)**:
`EventOk::CertCacheStore` / `EventError::CertCacheStore` are *not*
pre-seed-reachable. `DirCache::store_cert` is invoked only from
`process_cert`'s **new-cert** branch (state.rs:219-227
`early_action = store_cert`); a pre-seeded cache hit takes the
`cached = true` branch, which returns `DeployedCachedCert` before
any store. So `DeployedNewCert` / `CertCacheStore` (ok+warn) /
`NewCertParse` are all new-cert-path arms — the same
full-fake-CA class the task already marks out of scope. The
acceptance criterion's "…`DeployedCachedCert`/`CertCacheStore`" is
satisfied as: `CertCacheStore` is covered for the *warn* arm
(below), its ok arm belongs to the not-covered set.
- **Arm coverage achieved** (each via the real event stream, asserted
through a tracing test subscriber — work item 2's preferred shape,
hand-rolled on `tracing` itself, no tracing-subscriber dev-dep):
- `EventOk::AccountCacheStore` (debug) — dead-directory test
(`http://127.0.0.1:9`); the account key is generated and stored
eagerly before any network I/O (state.rs:377-395). The test also
verifies the file exists under the recomputed deterministic name.
- `EventError::Order` (warn) — plain-HTTP fake directory stub
(`std::net::TcpListener`, ephemeral 127.0.0.1 port) serving
`{"newNonce": "/"}` — discover fails to deserialize (the required
camelCase endpoints are missing), the order future errors, the
loop logs the warn. Verified the level is WARN, not ERROR.
- `EventError::CertCacheLoad` + `AccountCacheLoad` (errors) +
`EventError::AccountCacheStore` (warn) — `cache_dir` poisoned as
a *regular file*: DirCache reads of `cache_dir/<name>` fail with
ENOTDIR (an error, not NotFound-miss) and `create_dir_all` in the
store path fails — three arms in one scenario.
- `EventError::CachedCertParse` (error) — a real cache file under
the recomputed name holding a corrupt PEM (load succeeds, parse
fails).
- `EventOk::DeployedCachedCert` (debug) — a pre-seeded valid PEM
(rcgen ECDSA-P256 self-signed, PKCS#8 key PEM first, then cert —
`parse_cert`'s required order). Zero network I/O.
- **Work item 2, second consequence: the dead line was deleted.**
`debug!("ACME: state machine ended")` (former server.rs:135) is
unreachable under rustls-acme 0.12.1's never-terminating stream;
removing it also removed the dead-code trap. Note llvm-cov still
reports the closure's closing `});` (now server.rs:156) uncovered —
structurally uncoverable for the same reason (the spawned closure
never returns); same accepted class.
- **Remaining uncovered in the loop (all new-cert-path, explicitly
not-covered per work item 4)**: `DeployedNewCert` (125),
`CertCacheStore` ok (128), `CertCacheStore` warn (139-140),
`NewCertParse` (151-152) — reachable only through a *successful*
order (a full fake ACME CA: newNonce/newAccount/newOrder/
authorizations/finalize/certificate). Documented here, not silent.
- **Mechanical notes for future maintainers**: the fake directory is
served by a dedicated OS thread (one accept, then done); the
client side is rustls-acme's own `async_web_client` (plain
`async_net::TcpStream` for `http://`), which self-drives via
async-io's reactor thread — it works under `#[tokio::test]`'s
current-thread runtime without any tokio networking features.
The subscriber is installed with
`tracing::subscriber::set_default` (thread-local default), visible
to the spawned loop because `#[tokio::test]` polls the task on the
test thread. `Timer::after` on the huge renewal wait is safe:
async-io 2.6 maps `Instant::checked_add` overflow to `Timer::never`
(no panic).
- Timing: each scenario asserts on captured events after a
`tokio::time::timeout`-bounded poll loop (10s cap, events arrive in
~ms); the suite is stable across repeated runs.
## Summary
> Agent fills this on completion.
> Agent fills this on completion.
Closed the ACME event-loop coverage gap (review 001 §U-1) with
`tests/acme_event_loop.rs` — five integration tests driving the
crate's real ACME path (`TlsServerConfig::new` on an `Acme` identity)
and asserting the loop's logged events through a hand-rolled tracing
test subscriber:
- `event_loop_logs_account_store_against_a_dead_directory`
`EventOk::AccountCacheStore` fires before any network I/O; also
verifies the deterministic `DirCache` file name on disk.
- `event_loop_logs_order_warn_against_a_directory_with_no_usable_endpoints`
— a plain-HTTP stub on an ephemeral 127.0.0.1 port serves an
unparseable directory document; the `EventError::Order` warn fires
through the real event stream (real HTTP, real deserialize failure).
- `event_loop_deploys_a_valid_pre_seeded_cached_cert` — a valid
pre-seeded PEM chain (PKCS#8 key first) drives
`EventOk::DeployedCachedCert` with zero network I/O.
- `event_loop_logs_cert_cache_load_errors_when_the_cache_dir_is_a_file`
`CertCacheLoad` + `AccountCacheLoad` errors and the
`AccountCacheStore` warn from one ENOTDIR-poisoned cache path.
- `event_loop_logs_cached_cert_parse_error_on_a_corrupt_cert_cache`
a corrupt cached PEM surfaces `CachedCertParse` with the underlying
error attached.
Event collection is bounded by wall-clock timeout, never by the
spawned task's `JoinHandle` (rustls-acme 0.12.1's stream never
terminates). No test awaits the handle. The unreachable
`debug!("ACME: state machine ended")` line was deleted per work item 2.
Only non-test source change: that dead-line deletion. Dev-deps: added
`base64` (for the in-test recomputation of `DirCache`'s deterministic
file names).
Verification: `cargo llvm-cov --all-features` line coverage **99.21%**
crate-wide (from 98.26%); server.rs 96.08% → **98.85%** lines /
100.00% functions. The 7 remaining uncovered lines in server.rs are
exactly the four new-cert-path arms (125, 128, 139-140, 151-152 —
need a full fake ACME CA, out of scope per work item 4) plus the
closure's closing `});` (156 — structurally uncoverable, the spawned
task never returns). `cargo test` (93 passed), `cargo test
--all-features` (123 passed), `cargo test --features acme`, clippy
(default + all-features, `-D warnings`), `cargo fmt --check`,
`cargo doc --no-deps` — all green. The event-loop suite is stable
across five consecutive runs.