Files
buzz/scripts/check-postgres-test-discovery.py
Luke Tornquist bd73490418 ci: run PostgreSQL tests in isolated lane (#6730)
## Why

PR #6660 introduced four PostgreSQL-backed persistence tests and a
focused CI selector, but an exact test-name list does not automatically
cover future database tests. Broad ignored-test execution also exposed
shared-schema races and ambiguity between desired-state and
migration-applied schema expectations.

## What

- Establish a discoverable convention: PostgreSQL unit modules use
`postgres_tests`, PostgreSQL integration binaries use a `postgres_`
prefix, and unrelated external-infrastructure tests use an
`external_infra_` prefix.
- Add a dedicated nextest PostgreSQL profile and archive-backed CI job
covering the relevant crates without enumerating test names.
- Create a run-scoped desired-state source database and a unique
PostgreSQL database per test process for parallel-safe isolation, with
cleanup on success, failure, or interruption.
- Route destructive migration and migration-parity tests to clean
`template0` databases while desired-state tests clone the desired-state
database.
- Document discovery, schema modes, required role privileges, and a
bounded portable local runner.
- Keep the existing infrastructure-free unit-test jobs unchanged.

This is intentionally limited to test and CI harness behavior. It does
not move production database code, change persistence semantics, or
implement issue #20's broader shared test-utility refactor.

## Risk

Low production risk because all changes are confined to tests, CI
configuration, documentation, and test harness scripts.

Remaining operational risks:

- Cleanup retries `dropdb --force` five times across roughly four
seconds. Exhaustion warns with the database name but deliberately does
not mask the test result; individual PostgreSQL diagnostics are
suppressed.
- The seven-package boundary is duplicated between the runner and CI
archive and must remain synchronized if PostgreSQL tests move to a new
crate.
- Portable SHA fallback branches were exercised on Linux; no macOS Blox
workstation was needed for this Linux CI artifact.
- GitHub's unchanged generic Unit Tests job currently fails while
cold-linking `buzz-voice` because `sherpa-onnx-c-api` is absent. The
branch changes no voice/build/toolchain inputs; the exact parent
previously passed that job, and Blox passes the same infrastructure-free
suite (1,450/1,450). A prior-head one-job retry and the final-head run
both reproduced the hosted-runner failure.

## Verification

Author Blox workstation `2020088`:

- `cargo fmt --all -- --check`
- `just clippy`
- `just test-unit`: 1,450 passed; PostgreSQL tests remain skipped in
fast jobs
- PR #6660 focused tests: 4/4 passed through the final runner
- Full lane: 282/282 passed in 10.087s with a non-superuser role limited
to `CREATEDB`, `CREATEROLE`, and `pg_read_all_stats`
  - 272 desired-state tests
  - 10 migration-applied tests
  - 6 explicitly filtered external-infrastructure tests
- Cleanup fault injection: a deliberately underprivileged 282-test run
produced the expected 277 passes and 5 failures, and the post-run
catalog audit found zero lane databases after retry cleanup. The
restored successful run also left zero lane databases.

GitHub exact final head `92c231e48f299ea2af23817763f5ae80ac013d68`:

- [PostgreSQL
Tests](https://github.com/block/buzz/actions/runs/32789030203/job/97630827823):
282/282 passed across 10 binaries in 98.886s; 1,240 skipped, including 6
via the profile filter
- The shared relay/PostgreSQL archive predecessor completed successfully
- [Unit
Tests](https://github.com/block/buzz/actions/runs/32789030203/job/97626930420):
unrelated `sherpa-onnx-c-api` native-link failure described above
- Independent exact-head review on separate Blox workstation `2022762`:
no substantive findings after all initial findings were addressed. The
reviewer independently verified cleanup retry behavior, exit-status
preservation, shell portability, syntax, and a clean exact-head
worktree.

## References

- Stacked on #6660 at exact head
`561de54be4d9c2b622b7c2aa5b61bc3068f47e2a`
- https://github.com/TheSentinel454/buzz/issues/20
-
https://github.com/TheSentinel454/buzz/issues/20#issuecomment-5398526792

Generated with Codex.

## Update — August 24, 2026 review follow-up

- Centralized the repeated PostgreSQL test URL resolution in
crate-local, test-only helpers without introducing the broader shared
utility refactor from issue #20.
- Restored descriptive hybrid/Redis test function names. Structural
`external_infra_*_tests` modules now own exclusion, and the nextest
filter only recognizes module path segments.
- Added a three-second source guard that scans every Rust file and fails
CI when an ignored PostgreSQL test would be omitted or an
external-infrastructure test would be included. Fixture tests cover
accepted modules/binaries and both failure modes.
- Removed the two implementation-plan documents.

Performance profiling on Blox workstation `2027352` (same prebuilt
archive and cargo-nextest 0.9.143 for every comparison):

- Current per-test database model, 8 workers: 10.15–10.40s across three
runs. The 283 `createdb` calls and 284 `dropdb` calls consumed
28.85–29.46 aggregate seconds, about 39–40% of aggregate test-process
duration.
- Reusing one database per worker without cleanup: 6.55–6.84s, about 35%
faster, but all three runs failed because global matcher-queue tests
inherited a quiesced community from an earlier test; the failing test
varied with scheduling.
- Reusing workers with `TRUNCATE … RESTART IDENTITY CASCADE`: all 282
tests passed, but total time regressed to 10.47–10.67s. The 265 truncate
calls consumed 24.34–25.45 aggregate seconds, so truncation merely
replaced most clone/drop cost.
- Raising concurrency to 16 workers reduced one isolated-database run to
8.79s, but 24 workers exposed a cluster-global `pg_stat_activity` race.
The profile remains at the proven-safe 8 workers.

Decision: retain unique per-test databases. Harness cleanup uses `dropdb
--if-exists --force`; it drops the database rather than truncating
tables or deleting rows.

Verification at local branch head
`60086c1f6ee34a193c342255a5e8e6293b60e988`:

- Discovery inventory: exactly 282 intended tests; all 6 hybrid/Redis
tests present and structurally excluded.
- PR #6660 focused persistence tests: 4/4 passed.
- Full PostgreSQL lane: 282/282 passed three times; 1,240 skipped each
run; wall time 9.72–9.96s without profiling shims.
- Non-ignored tests with a desired-state database: 1,234/1,234 passed;
one mesh-demo timeout from the first run passed immediately in
isolation.
- `cargo fmt --all -- --check` and all-target/all-feature clippy for
`buzz-db`, `buzz-deletion`, and `buzz-relay` passed.

Final independent re-review at exact head
`42097c0136ab3fa8f0efe5720ae829c04575b9b6` on separate Blox workstation
`2028455`: no substantive residual findings. The reviewer independently
exercised ordinary strings, zero/one/three-hash raw strings, line/block
comments, and string-contained lookalikes, then reran the full 353-file
scan, shell syntax, Python AST parsing, and diff check.

Independent review found that the source guard's initial regular
expression could miss raw-string ignore reasons and treat a commented
attribute as real. Final head `42097c0136ab3fa8f0efe5720ae829c04575b9b6`
parses valid ordinary/raw Rust string literals only at attributes found
in comment-sanitized source; regression fixtures cover both cases. The
fixture suite, Python compilation, shell syntax, diff check, and full
353-file repository scan pass on Blox.

## Update — August 25, 2026 restack

PR #6660 merged, so this follow-up was rebased from its former exact
parent onto current `origin/main`
(`8d2d0ff5ad42733e9949442c4b6358d0ba87f9a8`). The final candidate head
is `05dcc2ab28948e3ab79bb44839a69f2ba44648a2`; the PR no longer carries
#6660's pre-squash history.

Fresh exact-head verification on Blox workstation `2028572`:

- Discovery guard passed across 353 Rust files; inventory remained
exactly 282 intended PostgreSQL tests.
- PR #6660 focused persistence tests: 4/4 passed.
- Full PostgreSQL lane: 282/282 passed three times; 1,243 skipped each
run; wall time 9.70–9.87s.
- `cargo fmt --all -- --check` and all-target/all-feature clippy for
`buzz-db`, `buzz-deletion`, and `buzz-relay` passed.

## Update — August 25, 2026 final hosted verification

- [GitHub PostgreSQL
Tests](https://github.com/block/buzz/actions/runs/32801679226/job/97667835080):
282/282 passed in 53.787s; 1,243 skipped.
- The final guard scanned all 353 Rust files successfully before the
hosted lane ran.
- The shared relay/archive prerequisite and infrastructure-free Unit
Tests job both passed at exact final head
`42097c0136ab3fa8f0efe5720ae829c04575b9b6`.

---------

Signed-off-by: Luke Tornquist <tornquist@squareup.com>
Signed-off-by: tornquist <tornquist@squareup.com>
2026-09-01 12:07:28 -04:00

435 lines
16 KiB
Python
Executable File

#!/usr/bin/env python3
"""Validate structural discovery for ignored PostgreSQL-backed Rust tests."""
from __future__ import annotations
import re
import sys
from pathlib import Path
try:
import tomllib
except ModuleNotFoundError: # Python 3.9 on supported macOS development hosts.
tomllib = None
IGNORE_ATTRIBUTE = re.compile(r"#\s*\[\s*ignore\s*=")
BARE_IGNORE_ATTRIBUTE = re.compile(r"#\s*\[\s*ignore\s*\]")
FUNCTION = re.compile(r"\b(?:async\s+)?fn\s+(?P<name>[A-Za-z_][A-Za-z0-9_]*)")
MODULE = re.compile(r"\bmod\s+(?P<name>[A-Za-z_][A-Za-z0-9_]*)\s*\{")
OUT_OF_LINE_MODULE = re.compile(r"\bmod\s+(?P<name>[A-Za-z_][A-Za-z0-9_]*)\s*;")
PATH_ATTRIBUTE = re.compile(r"#\s*\[\s*path\s*=\s*")
EXTERNAL_INFRA = re.compile(r"\b(?:s3|minio|storage|docker|network)\b", re.IGNORECASE)
RAW_STRING = re.compile(r'(?:b?r)(?P<hashes>#{0,255})"')
CHAR_LITERAL = re.compile(r"(?:b)?'(?:\\(?:u\{[0-9A-Fa-f_]+\}|x[0-9A-Fa-f]{2}|.)|[^\\'\n])'")
TOML_SECTION = re.compile(r"^\s*\[(?P<name>[^]]+)]\s*(?:#.*)?$")
TOML_PACKAGE_NAME = re.compile(
r"^\s*name\s*=\s*(?P<quote>['\"])(?P<name>[A-Za-z0-9_-]+)(?P=quote)\s*(?:#.*)?$"
)
def sanitize_rust(source: str) -> str:
"""Blank comments and literals while preserving byte offsets and braces."""
chars = list(source)
index = 0
length = len(source)
while index < length:
if source.startswith("//", index):
end = source.find("\n", index + 2)
end = length if end == -1 else end
for offset in range(index, end):
chars[offset] = " "
index = end
continue
if source.startswith("/*", index):
start = index
depth = 1
index += 2
while index < length and depth:
if source.startswith("/*", index):
depth += 1
index += 2
elif source.startswith("*/", index):
depth -= 1
index += 2
else:
index += 1
for offset in range(start, index):
if chars[offset] != "\n":
chars[offset] = " "
continue
character = CHAR_LITERAL.match(source, index)
if character:
start = index
index = character.end()
for offset in range(start, index):
chars[offset] = " "
continue
raw = RAW_STRING.match(source, index)
if raw:
start = index
terminator = '"' + raw.group("hashes")
index = raw.end()
end = source.find(terminator, index)
index = length if end == -1 else end + len(terminator)
for offset in range(start, index):
if chars[offset] != "\n":
chars[offset] = " "
continue
quote_start = index
if source.startswith('b"', index):
index += 1
if source[index] == '"':
index += 1
while index < length:
if source[index] == "\\":
index += 2
elif source[index] == '"':
index += 1
break
else:
index += 1
for offset in range(quote_start, min(index, length)):
if chars[offset] != "\n":
chars[offset] = " "
continue
index += 1
return "".join(chars)
def parse_rust_string_literal(source: str, start: int) -> tuple[str, int] | None:
"""Parse an ordinary or raw Rust string literal at or after start."""
index = start
while index < len(source) and source[index].isspace():
index += 1
raw = RAW_STRING.match(source, index)
if raw:
content_start = raw.end()
terminator = '"' + raw.group("hashes")
content_end = source.find(terminator, content_start)
if content_end == -1:
return None
return source[content_start:content_end], content_end + len(terminator)
if index >= len(source) or source[index] != '"':
return None
index += 1
content = []
while index < len(source):
if source[index] == "\\":
if index + 1 >= len(source):
return None
content.append(source[index + 1])
index += 2
elif source[index] == '"':
return "".join(content), index + 1
else:
content.append(source[index])
index += 1
return None
def ignore_attributes(source: str, sanitized: str) -> list[tuple[int, int, str]]:
"""Return real ignore attributes and reasons, excluding comments."""
attributes = []
for match in IGNORE_ATTRIBUTE.finditer(sanitized):
parsed = parse_rust_string_literal(source, match.end())
if parsed is None:
continue
reason, literal_end = parsed
attribute_end = literal_end
while attribute_end < len(source) and source[attribute_end].isspace():
attribute_end += 1
if attribute_end >= len(source) or source[attribute_end] != "]":
continue
attributes.append((match.start(), attribute_end + 1, reason))
return attributes
def module_ranges(source: str) -> list[tuple[int, int, str]]:
sanitized = sanitize_rust(source)
brace_pairs: dict[int, int] = {}
stack: list[int] = []
for index, char in enumerate(sanitized):
if char == "{":
stack.append(index)
elif char == "}" and stack:
brace_pairs[stack.pop()] = index
ranges = []
for match in MODULE.finditer(sanitized):
open_brace = sanitized.find("{", match.start(), match.end())
close_brace = brace_pairs.get(open_brace)
if close_brace is not None:
ranges.append((open_brace, close_brace, match.group("name")))
return ranges
def crate_root(path: Path) -> Path | None:
for candidate in path.parents:
if (candidate / "Cargo.toml").is_file():
return candidate
return None
def integration_binary_is_postgres(path: Path) -> bool:
root = crate_root(path)
return (
root is not None
and path.parent == root / "tests"
and path.name.startswith("postgres_")
)
def standard_module_paths(parent_source: Path, module_name: str) -> list[Path]:
"""Resolve Rust's conventional out-of-line module source paths."""
root = crate_root(parent_source)
is_crate_root = parent_source.name in {"lib.rs", "main.rs", "mod.rs"}
if root is not None:
is_crate_root = is_crate_root or (
parent_source.parent == root / "tests"
or parent_source.parent == root / "examples"
or parent_source.parent == root / "benches"
or parent_source.parent == root / "src" / "bin"
)
base = (
parent_source.parent
if is_crate_root
else parent_source.parent / parent_source.stem
)
return [base / f"{module_name}.rs", base / module_name / "mod.rs"]
def out_of_line_module_index(files: list[Path]) -> dict[Path, list[str]]:
"""Index explicit and conventional module names by resolved source file."""
names: dict[Path, list[str]] = {}
context_files = set(files)
for path in files:
if (root := crate_root(path)) is not None:
context_files.update(root.rglob("*.rs"))
for directory in {path.parent for path in files}:
context_files.update(directory.glob("*.rs"))
for parent_source in sorted(context_files):
source = parent_source.read_text(encoding="utf-8")
sanitized = sanitize_rust(source)
if "path" in source:
for attribute in PATH_ATTRIBUTE.finditer(sanitized):
equals = source.find("=", attribute.start(), attribute.end())
parsed = parse_rust_string_literal(source, equals + 1)
if parsed is None:
continue
module_path, literal_end = parsed
module = OUT_OF_LINE_MODULE.search(sanitized, literal_end)
if module is not None:
resolved_path = (parent_source.parent / module_path).resolve()
module_names = names.setdefault(resolved_path, [])
if module.group("name") not in module_names:
module_names.append(module.group("name"))
for module in OUT_OF_LINE_MODULE.finditer(sanitized):
module_name = module.group("name")
for candidate in standard_module_paths(parent_source, module_name):
if candidate.is_file():
module_names = names.setdefault(candidate.resolve(), [])
if module_name not in module_names:
module_names.append(module_name)
return names
def package_name(root: Path) -> str:
"""Read Cargo's package name with a Python 3.9-compatible fallback."""
manifest_path = root / "Cargo.toml"
if tomllib is not None:
try:
with manifest_path.open("rb") as manifest:
package = tomllib.load(manifest).get("package", {})
except tomllib.TOMLDecodeError as error:
raise ValueError(f"invalid Cargo manifest {manifest_path}: {error}") from error
name = package.get("name")
if isinstance(name, str) and name:
return name
else:
in_package = False
for line in manifest_path.read_text(encoding="utf-8").splitlines():
section = TOML_SECTION.match(line)
if section is not None:
in_package = section.group("name").strip() == "package"
continue
if in_package and (match := TOML_PACKAGE_NAME.match(line)) is not None:
return match.group("name")
raise ValueError(f"discoverable PostgreSQL tests lack a package name: {root}")
def file_has_postgres_lane_test(
path: Path, out_of_line_modules: dict[Path, list[str]]
) -> bool:
source = path.read_text(encoding="utf-8")
sanitized = sanitize_rust(source)
ranges = module_ranges(source)
external_modules = out_of_line_modules.get(path.resolve(), [])
for attribute_start, _attribute_end, reason in ignore_attributes(source, sanitized):
reason_lower = reason.lower()
modules = [name for start, end, name in ranges if start < attribute_start < end]
if (
("postgres" in reason_lower or "postgresql" in reason_lower)
and not EXTERNAL_INFRA.search(reason)
and not any(name.startswith("external_infra") for name in modules)
and (
any(name.endswith("postgres_tests") for name in modules + external_modules)
or integration_binary_is_postgres(path)
)
):
return True
return False
def postgres_packages(
files: list[Path], out_of_line_modules: dict[Path, list[str]]
) -> list[str]:
roots = {
root
for path in files
if file_has_postgres_lane_test(path, out_of_line_modules)
if (root := crate_root(path)) is not None
}
return sorted(package_name(root) for root in roots)
def validate_file(
path: Path, out_of_line_modules: dict[Path, list[str]]
) -> list[str]:
source = path.read_text(encoding="utf-8")
sanitized = sanitize_rust(source)
ranges = module_ranges(source)
external_modules = out_of_line_modules.get(path.resolve(), [])
errors = []
for match in BARE_IGNORE_ATTRIBUTE.finditer(sanitized):
function = FUNCTION.search(sanitized, match.end())
if function is None:
errors.append(f"{path}: ignored infrastructure test has no following function")
continue
modules = [name for start, end, name in ranges if start < match.start() < end]
in_postgres_structure = (
any(name.endswith("postgres_tests") for name in modules + external_modules)
or integration_binary_is_postgres(path)
)
if in_postgres_structure:
errors.append(
f"{path}:{source.count(chr(10), 0, function.start()) + 1}: "
f"{function.group('name')} uses bare #[ignore] in PostgreSQL discovery; "
'use #[ignore = "requires PostgreSQL"] or an explicit external-infra reason'
)
for attribute_start, attribute_end, reason in ignore_attributes(source, sanitized):
reason_lower = reason.lower()
mentions_postgres = "postgres" in reason_lower or "postgresql" in reason_lower
mentions_redis = "redis" in reason_lower
if not mentions_postgres and not mentions_redis:
continue
function = FUNCTION.search(sanitized, attribute_end)
if function is None:
errors.append(f"{path}: ignored infrastructure test has no following function")
continue
function_name = function.group("name")
modules = [
name for start, end, name in ranges if start < attribute_start < end
]
in_postgres_lane = (
any(name.endswith("postgres_tests") for name in modules + external_modules)
or integration_binary_is_postgres(path)
)
in_external_module = any(name.startswith("external_infra") for name in modules)
needs_external_infra = bool(EXTERNAL_INFRA.search(reason))
if mentions_redis and not mentions_postgres and in_postgres_lane and not in_external_module:
errors.append(
f"{path}:{source.count(chr(10), 0, function.start()) + 1}: "
f"{function_name} is Redis-only but sits inside PostgreSQL discovery; "
"move it under an external_infra* module"
)
elif needs_external_infra and not in_external_module:
errors.append(
f"{path}:{source.count(chr(10), 0, function.start()) + 1}: "
f"{function_name} requires infrastructure beyond PostgreSQL/Redis; "
"move it under an external_infra* module"
)
elif mentions_postgres and in_external_module and not needs_external_infra:
errors.append(
f"{path}:{source.count(chr(10), 0, function.start()) + 1}: "
f"{function_name} requires only PostgreSQL/Redis but is excluded by an "
"external_infra* module; move it into PostgreSQL discovery"
)
elif mentions_postgres and not needs_external_infra and not in_postgres_lane:
errors.append(
f"{path}:{source.count(chr(10), 0, function.start()) + 1}: "
f"{function_name} requires PostgreSQL but is not discoverable; "
"place it under postgres_tests or in a postgres_* integration binary"
)
return errors
def rust_files(arguments: list[str]) -> list[Path]:
files = []
for argument in arguments:
path = Path(argument)
if path.is_dir():
files.extend(candidate for candidate in path.rglob("*.rs") if "target" not in candidate.parts)
elif path.suffix == ".rs":
files.append(path)
else:
raise ValueError(f"not a Rust source file or directory: {path}")
return sorted(set(files))
def main() -> int:
arguments = sys.argv[1:]
print_packages = bool(arguments and arguments[0] == "--print-packages")
if print_packages:
arguments = arguments[1:]
if not arguments:
print(
f"usage: {Path(sys.argv[0]).name} [--print-packages] "
"<Rust source file or directory> [...]",
file=sys.stderr,
)
return 2
try:
files = rust_files(arguments)
except ValueError as error:
print(error, file=sys.stderr)
return 2
out_of_line_modules = out_of_line_module_index(files)
errors = [
error
for path in files
for error in validate_file(path, out_of_line_modules)
]
if errors:
print("PostgreSQL test discovery validation failed:", file=sys.stderr)
for error in errors:
print(f" - {error}", file=sys.stderr)
return 1
if print_packages:
try:
packages = postgres_packages(files, out_of_line_modules)
except (OSError, ValueError) as error:
print(error, file=sys.stderr)
return 1
for package in packages:
print(package)
return 0
print(f"validated PostgreSQL test discovery across {len(files)} Rust source files")
return 0
if __name__ == "__main__":
raise SystemExit(main())