Files
http-latency-prober/docs/summaries/2026-07-02-14-05-hxprobe-run-summary-footer.md
Jan Novak f487a4b1bd docs: hxprobe plans, summaries, explanations, usage, and changelog
Plans (docs/plans/):
- 2026-07-01-23-47-py-hxprobe-httpx.md — initial httpx probe design
- 2026-07-02-09-32 through 14-05 — standalone project, toolchain, usage
  doc + Makefile, file input (-f), simplification pass, run-summary footer

Summaries (docs/summaries/): one per completed feature, recording what
was actually built, deviations from the plan, and verification steps

Explanations (docs/explanations/): two deep-dives written during review —
hxprobe concurrency model and worst-exit-code + render-loop analysis

Usage (docs/usage/hxprobe.md): overview with pointer to hxprobe/USAGE.md
for the full runnable reference

Walkthrough (docs/py-latprobe-walkthrough.md): narrative tour of the
latprobe Python package for interview / code-review context

CHANGELOG.md: entries for all hxprobe features (toolchain, usage doc,
file input, simplification, run-summary footer)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-02 13:22:59 +02:00

5.6 KiB
Raw Permalink Blame History

hxprobe: end-of-run summary footer — summary

Plan: docs/plans/2026-07-02-14-05-hxprobe-run-summary-footer.md.

Trigger: a follow-up to docs/explanations/2026-07-02-13-25-hxprobe-worst-exit-code-and-render-loop.md — does a single worst-code exit even make sense across multiple URLs with different possible errors? The answer landed on: keep the scalar exit code (it's a documented cross-implementation contract with latprobe/Go — see hxprobe/USAGE.md's Exit codes table, asserted by 13 existing tests), and instead add the missing visibility as an end-of-run summary footer.

What changed

All in hxprobe/hxprobe/cli.py unless noted, matching the plan exactly (no deviations):

  1. _EXIT_LABELS (new, next to _PHASE_EXIT): inverse mapping from exit code → short label (ok, dns, connect, timeout, tls, http), used to render the footer's → exit N (label) line.

  2. Accumulation loop refactor: previously computed a single running worst directly inside the per-URL loop via two different idioms (if c > worst: worst = c for network failures, max(worst, EXIT_HTTP) for --fail). Now each URL first gets its own code (EXIT_OK folded up via max() across its failed samples and, if --fail, its ≥400 successes), appended to a new url_codes: list[int] (index-aligned with urls), and then folded into worst = max(worst, code). Unifies both idioms into one.

  3. _print_run_summary(urls, url_codes, worst, out) (new helper, next to _print_failure_summary): writes a separator line, an N URLs — X ok[, Y failed] header, one ✗ {label:<8}: {count} line per non-OK class present (first-seen order, same dedup style as _summarize_failures), and the closing → exit N (label) line.

  4. Call site: elif len(urls) > 1: _print_run_summary(urls, url_codes, worst, stdout) — added after the per-URL loop, in the else branch of the existing if ns.json_out: check (so it's text-mode-only), right before return worst. JSON path (json.dumps(json_items, ...)) is completely untouched — still a bare array, no top-level summary object, preserving the documented "same shape as latprobe's JSON" contract.

Design decisions (confirmed with user before implementing)

  • Exit code stays a scalar (worst/highest severity across URLs) — not count-of-failed-URLs, not binary 0/1. Both alternatives were presented and rejected because they'd break the documented 06 table, Go/latprobe parity, and the 13 existing exit-code tests.
  • Summary is a text footer, multi-URL only (len(urls) > 1) — not always shown, and not also duplicated into JSON as a top-level object (which would turn the JSON array into an object and break the documented array-shape parity). Single-URL text output is untouched; JSON output is untouched.

Tests

hxprobe/tests/test_cli.py: new TestCLIRunSummary class, 3 tests, reusing the existing _OKHandler/_start_server/_free_port/_invoke harness:

  • test_multi_url_mixed_shows_summary — one OK URL + one connection-refused URL: asserts EXIT_CONNECT, and the footer strings ("Summary: 2 URLs", "1 ok", "1 failed", "connect : 1", "→ exit 3").
  • test_multi_url_all_ok_summary — two OK URLs: asserts EXIT_OK, "Summary: 2 URLs — 2 ok", and no "✗" anywhere in output.
  • test_single_url_has_no_summary — one OK URL: asserts "Summary:" is absent (locks the multi-URL-only rule).

All 33 pre-existing tests pass unedited (33 + 3 new = 36 total).

Verification

  • hxprobe/.venv/bin/python -m pytest tests/test_cli.py tests/test_probe.py -q36 passed.
  • hxprobe/.venv/bin/ruff check hxprobe/ tests/all checks passed.
  • Manual smoke tests, all matching the plan's expected behavior exactly:
    • hxprobe https://example.com https://example.org → footer Summary: 2 URLs — 2 ok / → exit 0 (ok).
    • hxprobe https://example.com http://no.such.host.invalid → footer Summary: 2 URLs — 1 ok, 1 failed / ✗ dns : 1 / → exit 2 (dns); process exit code confirmed 2 via echo $?.
    • hxprobe https://example.com (single URL) → no footer, output byte-for-byte the same shape as before this change.
    • hxprobe --json https://example.com https://example.org → still a bare JSON array, no summary object.
    • Also captured a 3-URL mixed run (example.com ok, DNS failure, TLS failure against self-signed.badssl.com) to confirm severity ordering in the footer: DNS (2) and TLS (5) both counted, exit reported as 5 (tls) — the higher-severity class correctly wins the scalar while the footer still shows the DNS failure that the scalar alone would hide.

Docs updated (per CLAUDE.md conventions)

  • hxprobe/USAGE.md: new "Multi-URL summary footer" section with a real 3-URL mixed-outcome capture; refreshed the pre-existing "Multiple URLs" and -f configs/all-ok.txt / -f configs/dns-failure.txt examples, which were captured before this feature existed and were now stale (missing the footer) — replaced with fresh live captures; added a sentence to the Exit codes section pointing at the new section.
  • docs/usage/hxprobe.md: one-line addition to the exit-codes bullet pointing at hxprobe/USAGE.md's new section.
  • docs/explanations/2026-07-02-13-25-hxprobe-worst-exit-code-and-render-loop.md: appended an "Update (2026-07-02)" paragraph pointing forward to this work, per the plan's "optional — pointer instead of a new file" option.
  • CHANGELOG.md: new entry at the top.
  • This file.

No deviations from the approved plan.