Files
wehub-resource-sync 7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

228 lines
8.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Pre-commit hook to detect tautological / no-op test functions.
These patterns pass regardless of whether the system-under-test (SUT) works,
so they provide false confidence and inflate coverage without catching
regressions. A 2026 test-suite audit removed ~3,700 such tests
(see docs/processes/test-review/README.md); this hook prevents reintroduction.
Patterns flagged (each is *never* a legitimate assertion):
1. ASSERT_TRUE — a test whose only assertion(s) are ``assert True``
(or ``assert <constant-truthy-literal>``). Verifies nothing.
2. IMPORT_EXISTENCE — body is exactly ``from M import X`` + ``assert X is
not None``. The import already raises ImportError if X is missing, so
the assert pins nothing the import didn't already.
3. TAUTOLOGY_OR — ``assert <anything> or True`` (always True).
4. NULLCHECK_TAUTOLOGY — ``assert x is None or x is not None`` (or the
reverse ordering) on the same operand. Accepts every possible value.
Opt-out: add ``# allow: weak-test`` on the ``def`` line or the assert line.
Use sparingly and only with a comment explaining why the pattern is
intentional (e.g. a deliberately-minimal smoke test).
"""
import ast
import re
import sys
SUPPRESS_RE = re.compile(r"#\s*allow:\s*weak-test(?:\s|$)")
def _strip_docstring(body: list[ast.stmt]) -> list[ast.stmt]:
if (
body
and isinstance(body[0], ast.Expr)
and isinstance(body[0].value, ast.Constant)
and isinstance(body[0].value.value, str)
):
return body[1:]
return body
def _is_truthy_constant(node: ast.expr) -> bool:
"""True for ``True`` / non-empty literal constants that are always truthy."""
return isinstance(node, ast.Constant) and bool(node.value)
def _is_none(node: ast.expr) -> bool:
return isinstance(node, ast.Constant) and node.value is None
def _operand_src(node: ast.expr) -> str:
try:
return ast.unparse(node)
except Exception: # pragma: no cover - unparse is stable on 3.9+
return repr(node)
def _is_nullcheck_tautology(test: ast.expr) -> bool:
"""Detect ``x is None or x is not None`` (either ordering, same operand)."""
if not (isinstance(test, ast.BoolOp) and isinstance(test.op, ast.Or)):
return False
if len(test.values) != 2:
return False
left, right = test.values
if not (isinstance(left, ast.Compare) and isinstance(right, ast.Compare)):
return False
if not (len(left.ops) == 1 and len(right.ops) == 1):
return False
left_is = isinstance(left.ops[0], ast.Is)
left_isnot = isinstance(left.ops[0], ast.IsNot)
right_is = isinstance(right.ops[0], ast.Is)
right_isnot = isinstance(right.ops[0], ast.IsNot)
# Both comparisons must be against None
if not (_is_none(left.comparators[0]) and _is_none(right.comparators[0])):
return False
# Same operand on both sides
if _operand_src(left.left) != _operand_src(right.left):
return False
# One "is None" and one "is not None" => covers all values
return (left_is and right_isnot) or (left_isnot and right_is)
def _has_or_true(test: ast.expr) -> bool:
"""Detect ``<anything> or True`` (or ``True or <anything>``)."""
if not (isinstance(test, ast.BoolOp) and isinstance(test.op, ast.Or)):
return False
return any(_is_truthy_constant(v) for v in test.values)
class WeakTestChecker(ast.NodeVisitor):
def __init__(self, filepath: str, lines: list[str]):
self.filepath = filepath
self.lines = lines
self.issues: list[tuple[int, str]] = []
def _suppressed(self, lineno: int) -> bool:
idx = lineno - 1
return 0 <= idx < len(self.lines) and bool(
SUPPRESS_RE.search(self.lines[idx])
)
def _has_skip_marker(self, node: ast.FunctionDef) -> bool:
for dec in node.decorator_list:
src = _operand_src(dec)
if "skip" in src or "xfail" in src:
return True
return False
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
if node.name.startswith("test_") and not self._has_skip_marker(node):
self._check_test(node)
self.generic_visit(node)
def _check_test(self, node: ast.FunctionDef) -> None:
# Function-level opt-out (on the def line).
if self._suppressed(node.lineno):
return
body = _strip_docstring(node.body)
asserts = [s for s in body if isinstance(s, ast.Assert)]
# Pattern 3 & 4: per-assert tautologies (anywhere in the body).
for a in asserts:
if self._suppressed(a.lineno):
continue
if _has_or_true(a.test):
self.issues.append(
(a.lineno, "assertion is always true (`... or True`)")
)
elif _is_nullcheck_tautology(a.test):
self.issues.append(
(
a.lineno,
"assertion `x is None or x is not None` accepts any value",
)
)
if not asserts:
# Pattern 2: import-existence tautology (no Assert means it can't
# match here; handled below only when an assert exists).
return
# Pattern 1: every assertion is a truthy literal (e.g. `assert True`).
if all(_is_truthy_constant(a.test) for a in asserts):
if not self._suppressed(node.lineno):
self.issues.append(
(
node.lineno,
f"test '{node.name}' has no real assertion "
"(only `assert <truthy-literal>`)",
)
)
# Pattern 2: body is exactly import(s) + `assert X is not None`.
non_import = [
s for s in body if not isinstance(s, (ast.Import, ast.ImportFrom))
]
if (
len(non_import) == 1
and isinstance(non_import[0], ast.Assert)
and len(body) > 1 # at least one import present
):
test = non_import[0].test
if (
isinstance(test, ast.Compare)
and len(test.ops) == 1
and isinstance(test.ops[0], ast.IsNot)
and _is_none(test.comparators[0])
):
if not self._suppressed(node.lineno):
self.issues.append(
(
node.lineno,
f"test '{node.name}' is an import-existence "
"tautology (the import already fails if the "
"symbol is missing)",
)
)
def check_file(filepath: str) -> list[tuple[int, str]]:
try:
with open(filepath, encoding="utf-8") as fh:
source = fh.read()
except (OSError, UnicodeDecodeError):
return []
try:
tree = ast.parse(source)
except SyntaxError:
# Leave syntax errors to other tools (ruff / py_compile).
return []
checker = WeakTestChecker(filepath, source.splitlines())
checker.visit(tree)
return sorted(checker.issues)
def main(argv: list[str]) -> int:
files = [
f
for f in argv
if f.endswith(".py")
and ("/test_" in f or f.startswith("test_") or "/tests/" in f)
]
found = False
for filepath in files:
for lineno, message in check_file(filepath):
found = True
print(f"{filepath}:{lineno}: {message}")
if found:
print(
"\nWeak/tautological test patterns detected (see above).\n"
"These pass regardless of whether the code works. Either assert a "
"real outcome, or — if the no-op is intentional (e.g. a minimal "
"smoke test) — add `# allow: weak-test` with a justifying comment.\n"
"Background: docs/processes/test-review/README.md"
)
return 1
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))