Files
http-latency-prober/docs/plans/2026-07-02-11-14-hxprobe-file-input.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.4 KiB

Plan: read target URLs from a file for hxprobe

Context

hxprobe currently only accepts URLs as positional CLI arguments (hxprobe/hxprobe/cli.py:368, nargs="+"). The user wants a file-based input mode too, matching the existing convention simple.py/phases.py already use (python/simple.py:20-30's load_sites(): plain text, one URL per line, #-comments and blank lines skipped, first whitespace-separated token taken per line).

User-confirmed decisions:

  • Mutually exclusive with positional URL args (either pass URLs on the command line, or -f FILE, never both).
  • hxprobe onlylatprobe is intentionally left untouched.
  • Add example fixture files (hxprobe/configs/*.txt, mirroring python/configs/*.txt's exact set: all-ok, dns-failure, connection-refused, timeout, tls-errors, http-errors, mixed) plus one new section in the existing hxprobe/USAGE.md demonstrating the flag.

Implementation

hxprobe/hxprobe/cli.py:

  • urls positional becomes nargs="*" (was nargs="+") — no longer required on its own, since -f is now a second valid source.
  • New flag: -f, --file PATH — "read URLs from a file, one per line, # comments allowed (mutually exclusive with positional url args)". Placed right after the urls positional definition in the argparse block, since the two are the two ways of specifying what to probe.
  • New helper _load_urls(path: str) -> list[str], duplicating (not importing) simple.py's load_sites() logic — consistent with hxprobe's established "imports nothing outside its own directory" rule from the standalone-extraction work.
  • After parser.parse_args(), manual validation (mirrors the existing --timeout invalid-value handling style — write to the injected stderr, return EXIT_USAGE, rather than routing through argparse's mutually-exclusive-group machinery, which doesn't mix cleanly with a variadic positional):
    • both ns.urls and ns.file given → parser.error(...) (usage error, consistent with how _Parser.error() already handles bad usage)
    • neither given → parser.error(...)
    • ns.file given but unreadable (FileNotFoundError/OSError) → stderr.write(...); return EXIT_USAGE
    • ns.file given but yields zero URLs → same treatment
    • otherwise urls = _load_urls(ns.file) or urls = ns.urls

hxprobe/tests/test_cli.py: new hermetic tests — successful multi-URL run from a file, missing-file error, empty-file error, and the both-sources-given usage error. Uses a temp file (tempfile), no network needed for the parsing-error cases.

hxprobe/configs/*.txt (new directory) — same 7 fixtures as python/configs/, adapted:

  • all-ok.txt, dns-failure.txt, connection-refused.txt, tls-errors.txt — same URLs, same behavior (DNS/TCP/TLS failures are identical regardless of HTTP client sophistication); only the header comments change (hxprobe -f configs/<name>.txt instead of python3.14 python/simple.py ...).
  • timeout.txt — same two targets (10.255.255.1, 192.0.2.1, RFC 5737 TEST-NET-1) as the original; "expected exit code" documents normal-network behavior (exit 4), same caveat the original file already carries about network-dependent behavior.
  • http-errors.txt — same 404 URLs; header comment updated to show the demo command with --fail (hxprobe treats 4xx as success without --fail, unlike simple.py, which always raises on HTTPError) — "expected exit code" becomes 6, not simple.py's blanket 1.
  • mixed.txt — same mixed set; demo command includes --fail; expected exit code recalculated as the worst code across the included classes (dns=2, connect=3, tls=5, http=6 with --fail) → 6.
  • Each header's "Expected exit code" will be verified by actually running the fixture through hxprobe -f ... during implementation, not assumed from the simple.py originals — hxprobe's worst-code-wins exit scheme (hxprobe/hxprobe/cli.py:455-481) differs fundamentally from simple.py's blanket 0/1.

hxprobe/USAGE.md: new section "Reading URLs from a file (-f)", placed after the "Multiple URLs" case (same family of "what to probe" examples) — command + real captured output using configs/all-ok.txt or configs/mixed.txt, plus a short list of the other fixture files available and what each demonstrates.

Docs housekeeping (per CLAUDE.md convention): save this plan under docs/plans/, write a summary under docs/summaries/ after implementation, append a CHANGELOG.md entry.

Verification

  1. cd hxprobe && uv run python -m hxprobe -f configs/all-ok.txt — probes all 3 URLs, exit 0.
  2. cd hxprobe && uv run python -m hxprobe -f configs/dns-failure.txt — exit 2; connection-refused.txt → exit 3; tls-errors.txt → exit 5; --fail -f configs/http-errors.txt → exit 6; --fail -f configs/mixed.txt → exit 6 (confirms the worst-code documented in each header is accurate).
  3. uv run python -m hxprobe https://example.com -f configs/all-ok.txt — usage error (both sources given).
  4. uv run python -m hxprobe -f /no/such/file — usage error, clear message.
  5. cd hxprobe && make check — new hermetic tests pass alongside the existing 28.
  6. Re-run the new hxprobe/USAGE.md section's command and confirm captured output matches what's printed in the doc.
  7. grep -n "import" hxprobe/hxprobe/cli.py — confirm no new import from python/simple.py or anywhere outside hxprobe/.