diff --git a/test/infamy/tap.py b/test/infamy/tap.py index b1938326..1b12de29 100644 --- a/test/infamy/tap.py +++ b/test/infamy/tap.py @@ -1,3 +1,32 @@ +"""TAP (Test Anything Protocol) output helpers for Infamy tests. + +TAP is a simple line-based protocol for communicating test results between a +test producer and a consumer (harness). The canonical reference is: + + https://testanything.org/tap-specification.html + +The key elements are: + + Plan line: ``1..N`` — total number of tests, written once + Result lines: ``ok N - desc`` — test N passed + ``not ok N - desc``— test N failed + Directives: ``ok N # SKIP`` ``not ok N # TODO`` + Diagnostics: Any line beginning with ``#`` is a comment/diagnostic. + +The plan line may appear either at the beginning (before any results) or at +the end (after all results). Infamy writes it at the END so that the total +step count is known; a harness that requires an upfront plan will report +"no plan" on failure — see CommentWriter below for a known limitation. + +``CommentWriter`` redirects Python's ``sys.stdout`` so that ordinary +``print()`` calls are prefixed with ``# `` and appear as diagnostics, while +TAP result lines (written via ``self.out``, the *original* stdout) are emitted +verbatim. The plan line is also written via ``self.out``. + +Note: because ``sys.stdout`` is replaced *after* module import the default +argument ``output=sys.stdout`` captures the original stream at import time, +so nested or re-entrant ``Test()`` instances will share the same ``self.out``. +""" import contextlib import datetime import subprocess @@ -41,7 +70,7 @@ class Test: self._not_ok("Missing explicit test result\n") else: if t in (TestPass, TestSkip): - self.out.write(f"{self.steps}..{self.steps}\n") + self.out.write(f"1..{self.steps}\n") self.out.flush() raise SystemExit(0) if t is AssertionError: @@ -56,6 +85,8 @@ class Test: elif len(e.args) and type(e.args[0]) is subprocess.CompletedProcess: print("Failing subprocess stdout:\n", e.args[0].stdout) + self.out.write(f"1..{self.steps}\n") + self.out.flush() raise SystemExit(1) @contextlib.contextmanager