Files
http-latency-prober/Makefile
Jan Novak 24ea9c9e71 feat(py): step 3 — full latprobe package with --verbose flag
Adds the complete Python port of the Go latprobe tool plus diagnostic
verbose output that the Go version does not yet have.

Package (python/latprobe/):
- probe.py: raw-socket HTTP timing with CertInfo/VerboseDetail dataclasses;
  TTFB loop accumulates to \r\n\r\n so headers are parseable without changing
  t_first_byte semantics; captures resolved IP, TLS version/cipher/bits,
  verified cert (via getpeercert()), and all response headers when verbose=True
- aggregate.py: summarize() → per-phase min/avg/max PhaseStats
- cli.py: injectable run(args,stdout,stderr)->int; argparse with injected
  streams; ThreadPoolExecutor concurrency across URLs; four text-rendering
  branches; JSON output; worst-exit-code accumulation; -v/--verbose flag
  appends IP/TLS/cert/header block after every timing table; JSON extended
  with "verbose" object (omitted when flag absent)
- duration.py: parse Go-style duration strings (10s, 500ms, 2m, bare seconds)
- Exit codes: 0 ok, 1 usage, 2 dns, 3 connect, 4 timeout, 5 tls, 6 http≥400

Tests:
- test_probe.py: 18 hermetic tests (success, 404, DNS/connect/timeout/scheme
  failures, partial-phase invariants, verbose detail fields)
- test_cli.py: 38 hermetic tests (usage errors, single/aggregate/failure text,
  worst-code, --fail, JSON schema/ordering/grouping, verbose text and JSON)
- test_integration.py: 45 tests against live internet services (badssl.com for
  TLS errors, real cert/IP/header validation in verbose mode)

Makefile: PYTHONPATH fix, fnmatch pattern test_[!i]*.py to exclude
integration tests from py-test, new py-test-integration target

Docs: docs/usage/py-latprobe.md, python/configs/usage-latprobe.md (runnable
reference with captured output), plans for both the package and verbose feature

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

129 lines
4.2 KiB
Makefile

GO_DIR := go
PY_DIR := python
BINARY := latprobe
PYTHON := python3.14
ARGS ?=
SITES ?= $(PY_DIR)/sites.txt
.DEFAULT_GOAL := help
# ── help ──────────────────────────────────────────────────────────────────────
.PHONY: help
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) \
| awk 'BEGIN {FS = ":.*##"}; {printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}' \
| sort
# ── umbrella targets (delegate to Go now; py-* will be added during Python port) ──
.PHONY: all
all: check build ## Run checks then build
.PHONY: build
build: go-build ## Build binary (delegates to go-build)
.PHONY: test
test: go-test py-test ## Run all tests (Go + Python)
.PHONY: check
check: go-check py-check ## Run fmt + vet + test gate (Go + Python)
.PHONY: fmt
fmt: go-fmt ## Format source code (delegates to go-fmt)
.PHONY: vet
vet: go-vet ## Run go vet (delegates to go-vet)
.PHONY: clean
clean: go-clean py-clean ## Remove build and coverage artifacts
# ── Go targets ────────────────────────────────────────────────────────────────
.PHONY: go-build
go-build: ## go: build binary → go/latprobe
go -C $(GO_DIR) build -o $(BINARY) .
.PHONY: go-run
go-run: ## go: build and run (pass flags via ARGS="…")
go -C $(GO_DIR) run . $(ARGS)
.PHONY: go-test
go-test: ## go: run all tests
go -C $(GO_DIR) test ./...
.PHONY: go-test-verbose
go-test-verbose: ## go: run all tests with per-case output
go -C $(GO_DIR) test -v ./...
.PHONY: go-test-race
go-test-race: ## go: run tests with the race detector enabled
go -C $(GO_DIR) test -race ./...
.PHONY: go-check
go-check: go-fmt go-vet go-test ## go: fmt + vet + test (pre-commit gate)
.PHONY: go-cover
go-cover: ## go: run tests with coverage → go/coverage.html
go -C $(GO_DIR) test -coverprofile=coverage.out ./...
go -C $(GO_DIR) tool cover -html=coverage.out -o coverage.html
@echo "Coverage report: $(GO_DIR)/coverage.html"
.PHONY: go-fmt
go-fmt: ## go: format all Go source files with gofmt
gofmt -w $(GO_DIR)
.PHONY: go-vet
go-vet: ## go: run go vet on all packages
go -C $(GO_DIR) vet ./...
.PHONY: go-tidy
go-tidy: ## go: run go mod tidy
go -C $(GO_DIR) mod tidy
.PHONY: go-install
go-install: ## go: install binary to $GOBIN / $GOPATH/bin
go -C $(GO_DIR) install .
.PHONY: go-lint
go-lint: ## go: run golangci-lint (must be installed)
@command -v golangci-lint >/dev/null 2>&1 || { \
echo "golangci-lint not installed — see https://golangci-lint.run/usage/install/"; \
exit 1; \
}
cd $(GO_DIR) && golangci-lint run
.PHONY: go-clean
go-clean: ## go: remove binary and coverage artifacts
rm -f $(GO_DIR)/$(BINARY) $(GO_DIR)/coverage.out $(GO_DIR)/coverage.html
# ── Python targets ────────────────────────────────────────────────────────────
.PHONY: py-simple-run
py-simple-run: ## py: run simple.py (pass config via SITES=path/to/sites.txt)
$(PYTHON) $(PY_DIR)/simple.py $(SITES)
.PHONY: py-phases-run
py-phases-run: ## py: run phases.py (pass URLs via ARGS="url …" or SITES=path)
$(PYTHON) $(PY_DIR)/phases.py $(if $(ARGS),$(ARGS),$(SITES))
.PHONY: py-run
py-run: ## py: run the full latprobe package (pass flags via ARGS="…")
cd $(PY_DIR) && $(PYTHON) -m latprobe $(ARGS)
.PHONY: py-test
py-test: ## py: run Python unit tests (local, hermetic)
PYTHONPATH=$(PY_DIR) $(PYTHON) -m unittest discover -s $(PY_DIR)/tests -p 'test_[!i]*.py' -v $(ARGS)
.PHONY: py-test-integration
py-test-integration: ## py: run integration tests against live internet services (~30 s)
PYTHONPATH=$(PY_DIR) $(PYTHON) -m unittest discover -s $(PY_DIR)/tests -p 'test_integration.py' -v $(ARGS)
.PHONY: py-check
py-check: py-test ## py: run Python test gate (hermetic only)
.PHONY: py-clean
py-clean: ## py: remove Python bytecode and __pycache__ dirs
@find $(PY_DIR) -type d -name '__pycache__' -exec rm -rf {} + 2>/dev/null; \
find $(PY_DIR) -name '*.pyc' -delete 2>/dev/null; true