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>
This commit is contained in:
77
docs/summaries/2026-07-02-11-14-hxprobe-file-input.md
Normal file
77
docs/summaries/2026-07-02-11-14-hxprobe-file-input.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# 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 0–6 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)
|
||||
Reference in New Issue
Block a user