Spaces:
Running on Zero
Running on Zero
File size: 5,389 Bytes
79cf149 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | """Shared harness for the verification scripts.
Three suites -- reporting, ingestion and Drive delivery -- each accumulated
pass/fail, padded a listing and printed a banner in its own way, one of them
through module-level counters. The mechanics were identical, so they live here
and each suite is left holding only its own assertions.
A check records rather than raises. A verification run should report every
finding in one pass: stopping at the first failure hides how much else broke,
which is exactly what you need to know when deciding whether a change is
salvageable.
"""
from dataclasses import dataclass, field
from typing import Callable, List, Optional, Tuple, Type
RULE: str = "=" * 72
DETAIL_LIMIT: int = 70
class Failure(Exception):
"""Raised inside a check to report that it did not hold."""
def require(condition: bool, message: str) -> None:
"""Raises `Failure` with `message` unless `condition` holds."""
if not condition:
raise Failure(message)
@dataclass
class Check:
"""One recorded check, or a section heading when `passed` is None.
Headings share the list so that rendering keeps them interleaved with the
checks they introduce, rather than needing a second structure.
"""
name: str
passed: Optional[bool]
detail: str = ""
@property
def is_heading(self) -> bool:
"""Whether this entry is a section heading rather than a check."""
return self.passed is None
@dataclass
class Report:
"""Accumulates checks and renders them as one listing."""
entries: List[Check] = field(default_factory=list)
# -- recording ---------------------------------------------------------
def section(self, title: str) -> None:
"""Opens a titled group in the listing."""
self.entries.append(Check(title, None))
def check(self, name: str, condition: bool, detail: str = "") -> bool:
"""Records one check and returns its outcome.
The outcome is returned so a caller can skip dependent checks that
would only produce noise once their precondition has failed.
"""
held = bool(condition)
self.entries.append(Check(name, held, detail))
return held
def raises(self, name: str, kind: Type[BaseException],
call: Callable[[], object], detail: str = "") -> bool:
"""Records that `call` raises `kind`, and nothing else.
A wrong exception type is a distinct failure from no exception at all,
and both are reported as what actually happened.
"""
try:
call()
except kind as exc:
return self.check(name, True, detail or str(exc)[:DETAIL_LIMIT])
except Exception as exc: # noqa: BLE001 - the wrong type is the finding
return self.check(name, False,
"raised %s instead: %s" % (type(exc).__name__, exc))
return self.check(name, False, "did not raise %s" % kind.__name__)
def run(self, name: str, call: Callable[[], str]) -> bool:
"""Records a coarse check that returns its own detail, or raises.
For suites whose checks assert internally with `require` and describe
what they observed on the way through.
"""
try:
return self.check(name, True, call())
except Failure as exc:
return self.check(name, False, str(exc))
except Exception as exc: # noqa: BLE001 - report and keep going
return self.check(name, False, "%s: %s" % (type(exc).__name__, exc))
def extend(self, other: "Report") -> None:
"""Absorbs another report's entries, preserving order."""
self.entries.extend(other.entries)
# -- reporting ---------------------------------------------------------
@property
def results(self) -> List[Check]:
"""Every recorded check, headings excluded."""
return [e for e in self.entries if not e.is_heading]
@property
def failures(self) -> List[Check]:
"""The checks that did not hold."""
return [e for e in self.results if not e.passed]
def render(self) -> str:
"""Returns the listing, one line per check, headings interleaved."""
width = max([len(e.name) for e in self.results] + [4])
lines = []
for entry in self.entries:
if entry.is_heading:
lines.extend(["", entry.name])
else:
lines.append(" [%s] %-*s %s" % (
"PASS" if entry.passed else "FAIL", width, entry.name, entry.detail))
results, failed = self.results, self.failures
lines.extend(["", " %d of %d checks passed."
% (len(results) - len(failed), len(results))])
return "\n".join(lines)
def finish(self) -> int:
"""Prints the closing banner and returns a process exit code."""
print("\n" + RULE)
if not self.failures:
print("All %d checks passed." % len(self.results))
return 0
print("FAILED: %d of %d checks did not hold."
% (len(self.failures), len(self.results)))
for entry in self.failures:
print(" - %s: %s" % (entry.name, entry.detail))
return 1
def banner(title: str) -> None:
"""Prints a suite's opening banner."""
print(RULE)
print(title)
print(RULE)
|