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

103 lines
5.4 KiB
Markdown

# 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 only** — `latprobe` 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/`.