Files
http-latency-prober/docs/summaries/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

78 lines
4.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Summary: hxprobe reads target URLs from a file
Plan: [docs/plans/2026-07-02-11-14-hxprobe-file-input.md](../plans/2026-07-02-11-14-hxprobe-file-input.md)
## What was built
- **`hxprobe/hxprobe/cli.py`**: new `-f`/`--file PATH` flag, mutually
exclusive with positional `url` args. `urls` positional changed
`nargs="+"``nargs="*"`. New `_load_urls()` helper (same format as
`simple.py`'s `load_sites()`: one URL per line, `#` comments, blank
lines skipped, first token per line) — reimplemented locally rather than
imported, keeping hxprobe's "no imports outside its own directory" rule
intact. Validation added right after `parser.parse_args()`, inside the
same `try/except _ArgExit` block: both-given and neither-given are usage
errors via `parser.error()`; missing/unreadable/empty file are usage
errors via direct `stderr.write()` + `return EXIT_USAGE` (mirroring the
existing `--timeout` invalid-value handling style already in the file).
- **`hxprobe/tests/test_cli.py`**: new `TestCLIFileInput` class, 5 tests —
reads URLs from a temp file successfully, missing file, empty file,
both-sources error, neither-given error.
- **`hxprobe/configs/*.txt`**: 7 new fixtures mirroring
`python/configs/`'s exact set. Each header comment states an "Expected
exit code" that was verified by actually running the fixture through
`hxprobe -f ...` during implementation (not assumed from the
`simple.py` originals, whose blanket 0/1 exit scheme is fundamentally
different from hxprobe's per-failure-class 06 worst-code-wins scheme).
- **`hxprobe/USAGE.md`**: new "Reading URLs from a file (`-f`)" section,
placed after "Multiple URLs", with real captured output for the
successful case, the mutually-exclusive error case, and one failure
fixture (`dns-failure.txt`), plus a table listing all 7 fixtures and
their expected exit codes.
## Key design decisions
- **Manual post-parse validation instead of `argparse`'s
`add_mutually_exclusive_group`** — a variadic positional (`nargs="*"`)
doesn't mix cleanly with argparse's built-in mutually-exclusive-group
machinery. Manual checks after `parser.parse_args()` (still inside the
same `try/except _ArgExit`, using `parser.error()`) give the same
usage-error behavior with full control over the message text.
- **File I/O errors don't go through `parser.error()`** — they use the
same direct `stderr.write()` + `return EXIT_USAGE` pattern already
established for `--timeout` parsing failures, since they're discovered
after parsing succeeds, not during it.
- **Every fixture's exit code was verified live, not assumed.** Two
required real judgment calls the `simple.py` originals didn't need:
`http-errors.txt` and `mixed.txt` both needed `--fail` added to their
demo command (hxprobe treats 4xx as success without it, unlike
`simple.py` which always raises on `HTTPError`) to actually demonstrate
a failure — without `--fail` both would silently show exit 0.
## Deviations from the plan
None of substance. The plan anticipated needing to verify exit codes
live rather than assume them; that anticipation paid off exactly as
expected for `http-errors.txt`/`mixed.txt` (needed `--fail` added) and
`timeout.txt` (needed `--timeout 2s` added to keep the demo fast, and a
note added about this sandbox occasionally short-circuiting one of the two
timeout targets to an immediate "connection refused" — observed directly:
in this session's test run, `10.255.255.1` genuinely timed out; in an
earlier, unrelated test earlier in the session it instead got refused
immediately. The fixture keeps both targets so at least one demonstrates
the real timeout path regardless).
## Verification
- `all-ok.txt` → exit 0, `dns-failure.txt` → 2, `connection-refused.txt`
→ 3, `tls-errors.txt` → 5, `timeout.txt` (with `--timeout 2s`) → 4,
`http-errors.txt` (with `--fail`) → 6, `mixed.txt` (with `--fail`) → 6 —
all confirmed via real `$?` checks (not through a `grep` pipe, which
masks the real exit code — caught and corrected this during testing)
- `uv run pytest tests/test_cli.py -m "not integration"` — 16/16 pass
(11 pre-existing + 5 new)
- `uv run ruff check .` / `uv run ruff format --check .` — clean
- Both-sources-given and missing-file error messages verified against the
doc's pasted output, including the `usage:` block that's actually
printed (an early draft of the doc omitted it — caught on review)