From fcb6593afd7ad7a939fb854e8064eda3558c818d Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 23 Aug 2026 11:36:56 -0700 Subject: [PATCH] Homepage (TDD slice 2): the http probe method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Red test first: a raw TCP listener that never speaks HTTP must read down under an http probe (up under tcp), while a real HTTP server reads up. This test caught a real hang — the backgrounded listener inherited the test runner's stdout pipe and held it open; fixed by redirecting its output and killing both listeners in teardown. scripts/homepage-status.py: probe() now dispatches on method. http does an urlopen (any HTTP status = up; no connection = down); tcp connects. The checker is now complete: tcp + http + wip + atomic write + generated_at. Remaining slices: services.json schema, the pages + theme + toggle, the Caddyfile (validate + two hashes), the git-driven deploy. --- scripts/homepage-status.py | 13 +++++++++++ tests/homepage_checker.bats | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/scripts/homepage-status.py b/scripts/homepage-status.py index 3e49f62..0bfdd87 100755 --- a/scripts/homepage-status.py +++ b/scripts/homepage-status.py @@ -11,6 +11,8 @@ import os import socket import sys import tempfile +import urllib.error +import urllib.request from datetime import datetime, timezone TIMEOUT = 5 # seconds, per probe (Q11 / Q40) @@ -18,6 +20,17 @@ TIMEOUT = 5 # seconds, per probe (Q11 / Q40) def probe(host, port, method="tcp"): """Return 'up' if the backend answers, else 'down'.""" + if method == "http": + try: + urllib.request.urlopen( + urllib.request.Request("http://%s:%d/" % (host, port)), + timeout=TIMEOUT, + ) + return "up" + except urllib.error.HTTPError: + return "up" # an HTTP response of any status means the backend is up + except Exception: + return "down" try: with socket.create_connection((host, port), timeout=TIMEOUT): return "up" diff --git a/tests/homepage_checker.bats b/tests/homepage_checker.bats index edf5090..cc461fb 100644 --- a/tests/homepage_checker.bats +++ b/tests/homepage_checker.bats @@ -19,6 +19,8 @@ setup() { teardown() { [ -n "${LISTENER_PID:-}" ] && kill "$LISTENER_PID" 2>/dev/null + [ -n "${RAW_PID:-}" ] && kill "$RAW_PID" 2>/dev/null + [ -n "${HTTP_PID:-}" ] && kill "$HTTP_PID" 2>/dev/null rm -rf "$TMP" } @@ -43,3 +45,46 @@ assert s["wip"] == "wip", s assert "generated_at" in d PY } + +@test "checker: http method — a non-HTTP listener is down, a real HTTP server is up" { + local svc="$TMP/services.json" out="$TMP/status.json" + local RAW_PORT=18903 HTTP_PORT=18904 + # A raw TCP listener that accepts connections but never speaks HTTP. + # Redirect its output so it cannot hold the test runner's stdout pipe open. + python3 - "$RAW_PORT" >/dev/null 2>&1 <<'PY' & +import socket, threading, time, sys +s = socket.socket() +s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +s.bind(("127.0.0.1", int(sys.argv[1]))) +s.listen(5) +def hold(): + while True: + try: + c, _ = s.accept() + c.close() + except Exception: + break +threading.Thread(target=hold, daemon=True).start() +time.sleep(300) +PY + RAW_PID=$! + # A real HTTP server. + python3 -m http.server "$HTTP_PORT" --bind 127.0.0.1 >/dev/null 2>&1 & + HTTP_PID=$! + sleep 0.5 + cat > "$svc" </dev/null +}