#!/usr/bin/env python3 """Static structure contracts for the split Rust source tree. These checks are intentionally compiler-independent. They prevent the module split from regressing into large monoliths, keep unit tests out of implementation files, and catch the class of dangling-attribute/broken-include mistakes that can otherwise surface only at cargo check time. """ from __future__ import annotations from pathlib import Path import re from rust_source import rust_source ROOT = Path(__file__).resolve().parents[2] SRC = ROOT / "src" MAX_LINES = 500 def is_test_source(path: Path) -> bool: relative = path.relative_to(SRC) return path.name == "tests.rs" or path.stem.endswith("_tests") or "tests" in relative.parts include_re = re.compile(r'include!\(\s*"([^"]+\.rs)"\s*\)') test_attr_re = re.compile(r'(?m)^\s*#\[(?:tokio::)?test(?:\([^\]]*\))?\]') dangling_doc_before_close_re = re.compile(r'(?ms)(?:^\s*///[^\n]*\n){1,8}\s*}') dangling_attr_before_close_re = re.compile(r'(?ms)^\s*#\[[^\n]+\]\s*\n\s*}') def delimiters_balanced(text: str) -> bool: stack: list[str] = [] pairs = {"}": "{", ")": "(", "]": "["} i = 0 state = "code" block_depth = 0 raw_hashes = 0 while i < len(text): char = text[i] nxt = text[i + 1] if i + 1 < len(text) else "" if state == "code": if char == "/" and nxt == "/": state = "line"; i += 2; continue if char == "/" and nxt == "*": state = "block"; block_depth = 1; i += 2; continue if char == '"': state = "string"; i += 1; continue if char == "r": raw = re.match(r'r(#{0,16})"', text[i:]) if raw: raw_hashes = len(raw.group(1)); state = "raw"; i += len(raw.group(0)); continue if char == "'": # Lifetimes are identifiers; only enter char mode when a nearby closing quote exists. cursor = i + 1 closing = False while cursor < min(len(text), i + 8): if text[cursor] == "\\": cursor += 2; continue if text[cursor] == "'": closing = True; break if text[cursor] in "\r\n": break cursor += 1 if closing: state = "char"; i += 1; continue if char in "({[": stack.append(char) elif char in "})]": if not stack or stack[-1] != pairs[char]: return False stack.pop() i += 1 elif state == "line": if char == "\n": state = "code" i += 1 elif state == "block": if char == "/" and nxt == "*": block_depth += 1; i += 2 elif char == "*" and nxt == "/": block_depth -= 1; i += 2 if block_depth == 0: state = "code" else: i += 1 elif state == "string": if char == "\\": i += 2 elif char == '"': state = "code"; i += 1 else: i += 1 elif state == "char": if char == "\\": i += 2 elif char == "'": state = "code"; i += 1 else: i += 1 elif state == "raw": terminator = '"' + "#" * raw_hashes if text.startswith(terminator, i): i += len(terminator); state = "code" else: i += 1 return not stack and state in {"code", "line"} # The 500-line rule applies to every Rust source in the repository, including integration tests. for path in sorted(ROOT.rglob("*.rs")): if any(part in {"target", ".git"} for part in path.parts): continue lines = path.read_text(encoding="utf-8").splitlines() assert len(lines) <= MAX_LINES, f"{path.relative_to(ROOT)} has {len(lines)} lines (max {MAX_LINES})" for path in sorted(SRC.rglob("*.rs")): text = path.read_text(encoding="utf-8") lines = text.splitlines() assert delimiters_balanced(text), f"unbalanced delimiters in split Rust source: {path.relative_to(ROOT)}" nonblank = [line.strip() for line in lines if line.strip()] if nonblank: assert not nonblank[-1].startswith(("#[", "///", "//!")), f"dangling Rust attribute/doc comment at EOF: {path.relative_to(ROOT)}" assert not dangling_doc_before_close_re.search(text), f"doc comment before closing brace does not document an item: {path.relative_to(ROOT)}" assert not dangling_attr_before_close_re.search(text), f"attribute before closing brace does not decorate an item: {path.relative_to(ROOT)}" for target in include_re.findall(text): included = path.parent / target assert included.is_file(), f"broken include! in {path.relative_to(ROOT)}: {target}" included_text = included.read_text(encoding="utf-8") # include! expands into the caller's existing module/item scope. Inner module docs # (`//!`) in a split fragment are therefore invalid once another item precedes them. assert not re.search(r'(?m)^\s*//!', included_text), ( f"inner module doc comment in include! fragment: {included.relative_to(ROOT)}" ) if not is_test_source(path): assert not test_attr_re.search(text), f"unit test remains in implementation source: {path.relative_to(ROOT)}" # Test-only implementation must live in a separate file and be pulled in explicitly. for match in re.finditer(r'(?m)^\s*#\[cfg\(test\)\]\s*$', text): tail = text[match.end():].lstrip() assert tail.startswith("include!(") or tail.startswith("mod tests;"), ( f"test cfg in implementation must target a separate test file: {path.relative_to(ROOT)}" ) # Explicit regressions from the first Web Server split. response = (SRC / "web_server/response.rs").read_text(encoding="utf-8") assert not response.rstrip().endswith("#[derive(Debug, Clone)]"), "dangling derive returned in Web Server response module" fastcgi = rust_source(ROOT, "src/web_server/fastcgi.rs") request = (SRC / "web_server/request.rs").read_text(encoding="utf-8") assert "use anyhow::{Context" in fastcgi or "Context," in fastcgi or ", Context" in fastcgi assert "htaccess," in fastcgi assert "request::server_protocol" in fastcgi assert "pub(super) fn server_protocol" in request headers = (SRC / "web_server/response/headers.rs").read_text(encoding="utf-8") assert "htaccess," in headers, "Web Server response headers must import sibling htaccess after module split" assert "ACCEPT_ENCODING," not in headers, "stale ACCEPT_ENCODING import reintroduced in response headers" # Regressions exposed by a real `cargo check --locked --all-targets` after the 0.25.1 split. assert "pub(super) trait FastCgiIo" in fastcgi, "FastCgiIo visibility must match sibling-visible FastCGI helpers" for relative in ("src/proxy/websocket/tests.rs", "tests/http3.rs"): text = (ROOT / relative).read_text(encoding="utf-8") assert 'parse::().unwrap()' not in text, ( f"{relative} uses Result::unwrap() for h3::ext::Protocol even though InvalidProtocol has no Debug" ) server_tests = (SRC / "server/serve/tests.rs").read_text(encoding="utf-8") assert server_tests.count("serve_inner(") == server_tests.count("Http2Connections::default()"), ( "direct serve_inner test calls must pass the Http2Connections argument" ) cache_policy_tests = (SRC / "cache/tests/tests/part_03.rs").read_text(encoding="utf-8") assert "let plan = plan(" not in cache_policy_tests, "cache policy test shadows the plan() function" cold_tests = (SRC / "cache/tests/cold_tier_tests/part_02.rs").read_text(encoding="utf-8") for field in ("scheme:", "authority:", "target:", "tags:", "created_at:"): assert field in cold_tests, f"ColdEntry test fixture is missing current field {field}" admin_tests = (SRC / "admin/tests/part_02.rs").read_text(encoding="utf-8") assert "let configuration = router\n .clone()\n .oneshot(" in admin_tests, ( "admin persistence test must retain Router before Tower::oneshot consumes a clone" ) web_tests_1 = (SRC / "web_server/tests/part_01.rs").read_text(encoding="utf-8") assert "ETAG, EXPIRES," not in web_tests_1, "unused EXPIRES import returned in Web Server tests" print(f"PASS: Rust modules are <= {MAX_LINES} lines, test code is isolated, include! targets resolve, and split-module/all-target regressions are guarded.")