iop/scripts/readability_audit_test.py

2317 lines
94 KiB
Python

#!/usr/bin/env python3
"""Unit tests for readability_audit.py.
Run with:
python3 -m unittest scripts.readability_audit_test
Tests use temporary fixture trees, temporary git repositories, and in-memory
baselines to verify:
- tracked inputs sorting and exclusion
- language-aware file-kind classification and threshold boundaries
- multi-language function identity (raw/multiline/comment/heredoc aware)
- ratchet identity where level drops pass and new/increased identities fail
- strict baseline schema validation
- task read-set configuration failures, budget/reason, and total ratchet
- CLI exit codes and rendered failure output
- input mode: tracked vs worktree projection (modified/deleted/untracked)
"""
import json
import os
import shutil
import subprocess
import sys
import tempfile
import textwrap
import unittest
# Ensure the audit module is importable.
SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, SCRIPTS_DIR)
import readability_audit as ra
AUDIT_SCRIPT = os.path.join(SCRIPTS_DIR, "readability_audit.py")
class _RepoFixture(unittest.TestCase):
"""Base fixture: a temporary directory plus optional git repository."""
prefix = "ra-test-"
def setUp(self):
self.tmpdir = tempfile.mkdtemp(prefix=self.prefix)
def tearDown(self):
shutil.rmtree(self.tmpdir, ignore_errors=True)
def _write(self, relpath: str, content: str) -> None:
full = os.path.join(self.tmpdir, relpath)
os.makedirs(os.path.dirname(full) or self.tmpdir, exist_ok=True)
with open(full, "w") as f:
f.write(content)
def _git(self, *args: str) -> subprocess.CompletedProcess:
return subprocess.run(["git", *args], check=True, cwd=self.tmpdir,
capture_output=True)
def _git_init(self) -> None:
subprocess.run(["git", "init", "-q", self.tmpdir], check=True,
capture_output=True)
self._git("config", "user.email", "test@test")
self._git("config", "user.name", "Test")
self._git("add", "-A")
self._git("commit", "-q", "-m", "init")
# ============================================================
# TestTrackedInputsSortedAndExcluded
# ============================================================
class TestTrackedInputsSortedAndExcluded(unittest.TestCase):
"""Input filtering: tracked languages only, excluded dirs removed, sorted."""
def test_tracked_languages_only_and_excluded(self) -> None:
original = ra._run_git_ls_files_z
ra._run_git_ls_files_z = lambda: [
"src/main.go", "src/util.py",
"proto/gen/generate.pb.go",
"apps/client/lib/gen/api.dart",
"build/vendor/lib.go",
"scripts/run.sh",
"src/lib.kt",
"src/view.swift",
"src/app.dart",
"src/data.json",
]
try:
tracked = [f for f in ra._run_git_ls_files_z() if not ra._should_exclude(f)]
tracked_lang = sorted(f for f in tracked if ra._detect_language(f))
self.assertEqual(tracked_lang, [
"scripts/run.sh", "src/app.dart", "src/lib.kt",
"src/main.go", "src/util.py", "src/view.swift",
])
self.assertNotIn("src/data.json", tracked_lang)
finally:
ra._run_git_ls_files_z = original
def test_exclusion_patterns(self) -> None:
self.assertTrue(ra._should_exclude("proto/gen/foo.go"))
self.assertTrue(ra._should_exclude("apps/client/lib/gen/bar.dart"))
self.assertTrue(ra._should_exclude("build/vendor/x.go"))
self.assertTrue(ra._should_exclude("agent-ops/rules/common/rules-roadmap.md"))
self.assertTrue(ra._should_exclude("agent-ops/skills/common/plan/SKILL.md"))
self.assertFalse(ra._should_exclude("agent-ops/skills/project/e2e-smoke/SKILL.md"))
self.assertFalse(ra._should_exclude("src/main.go"))
self.assertFalse(ra._should_exclude("scripts/run.sh"))
# ============================================================
# TestPolicyThresholdsByFileKind [REVIEW_REVIEW_REFACTOR-2]
# ============================================================
class TestPolicyThresholdsByFileKind(_RepoFixture):
"""File-kind policy: language-aware test classification and boundaries."""
prefix = "ra-test-policy-"
def test_classify_file(self) -> None:
self.assertEqual(ra.classify_file("apps/edge/main.go"), "production")
self.assertEqual(ra.classify_file("apps/edge/test/foo_test.go"), "test")
self.assertEqual(ra.classify_file("apps/edge/foo_test.go"), "test")
self.assertEqual(ra.classify_file("agent-ops/skills/my-skill/SKILL.md"), "skill_entrypoint")
self.assertEqual(ra.classify_file("agent-ops/skills/other/SKILL.md"), "skill_entrypoint")
def test_classify_file_language_aware_test_names(self) -> None:
"""Every instrumented language reports its own test naming convention."""
self.assertEqual(ra.classify_file("scripts/readability_audit_test.py"), "test")
self.assertEqual(ra.classify_file("scripts/test_readability.py"), "test")
self.assertEqual(ra.classify_file("apps/client/lib/widget_test.dart"), "test")
self.assertEqual(ra.classify_file("android/app/src/RepoTest.kt"), "test")
self.assertEqual(ra.classify_file("android/app/src/RepoTests.kt"), "test")
self.assertEqual(ra.classify_file("ios/Runner/AppTests.swift"), "test")
self.assertEqual(ra.classify_file("scripts/e2e_test.sh"), "test")
self.assertEqual(ra.classify_file("scripts/tests/helper.sh"), "test")
# production files that merely mention test must stay production
self.assertEqual(ra.classify_file("apps/edge/testing.go"), "production")
self.assertEqual(ra.classify_file("scripts/readability_audit.py"), "production")
def test_classify_project_shell_test_entrypoints(self) -> None:
"""Real scripts/e2e-*.sh entrypoints are tests; other shell stays production."""
repo_root = os.path.dirname(SCRIPTS_DIR)
for path in ("scripts/e2e-smoke.sh", "scripts/e2e-long-context-admission-smoke.sh"):
self.assertTrue(os.path.isfile(os.path.join(repo_root, path)), path)
self.assertEqual(ra.classify_file(path), "test", path)
self.assertEqual(ra._file_threshold_for_kind(ra.classify_file(path)),
ra.FILE_POLICY["test"], path)
for path in ("scripts/dev/edge.sh", "agent-ops/bin/sync.sh"):
self.assertTrue(os.path.isfile(os.path.join(repo_root, path)), path)
self.assertEqual(ra.classify_file(path), "production", path)
self.assertEqual(ra._file_threshold_for_kind(ra.classify_file(path)),
ra.FILE_POLICY["production"], path)
# the pattern is anchored: neither a bare name nor a nested copy matches
self.assertEqual(ra.classify_file("scripts/e2e.sh"), "production")
self.assertEqual(ra.classify_file("tools/scripts/e2e-smoke.sh"), "production")
def test_shell_entrypoint_uses_test_threshold_end_to_end(self) -> None:
"""An 810-line e2e entrypoint reports test 'warning', not production's."""
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
body = "\n".join(f"echo {i}" for i in range(810)) + "\n"
self._write("scripts/e2e-fixture-smoke.sh", body)
self._write("scripts/dev/fixture.sh", body)
result = ra.audit(["scripts/e2e-fixture-smoke.sh", "scripts/dev/fixture.sh"])
kinds = {e["path"]: e["kind"] for e in result["files"]}
levels = {v["path"]: v["level"] for v in result["violations"]
if v["metric"] == "file_loc"}
self.assertEqual(kinds["scripts/e2e-fixture-smoke.sh"], "test")
self.assertEqual(kinds["scripts/dev/fixture.sh"], "production")
self.assertEqual(levels["scripts/e2e-fixture-smoke.sh"], "warning")
self.assertEqual(levels["scripts/dev/fixture.sh"], "split_review")
finally:
os.chdir(old_cwd)
def test_python_test_file_uses_test_policy(self) -> None:
"""A 700-line *_test.py is under the test warning threshold, not production's."""
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
body = "\n".join(f"# line {i}" for i in range(700)) + "\n"
self._write("pkg/thing_test.py", body)
self._write("pkg/thing.py", body)
result = ra.audit(["pkg/thing_test.py", "pkg/thing.py"])
levels = {v["path"]: v["level"] for v in result["violations"]
if v["metric"] == "file_loc"}
self.assertNotIn("pkg/thing_test.py", levels)
self.assertEqual(levels["pkg/thing.py"], "warning")
finally:
os.chdir(old_cwd)
def test_production_file_threshold_exact_and_one_over(self) -> None:
"""production: warning=500, split_review=800, exception=1000."""
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
for count in (500, 501, 800, 801, 1000, 1001):
lines = "\n".join(f"line {i}" for i in range(count)) + "\n"
self._write(f"src/f_{count}.go", lines)
files = [f"src/f_{c}.go" for c in (500, 501, 800, 801, 1000, 1001)]
result = ra.audit(files)
file_violations = [v for v in result["violations"] if v["metric"] == "file_loc"]
self.assertEqual(len(file_violations), 5)
levels_by_path = {v["path"]: v["level"] for v in file_violations}
self.assertNotIn("src/f_500.go", levels_by_path)
self.assertEqual(levels_by_path["src/f_501.go"], "warning")
self.assertEqual(levels_by_path["src/f_800.go"], "warning")
self.assertEqual(levels_by_path["src/f_801.go"], "split_review")
self.assertEqual(levels_by_path["src/f_1000.go"], "split_review")
self.assertEqual(levels_by_path["src/f_1001.go"], "exception")
finally:
os.chdir(old_cwd)
def test_test_file_threshold_exact_and_one_over(self) -> None:
"""test: warning=800, split_review=1000."""
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
for count in (800, 801, 1000, 1001):
lines = "\n".join(f"line {i}" for i in range(count)) + "\n"
self._write(f"test/f_{count}_test.go", lines)
files = [f"test/f_{c}_test.go" for c in (800, 801, 1000, 1001)]
result = ra.audit(files)
file_violations = [v for v in result["violations"] if v["metric"] == "file_loc"]
self.assertEqual(len(file_violations), 3)
levels_by_path = {v["path"]: v["level"] for v in file_violations}
self.assertNotIn("test/f_800_test.go", levels_by_path)
self.assertEqual(levels_by_path["test/f_801_test.go"], "warning")
self.assertEqual(levels_by_path["test/f_1000_test.go"], "warning")
self.assertEqual(levels_by_path["test/f_1001_test.go"], "split_review")
finally:
os.chdir(old_cwd)
def test_skill_entrypoint_threshold(self) -> None:
"""skill_entrypoint: warning=300, split_review=500."""
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
for name, count in (("foo", 300), ("bar", 301), ("baz", 500), ("qux", 501)):
lines = "\n".join(f"line {i}" for i in range(count)) + "\n"
self._write(f"agent-ops/skills/{name}/SKILL.md", lines)
files = [f"agent-ops/skills/{n}/SKILL.md" for n in ("foo", "bar", "baz", "qux")]
result = ra.audit(files)
file_violations = [v for v in result["violations"] if v["metric"] == "file_loc"]
self.assertEqual(len(file_violations), 3)
levels_by_path = {v["path"]: v["level"] for v in file_violations}
self.assertNotIn("agent-ops/skills/foo/SKILL.md", levels_by_path)
self.assertEqual(levels_by_path["agent-ops/skills/bar/SKILL.md"], "warning")
self.assertEqual(levels_by_path["agent-ops/skills/baz/SKILL.md"], "warning")
self.assertEqual(levels_by_path["agent-ops/skills/qux/SKILL.md"], "split_review")
finally:
os.chdir(old_cwd)
def test_function_threshold(self) -> None:
"""function: warning=80, split_review=120."""
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
for count in (79, 81, 120, 121):
body = "\n".join(f" line{i}" for i in range(count))
self._write(f"src/fn_{count}.go",
f"package p\n\nfunc fn{count}() {{\n{body}\n}}\n")
files = [f"src/fn_{c}.go" for c in (79, 81, 120, 121)]
result = ra.audit(files)
func_violations = [v for v in result["violations"] if v["metric"] == "function_loc"]
self.assertEqual(len(func_violations), 4)
levels_by_path = {v["path"]: v["level"] for v in func_violations}
self.assertEqual(levels_by_path["src/fn_79.go"], "warning")
self.assertEqual(levels_by_path["src/fn_81.go"], "warning")
self.assertEqual(levels_by_path["src/fn_120.go"], "split_review")
self.assertEqual(levels_by_path["src/fn_121.go"], "split_review")
finally:
os.chdir(old_cwd)
# ============================================================
# TestMultiLanguageFunctionIdentity [REVIEW_REVIEW_REFACTOR-4]
# ============================================================
class TestMultiLanguageFunctionIdentity(unittest.TestCase):
"""Raw/multiline/comment/heredoc aware measurement and qualified identity."""
def test_go_qualified_name_with_receiver(self) -> None:
source = textwrap.dedent("""\
package main
type MyType struct{}
func (m *MyType) DoSomething() {
x := 1
y := 2
}
func Standalone() {
z := 3
}
""")
names = {f["name"] for f in ra._extract_functions_go(source, "test.go")}
self.assertIn("MyType.DoSomething", names)
self.assertIn("Standalone", names)
def test_go_brace_in_string_not_counted(self) -> None:
source = textwrap.dedent("""\
package main
func WithBraceInString() string {
s := "hello } world"
return s
}
""")
funcs = ra._extract_functions_go(source, "test.go")
self.assertEqual(len(funcs), 1)
self.assertEqual(funcs[0]["name"], "WithBraceInString")
self.assertEqual(funcs[0]["loc"], 4)
def test_go_raw_string_does_not_end_function_early(self) -> None:
"""A backtick raw string with a brace must not terminate the function."""
source = textwrap.dedent("""\
package main
func WithRawString() string {
s := `line one
} not a brace
line three`
return s
}
""")
funcs = ra._extract_functions_go(source, "test.go")
self.assertEqual(len(funcs), 1)
self.assertEqual(funcs[0]["name"], "WithRawString")
self.assertEqual(funcs[0]["loc"], 6)
def test_go_block_comment_ignored(self) -> None:
source = textwrap.dedent("""\
package main
func WithBlockComment() {
/* this is a { block comment } with braces */
x := 1
}
""")
funcs = ra._extract_functions_go(source, "test.go")
self.assertEqual(len(funcs), 1)
self.assertEqual(funcs[0]["name"], "WithBlockComment")
self.assertEqual(funcs[0]["loc"], 4)
def test_go_block_comment_does_not_nest(self) -> None:
"""Go block comments do not nest: the first */ closes the comment."""
source = textwrap.dedent("""\
package main
func WithComment() {
/* outer /* inner */
x := 1
}
""")
funcs = ra._extract_functions_go(source, "test.go")
self.assertEqual(len(funcs), 1)
self.assertEqual(funcs[0]["name"], "WithComment")
self.assertEqual(funcs[0]["loc"], 4)
def test_kotlin_nested_block_comment_ignored(self) -> None:
"""Kotlin comments nest, so an inner */ must not resume brace counting."""
source = textwrap.dedent("""\
class Repo {
fun query(): String {
/* outer /* inner } */ still comment } */
return "ok"
}
}
""")
funcs = ra._extract_functions_kotlin(source, "test.kt")
self.assertEqual(len(funcs), 1)
self.assertEqual(funcs[0]["name"], "Repo.query")
self.assertEqual(funcs[0]["loc"], 4)
def test_swift_nested_block_comment_ignored(self) -> None:
source = textwrap.dedent("""\
extension Foo {
func render() -> String {
/* outer /* inner } */ still comment } */
return "ok"
}
}
""")
funcs = ra._extract_functions_swift(source, "test.swift")
self.assertEqual(len(funcs), 1)
self.assertEqual(funcs[0]["name"], "Foo.render")
self.assertEqual(funcs[0]["loc"], 4)
def test_shell_multiline_quote_holds_awk_body(self) -> None:
"""A quoted awk body spans newlines: its braces and callables are text."""
source = textwrap.dedent("""\
summarize() {
awk '
function reset() {
count = 0
}
/^}/ { reset(); print }
' "$1"
echo done
}
helper() {
echo "hi"
}
""")
funcs = ra._extract_functions_shell(source, "test.sh")
names = {f["name"] for f in funcs}
self.assertEqual(names, {"summarize", "helper"})
by_name = {f["name"]: f for f in funcs}
self.assertEqual(by_name["summarize"]["loc"], 9)
self.assertEqual(by_name["helper"]["loc"], 3)
def test_shell_multiline_double_quote_preserves_lines(self) -> None:
source = textwrap.dedent("""\
emit() {
printf "%s
} not a brace
" "$1"
echo done
}
""")
funcs = ra._extract_functions_shell(source, "test.sh")
self.assertEqual(len(funcs), 1)
self.assertEqual(funcs[0]["name"], "emit")
self.assertEqual(funcs[0]["loc"], 6)
def test_dart_generic_async_function(self) -> None:
source = textwrap.dedent("""\
import 'dart:async';
Future<void> myAsyncFunc() async {
await doSomething();
print('done');
}
String simpleFunc() {
return 'hello';
}
""")
names = {f["name"] for f in ra._extract_functions_dart(source, "test.dart")}
self.assertIn("myAsyncFunc", names)
self.assertIn("simpleFunc", names)
def test_dart_string_braces_ignored(self) -> None:
source = textwrap.dedent("""\
void main() {
var s = "hello } world";
print(s);
}
""")
funcs = ra._extract_functions_dart(source, "test.dart")
self.assertEqual(len(funcs), 1)
self.assertEqual(funcs[0]["name"], "main")
def test_dart_multiline_string_braces_ignored(self) -> None:
source = textwrap.dedent("""\
void withMultiline() {
var s = '''
} not a brace
''';
print(s);
}
""")
funcs = ra._extract_functions_dart(source, "test.dart")
self.assertEqual(len(funcs), 1)
self.assertEqual(funcs[0]["name"], "withMultiline")
self.assertEqual(funcs[0]["loc"], 6)
def test_dart_class_scope_identity(self) -> None:
source = textwrap.dedent("""\
class Repo {
void save() {
print('a');
}
}
class Cache {
void save() {
print('b');
}
}
""")
names = {f["name"] for f in ra._extract_functions_dart(source, "test.dart")}
self.assertEqual(names, {"Repo.save", "Cache.save"})
def test_dart_callback_type_is_not_a_function(self) -> None:
"""`void Function(...)` declares a callback type, never a callable named Function."""
source = textwrap.dedent("""\
class Button {
void Function() onTap;
void Function(String value)? onChanged;
void register(void Function() cb) {
onTap = cb;
}
}
""")
funcs = ra._extract_functions_dart(source, "test.dart")
names = {f["name"] for f in funcs}
self.assertEqual(names, {"Button.register"})
self.assertEqual(funcs[0]["loc"], 3)
def test_dart_body_less_declaration_is_not_a_function(self) -> None:
"""An abstract declaration has no body to measure."""
source = textwrap.dedent("""\
abstract class Repo {
void save();
Future<void> load();
}
class FileRepo extends Repo {
void save() {
print('saved');
}
}
""")
names = {f["name"] for f in ra._extract_functions_dart(source, "test.dart")}
self.assertEqual(names, {"FileRepo.save"})
def test_dart_expression_body_is_measured(self) -> None:
"""A `=>` body is a real callable and spans to its terminating `;`."""
source = textwrap.dedent("""\
class View {
Widget build() => Column(
children: [
Text('a'),
],
);
}
""")
funcs = ra._extract_functions_dart(source, "test.dart")
self.assertEqual([f["name"] for f in funcs], ["View.build"])
self.assertEqual(funcs[0]["loc"], 5)
self.assertEqual((funcs[0]["start"], funcs[0]["end"]), (2, 6))
def test_shell_body_less_call_is_not_a_function(self) -> None:
"""`name()` with no body is not a declaration; the real one still is."""
source = textwrap.dedent("""\
#!/bin/sh
usage() {
echo "usage"
}
if [ -z "$1" ]; then
usage
fi
""")
names = {f["name"] for f in ra._extract_functions_shell(source, "test.sh")}
self.assertEqual(names, {"usage"})
def test_shell_next_line_brace_body(self) -> None:
source = textwrap.dedent("""\
run_all()
{
echo "a"
}
""")
funcs = ra._extract_functions_shell(source, "test.sh")
self.assertEqual(len(funcs), 1)
self.assertEqual(funcs[0]["name"], "run_all")
self.assertEqual(funcs[0]["loc"], 4)
def test_next_line_type_brace_keeps_scope_identity(self) -> None:
"""A type whose body opener is on a later line still qualifies its methods."""
dart = textwrap.dedent("""\
class Repo
extends Base
{
void save() {
print('a');
}
}
""")
self.assertEqual({f["name"] for f in ra._extract_functions_dart(dart, "t.dart")},
{"Repo.save"})
kotlin = textwrap.dedent("""\
class Repo(
private val db: Db,
) {
fun save(): Int {
return 1
}
}
""")
self.assertEqual({f["name"] for f in ra._extract_functions_kotlin(kotlin, "t.kt")},
{"Repo.save"})
swift = textwrap.dedent("""\
struct Repo
{
func save() {
print("a")
}
}
""")
self.assertEqual({f["name"] for f in ra._extract_functions_swift(swift, "t.swift")},
{"Repo.save"})
def test_go_multiline_signature_with_receiver(self) -> None:
"""A receiver clause is not the header: the parameter list is."""
source = textwrap.dedent("""\
package main
func (m *MyType) DoSomething(
a int,
b string,
) error {
return nil
}
""")
funcs = ra._extract_functions_go(source, "test.go")
self.assertEqual([f["name"] for f in funcs], ["MyType.DoSomething"])
self.assertEqual(funcs[0]["loc"], 6)
def test_kotlin_function_name_and_property_excluded(self) -> None:
"""Kotlin properties are not functions; only fun declarations count."""
source = textwrap.dedent("""\
package com.example
fun calculate(a: Int, b: Int): Int {
return a + b
}
val constant = 42
var mutable = 0
""")
names = {f["name"] for f in ra._extract_functions_kotlin(source, "test.kt")}
self.assertEqual(names, {"calculate"})
def test_kotlin_multiline_raw_string_and_scope_identity(self) -> None:
source = textwrap.dedent("""\
class Repo {
fun query(): String {
val sql = \"\"\"
SELECT } FROM t
\"\"\"
return sql
}
}
""")
funcs = ra._extract_functions_kotlin(source, "test.kt")
self.assertEqual(len(funcs), 1)
self.assertEqual(funcs[0]["name"], "Repo.query")
self.assertEqual(funcs[0]["loc"], 6)
def test_swift_function_name_and_property_excluded(self) -> None:
source = textwrap.dedent("""\
func fetchData() {
print("fetching")
}
var counter = 0
let limit = 10
""")
names = {f["name"] for f in ra._extract_functions_swift(source, "test.swift")}
self.assertEqual(names, {"fetchData"})
def test_swift_multiline_string_and_extension_identity(self) -> None:
source = textwrap.dedent("""\
extension Foo {
func render() -> String {
let text = \"\"\"
} not a brace
\"\"\"
return text
}
}
""")
funcs = ra._extract_functions_swift(source, "test.swift")
self.assertEqual(len(funcs), 1)
self.assertEqual(funcs[0]["name"], "Foo.render")
self.assertEqual(funcs[0]["loc"], 6)
def test_shell_function_name(self) -> None:
source = textwrap.dedent("""\
#!/bin/sh
my_function() {
echo "hello"
}
another_function() {
echo "world"
}
""")
names = {f["name"] for f in ra._extract_functions_shell(source, "test.sh")}
self.assertIn("my_function", names)
self.assertIn("another_function", names)
def test_shell_heredoc_braces_ignored(self) -> None:
source = textwrap.dedent("""\
generate_config() {
cat <<EOF
{
"a": 1
}
EOF
echo done
}
""")
funcs = ra._extract_functions_shell(source, "test.sh")
self.assertEqual(len(funcs), 1)
self.assertEqual(funcs[0]["name"], "generate_config")
self.assertEqual(funcs[0]["loc"], 8)
def test_kotlin_overload_identity_is_signature_qualified(self) -> None:
"""Colliding overloads split by signature; unique callables keep scope.name."""
source = textwrap.dedent("""\
class C {
fun run(value: Int) {
println(value)
}
fun run(value: String) {
println(value)
}
fun once() {
println(1)
}
}
""")
names = {f["name"] for f in ra._extract_functions_kotlin(source, "t.kt")}
self.assertEqual(names, {"C.run(value: Int)", "C.run(value: String)", "C.once"})
def test_swift_overload_identity_is_signature_qualified(self) -> None:
source = textwrap.dedent("""\
struct C {
func run(value: Int) {
print(value)
}
func run(value: String) {
print(value)
}
}
""")
names = {f["name"] for f in ra._extract_functions_swift(source, "t.swift")}
self.assertEqual(names, {"C.run(value: Int)", "C.run(value: String)"})
def test_dart_overload_identity_is_signature_qualified(self) -> None:
source = textwrap.dedent("""\
class C {
void run(int value) {
print(value);
}
void run(String value) {
print(value);
}
}
""")
names = {f["name"] for f in ra._extract_functions_dart(source, "t.dart")}
self.assertEqual(names, {"C.run(int value)", "C.run(String value)"})
def test_go_receiver_overload_identity_excludes_receiver_clause(self) -> None:
"""Same method name on two receivers stays distinct without a signature."""
source = textwrap.dedent("""\
package main
func (a *A) Run(x int) {
_ = x
}
func (b *B) Run(x string) {
_ = x
}
""")
names = {f["name"] for f in ra._extract_functions_go(source, "t.go")}
self.assertEqual(names, {"A.Run", "B.Run"})
def test_overload_identity_is_deterministic(self) -> None:
source = textwrap.dedent("""\
class C {
fun run(value: Int) {
println(value)
}
fun run(value: String) {
println(value)
}
}
""")
rendered = {json.dumps(ra._extract_functions_kotlin(source, "t.kt"), sort_keys=True)
for _ in range(3)}
self.assertEqual(len(rendered), 1)
def test_python_property_and_setter_identity_is_split(self) -> None:
"""A property/setter pair shares scope.name and must not collide."""
source = textwrap.dedent("""\
class C:
@property
def value(self):
return self._v
@value.setter
def value(self, v):
self._v = v
""")
names = {f["name"] for f in ra._extract_functions_python(source, "t.py")}
self.assertEqual(names, {"C.value(self)", "C.value(self, v)"})
def test_identical_signature_collision_gets_stable_ordinal(self) -> None:
"""When signatures tie too, position breaks the tie deterministically."""
source = textwrap.dedent("""\
class C:
if FLAG:
def run(self):
return 1
else:
def run(self):
return 2
""")
funcs = ra._extract_functions_python(source, "t.py")
self.assertEqual(sorted(f["name"] for f in funcs),
["C.run(self)#1", "C.run(self)#2"])
first = next(f for f in funcs if f["name"] == "C.run(self)#1")
second = next(f for f in funcs if f["name"] == "C.run(self)#2")
self.assertLess(first["start"], second["start"])
def test_extracted_functions_expose_no_internal_keys(self) -> None:
"""The signature is an identity input, not part of the report schema."""
source = "class C {\n fun run(a: Int) {\n println(a)\n }\n}\n"
for fn in ra._extract_functions_kotlin(source, "t.kt"):
self.assertEqual(set(fn), {"name", "loc", "start", "end"})
def test_python_scope_qualified_identity(self) -> None:
"""Python classes are not functions and methods carry their class scope."""
source = textwrap.dedent("""\
class Foo:
def bar(self):
pass
def baz(self):
return 1
def standalone():
x = 1
y = 2
""")
names = {f["name"] for f in ra._extract_functions_python(source, "test.py")}
self.assertNotIn("Foo", names)
self.assertEqual(names, {"Foo.bar", "Foo.baz", "standalone"})
def test_python_duplicate_method_names_stay_distinct(self) -> None:
source = textwrap.dedent("""\
class A:
def run(self):
pass
class B:
def run(self):
x = 1
return x
def run():
pass
""")
names = {f["name"] for f in ra._extract_functions_python(source, "test.py")}
self.assertEqual(names, {"A.run", "B.run", "run"})
def test_python_string_braces_ignored(self) -> None:
source = textwrap.dedent("""\
def with_brace():
s = "hello } world"
return s
""")
funcs = ra._extract_functions_python(source, "test.py")
self.assertEqual(len(funcs), 1)
self.assertEqual(funcs[0]["name"], "with_brace")
def test_kotlin_multiline_expression_body_spans_full_expression(self) -> None:
"""An `=` body ends at the expression, not at the declaration line."""
source = textwrap.dedent("""\
class C {
fun compute() =
listOf(
1,
2,
)
fun five() = 5
fun call() = compute()
.size
}
""")
by_name = {f["name"]: f
for f in ra._extract_functions_kotlin(source, "test.kt")}
self.assertEqual(set(by_name), {"C.compute", "C.five", "C.call"})
self.assertEqual(by_name["C.compute"]["start"], 2)
self.assertEqual(by_name["C.compute"]["end"], 6)
self.assertEqual(by_name["C.compute"]["loc"], 5)
# A one-line expression body stays exactly one line.
self.assertEqual(by_name["C.five"]["start"], 8)
self.assertEqual(by_name["C.five"]["end"], 8)
self.assertEqual(by_name["C.five"]["loc"], 1)
# A leading chain line continues the declaration-line expression.
self.assertEqual(by_name["C.call"]["start"], 10)
self.assertEqual(by_name["C.call"]["end"], 11)
self.assertEqual(by_name["C.call"]["loc"], 2)
def test_kotlin_depth_zero_expression_continuations_span_full_expression(self) -> None:
"""Depth-0 chain and operator continuations span to the real end line."""
source = textwrap.dedent("""\
class C {
fun call() = compute()
.size
fun total() = 1 +
2
fun done() = 3
}
""")
by_name = {f["name"]: f
for f in ra._extract_functions_kotlin(source, "test.kt")}
self.assertEqual(set(by_name), {"C.call", "C.total", "C.done"})
# Leading `.size` chain belongs to the declaration-line expression.
self.assertEqual(by_name["C.call"]["start"], 2)
self.assertEqual(by_name["C.call"]["end"], 3)
self.assertEqual(by_name["C.call"]["loc"], 2)
# A trailing binary operator keeps the expression open one more line.
self.assertEqual(by_name["C.total"]["start"], 4)
self.assertEqual(by_name["C.total"]["end"], 5)
self.assertEqual(by_name["C.total"]["loc"], 2)
# The sibling after a continuation is never swallowed.
self.assertEqual(by_name["C.done"]["start"], 6)
self.assertEqual(by_name["C.done"]["end"], 6)
self.assertEqual(by_name["C.done"]["loc"], 1)
def test_kotlin_depth_zero_operator_grammar_continuations_span_full_expression(self) -> None:
"""Comparison/word/range/infix and leading operators span to the end line."""
source = textwrap.dedent("""\
class C {
infix fun Int.join(other: Int) = this + other
infix fun String.then(other: Int) = other
fun less() = 1 <
2
fun lessNoSpace() = 1<
2
fun greaterNoSpace() = 2>
1
fun contained() = 1 in
listOf(1)
fun typed(value: Any) = value is
String
fun cast(value: Any) = value as
String
fun range() = 1..<
3
fun custom() = 1 join
2
fun stringInfix() = "x" then
1
fun leadingLogical() = true
&& false
fun leadingCast(value: Any) = value
as String
fun genericCast(value: Any) = value as List<Int>
fun genericTyped(value: Any) = value is Map<String, List<Int>>
fun plain(value: Any) = value
fun done() = 3
}
""")
by_name = {f["name"]: f
for f in ra._extract_functions_kotlin(source, "test.kt")}
self.assertEqual(set(by_name), {
"C.Int.join", "C.String.then", "C.less", "C.lessNoSpace",
"C.greaterNoSpace", "C.contained", "C.typed", "C.cast",
"C.range", "C.custom", "C.stringInfix", "C.leadingLogical",
"C.leadingCast", "C.genericCast", "C.genericTyped", "C.plain",
"C.done",
})
# Trailing comparison (with or without lexical spacing), containment/
# type test/cast, open range, and custom infix with identifier or
# string literal left operand, plus newline-before logical/cast
# operators, each keep the expression open exactly one more line.
expected_spans = {
"C.less": (4, 5),
"C.lessNoSpace": (6, 7),
"C.greaterNoSpace": (8, 9),
"C.contained": (10, 11),
"C.typed": (12, 13),
"C.cast": (14, 15),
"C.range": (16, 17),
"C.custom": (18, 19),
"C.stringInfix": (20, 21),
"C.leadingLogical": (22, 23),
"C.leadingCast": (24, 25),
}
for name, (start, end) in expected_spans.items():
self.assertEqual(by_name[name]["start"], start, name)
self.assertEqual(by_name[name]["end"], end, name)
self.assertEqual(by_name[name]["loc"], 2, name)
# Complete infix/generic/simple bodies stay one line, and no sibling
# after either a generic closer or comparison is swallowed.
expected_single_line_spans = {
"C.Int.join": 2,
"C.String.then": 3,
"C.genericCast": 26,
"C.genericTyped": 27,
"C.plain": 28,
"C.done": 29,
}
for name, line in expected_single_line_spans.items():
actual = (by_name[name]["start"], by_name[name]["end"], by_name[name]["loc"])
self.assertEqual(actual, (line, line, 1), name)
def test_kotlin_generic_type_projection_closers_preserve_siblings(self) -> None:
"""Complete Kotlin type projections do not turn their `>` into comparisons."""
source = textwrap.dedent("""\
class C {
fun functionType(value: Any) = value as List<(Int) -> String>
fun parenthesized(value: Any) = value as List<(String)>
fun annotated(value: Any) = value as List<@Ann String>
fun nonNull(value: Any) = value as List<T & Any>
fun escaped(value: Any) = value as List<`when`>
fun genericComparison(value: Any) = value as List<Int> >
value
fun done() = 3
}
""")
by_name = {f["name"]: f
for f in ra._extract_functions_kotlin(source, "test.kt")}
expected = {
"C.functionType": (2, 2, 1),
"C.parenthesized": (3, 3, 1),
"C.annotated": (4, 4, 1),
"C.nonNull": (5, 5, 1),
"C.escaped": (6, 6, 1),
"C.genericComparison": (7, 8, 2),
"C.done": (9, 9, 1),
}
self.assertEqual(set(by_name), set(expected))
for name, span in expected.items():
actual = (by_name[name]["start"], by_name[name]["end"], by_name[name]["loc"])
self.assertEqual(actual, span, name)
def test_kotlin_qualified_annotation_type_projection_closer_preserves_siblings(self) -> None:
"""Qualified annotations keep their outer generic closer local."""
source = textwrap.dedent("""\
class C {
fun qualified(value: Any) = value as @com.example.Ann("reason") List<String>
fun safeCast(value: Any) = value as?@com.example.Ann("reason") List<String>
fun typed(value: Any) = value is @com.example.Ann("reason") List<String>
fun simple(value: Any) = value as @com.example.Ann List<String>
fun simpleSafeCast(value: Any) = value as?@com.example.Ann List<String>
fun simpleTyped(value: Any) = value is @com.example.Ann List<String>
fun simpleMultiple(value: Any) = value as @Ann @com.example.Other List<String>
fun constructorThenSimple(value: Any) = value as @Ann("reason") @com.example.Other List<String>
fun simpleThenConstructor(value: Any) = value as @Ann @com.example.Other("reason") List<String>
fun constructorMultiple(value: Any) = value as @Ann("one") @com.example.Other("two") List<String>
fun annotationArgument(value: Any) = value as List<@com.example.Ann([value as String, 1 > 0]) String>
fun afterOperator(value: Any) = value as
@com.example.Ann List<String>
fun afterAnnotation(value: Any) = value as? @com.example.Ann
List<String>
fun betweenAnnotations(value: Any) = value is @Ann
@com.example.Other List<String>
fun afterConstructor(value: Any) = value !is @com.example.Ann("reason")
List<String>
fun mixedMultiline(value: Any) = value as @Ann
@com.example.Other("reason")
List<String>
fun done() = 3
}
""")
by_name = {f["name"]: f
for f in ra._extract_functions_kotlin(source, "test.kt")}
expected = {
"C.qualified": (2, 2, 1),
"C.safeCast": (3, 3, 1),
"C.typed": (4, 4, 1),
"C.simple": (5, 5, 1),
"C.simpleSafeCast": (6, 6, 1),
"C.simpleTyped": (7, 7, 1),
"C.simpleMultiple": (8, 8, 1),
"C.constructorThenSimple": (9, 9, 1),
"C.simpleThenConstructor": (10, 10, 1),
"C.constructorMultiple": (11, 11, 1),
"C.annotationArgument": (12, 12, 1),
"C.afterOperator": (13, 14, 2),
"C.afterAnnotation": (15, 16, 2),
"C.betweenAnnotations": (17, 18, 2),
"C.afterConstructor": (19, 20, 2),
"C.mixedMultiline": (21, 23, 3),
"C.done": (24, 24, 1),
}
self.assertEqual(set(by_name), set(expected))
for name, span in expected.items():
actual = (by_name[name]["start"], by_name[name]["end"], by_name[name]["loc"])
self.assertEqual(actual, span, name)
def test_kotlin_adjacent_safe_cast_generic_closer_preserves_sibling(self) -> None:
"""`as?` accepts an adjacent type name but identifier tails do not."""
source = textwrap.dedent("""\
class C {
fun adjacent(value: Any) = value as?List<Int>
fun spaced(value: Any) = value as? List<Int>
fun done() = 3
}
""")
by_name = {f["name"]: f
for f in ra._extract_functions_kotlin(source, "test.kt")}
expected = {
"C.adjacent": (2, 2, 1),
"C.spaced": (3, 3, 1),
"C.done": (4, 4, 1),
}
self.assertEqual(set(by_name), set(expected))
for name, span in expected.items():
actual = (by_name[name]["start"], by_name[name]["end"], by_name[name]["loc"])
self.assertEqual(actual, span, name)
self.assertFalse(ra._kotlin_trailing_generic_type_closer("value asList<Int>"))
self.assertFalse(ra._kotlin_trailing_generic_type_closer("value isReady<Int>"))
def test_kotlin_multiline_generic_type_arguments_preserve_span_and_siblings(self) -> None:
"""Kotlin generic arguments stay open through their outer closer."""
source = textwrap.dedent("""\
class C {
fun simple(value: Any) = value as List<
Int
>
fun nested(value: Any) = value as Map<
String,
List<Int>
>
fun done() = 3
}
""")
by_name = {f["name"]: f
for f in ra._extract_functions_kotlin(source, "test.kt")}
expected = {
"C.simple": (2, 4, 3),
"C.nested": (5, 8, 4),
"C.done": (9, 9, 1),
}
self.assertEqual(set(by_name), set(expected))
for name, span in expected.items():
actual = (by_name[name]["start"], by_name[name]["end"], by_name[name]["loc"])
self.assertEqual(actual, span, name)
def test_shell_escaped_and_operator_comment_braces_are_ignored(self) -> None:
"""`:;# }` is a comment and `\\}` is a literal: neither closes a body."""
source = textwrap.dedent("""\
close_brace() {
:;# }
echo done
}
escaped() {
echo \\}
echo done
}
""")
by_name = {f["name"]: f
for f in ra._extract_functions_shell(source, "test.sh")}
self.assertEqual(set(by_name), {"close_brace", "escaped"})
self.assertEqual(by_name["close_brace"]["start"], 1)
self.assertEqual(by_name["close_brace"]["end"], 4)
self.assertEqual(by_name["close_brace"]["loc"], 4)
self.assertEqual(by_name["escaped"]["start"], 6)
self.assertEqual(by_name["escaped"]["end"], 9)
self.assertEqual(by_name["escaped"]["loc"], 4)
def test_shell_parameter_expansion_and_quotes_still_scan(self) -> None:
"""Token-aware blanking leaves `${...}` and quoted `#` lexically intact."""
source = textwrap.dedent("""\
expand() {
echo "${VAR}"
echo "${OTHER:-#default}"
local hash='#not-a-comment'
echo "$hash"
}
""")
funcs = ra._extract_functions_shell(source, "test.sh")
self.assertEqual(len(funcs), 1)
self.assertEqual(funcs[0]["name"], "expand")
self.assertEqual(funcs[0]["loc"], 6)
def test_shell_hash_comment_boundary_preserves_function_close(self) -> None:
"""A non-comment `#` never erases a same-line function close."""
source = textwrap.dedent("""\
hash_word() {
echo foo#bar; }
trim() {
echo ${value#x}; }
next() {
echo done
}
""")
by_name = {f["name"]: f
for f in ra._extract_functions_shell(source, "test.sh")}
self.assertEqual(set(by_name), {"hash_word", "trim", "next"})
# `#` inside a word is a token, so `; }` still closes the body.
self.assertEqual(by_name["hash_word"]["start"], 1)
self.assertEqual(by_name["hash_word"]["end"], 2)
self.assertEqual(by_name["hash_word"]["loc"], 2)
# `#` inside `${...}` is an expansion operator, not a comment.
self.assertEqual(by_name["trim"]["start"], 3)
self.assertEqual(by_name["trim"]["end"], 4)
self.assertEqual(by_name["trim"]["loc"], 2)
# The sibling after the same-line closes keeps its own identity.
self.assertEqual(by_name["next"]["start"], 5)
self.assertEqual(by_name["next"]["end"], 7)
self.assertEqual(by_name["next"]["loc"], 3)
def test_go_type_braces_do_not_open_function_body(self) -> None:
"""Generic constraint and result-type braces are not the body opener."""
source = textwrap.dedent("""\
package p
func Map[T interface{ ~int | ~string }](in []T) []T {
return in
}
func Meta() (out struct{ A int }) {
return out
}
""")
by_name = {f["name"]: f for f in ra._extract_functions_go(source, "test.go")}
self.assertEqual(set(by_name), {"Map", "Meta"})
# The generic parameter list is not the signature; the value list is.
lines = source.splitlines()
self.assertEqual(
ra._declaration_signature(lines, 2, lines[2].index("[")), "(in []T)")
self.assertEqual(
ra._declaration_signature(lines, 6, lines[6].index("(")), "()")
self.assertEqual(by_name["Map"]["start"], 3)
self.assertEqual(by_name["Map"]["end"], 5)
self.assertEqual(by_name["Map"]["loc"], 3)
self.assertEqual(by_name["Meta"]["start"], 7)
self.assertEqual(by_name["Meta"]["end"], 9)
self.assertEqual(by_name["Meta"]["loc"], 3)
# ============================================================
# TestAllowlistIdentityAndReasonRatchet [REVIEW_REVIEW_REFACTOR-2]
# ============================================================
def _baseline(file_thresholds=None, function_thresholds=None, task_totals=None):
return {
"file_thresholds": file_thresholds or [],
"function_thresholds": function_thresholds or [],
"task_read_set_totals": task_totals or [],
}
def _current(violations):
return {
"violations": violations,
"summary": {"total_files": 1, "total_lines_of_code": 1000,
"total_functions": 1, "violations_count": len(violations)},
}
class TestAllowlistIdentityAndReasonRatchet(_RepoFixture):
"""Stable (path, metric, function) identity: drops pass, new/increases fail."""
prefix = "ra-test-ratchet-"
def test_report_function_identities_are_unique_per_path(self) -> None:
"""Ratchet identity is (path, metric, function): duplicates would collide."""
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("src/overload.kt", textwrap.dedent("""\
class C {
fun run(value: Int) {
println(value)
}
fun run(value: String) {
println(value)
}
}
"""))
self._write("src/prop.py", textwrap.dedent("""\
class C:
@property
def value(self):
return self._v
@value.setter
def value(self, v):
self._v = v
"""))
result = ra.audit(["src/overload.kt", "src/prop.py"])
identities = [(entry["path"], fn["name"])
for entry in result["files"]
for fn in entry.get("functions", [])]
self.assertEqual(len(identities), 4)
self.assertEqual(len(identities), len(set(identities)))
finally:
os.chdir(old_cwd)
def test_same_value_with_identity_passes(self) -> None:
baseline = _baseline(
file_thresholds=[
{"path": "src/big.go", "metric": "file_loc", "level": "exception",
"value": 1000, "reason": "existing large file"},
],
function_thresholds=[
{"path": "src/big.go", "function": "MyType.DoSomething",
"metric": "function_loc", "level": "split_review",
"value": 120, "reason": "existing large function"},
],
)
current = _current([
{"path": "src/big.go", "metric": "file_loc", "level": "exception",
"value": 1000, "reason": "file exceeds threshold"},
{"path": "src/big.go", "metric": "function_loc", "level": "split_review",
"value": 120, "function": "MyType.DoSomething",
"reason": "function exceeds threshold"},
])
self.assertEqual(ra.ratchet_check(current, baseline, None), [])
def test_new_function_identity_fails(self) -> None:
current = _current([
{"path": "src/new.go", "metric": "function_loc", "level": "split_review",
"value": 150, "function": "NewFunc", "reason": "new oversized function"},
])
new = ra.ratchet_check(current, _baseline(), None)
self.assertEqual(len(new), 1)
self.assertEqual(new[0]["function"], "NewFunc")
self.assertIn("new violation", new[0]["reason"])
def test_level_drop_passes(self) -> None:
"""An improvement that lowers the level is not a new violation."""
baseline = _baseline(file_thresholds=[
{"path": "src/big.go", "metric": "file_loc", "level": "exception",
"value": 1200, "reason": "existing large file"},
])
current = _current([
{"path": "src/big.go", "metric": "file_loc", "level": "split_review",
"value": 900, "reason": "file exceeds threshold"},
])
self.assertEqual(ra.ratchet_check(current, baseline, None), [])
def test_level_increase_fails(self) -> None:
baseline = _baseline(file_thresholds=[
{"path": "src/big.go", "metric": "file_loc", "level": "split_review",
"value": 900, "reason": "existing large file"},
])
current = _current([
{"path": "src/big.go", "metric": "file_loc", "level": "exception",
"value": 1200, "reason": "file exceeds threshold"},
])
new = ra.ratchet_check(current, baseline, None)
self.assertEqual(len(new), 1)
self.assertIn("level increased", new[0]["reason"])
def test_value_increase_fails(self) -> None:
baseline = _baseline(file_thresholds=[
{"path": "src/big.go", "metric": "file_loc", "level": "exception",
"value": 1100, "reason": "smaller baseline"},
])
current = _current([
{"path": "src/big.go", "metric": "file_loc", "level": "exception",
"value": 1150, "reason": "got bigger"},
])
new = ra.ratchet_check(current, baseline, None)
self.assertEqual(len(new), 1)
self.assertIn("value increased", new[0]["reason"])
def test_empty_reason_in_baseline_fails(self) -> None:
baseline = _baseline(file_thresholds=[
{"path": "src/big.go", "metric": "file_loc", "level": "exception",
"value": 1000, "reason": ""},
])
new = ra.ratchet_check(_current([]), baseline, None)
self.assertEqual(len(new), 1)
self.assertEqual(new[0]["path"], "<baseline>")
self.assertIn("reason", new[0]["reason"])
def test_duplicate_function_in_baseline_fails(self) -> None:
baseline = _baseline(function_thresholds=[
{"path": "src/fn.go", "function": "DoSomething", "metric": "function_loc",
"level": "split_review", "value": 120, "reason": "first entry"},
{"path": "src/fn.go", "function": "DoSomething", "metric": "function_loc",
"level": "split_review", "value": 120, "reason": "duplicate entry"},
])
new = ra.ratchet_check(_current([]), baseline, None)
self.assertEqual(new[0]["path"], "<baseline>")
self.assertIn("duplicate identity", new[0]["reason"])
def test_same_identity_across_levels_is_duplicate(self) -> None:
"""One identity may hold exactly one allowlisted level."""
baseline = _baseline(file_thresholds=[
{"path": "src/fn.go", "metric": "file_loc", "level": "warning",
"value": 600, "reason": "entry a"},
{"path": "src/fn.go", "metric": "file_loc", "level": "exception",
"value": 1200, "reason": "entry b"},
])
new = ra.ratchet_check(_current([]), baseline, None)
self.assertEqual(new[0]["path"], "<baseline>")
self.assertIn("duplicate identity", new[0]["reason"])
def test_malformed_metric_fails(self) -> None:
baseline = _baseline(file_thresholds=[
{"path": "src/big.go", "metric": "function_loc", "level": "exception",
"value": 1000, "reason": "wrong metric"},
])
new = ra.ratchet_check(_current([]), baseline, None)
self.assertIn("invalid 'metric'", new[0]["reason"])
def test_malformed_level_fails(self) -> None:
baseline = _baseline(function_thresholds=[
{"path": "src/big.go", "function": "Foo", "metric": "function_loc",
"level": "exception", "value": 130, "reason": "functions have no exception level"},
])
new = ra.ratchet_check(_current([]), baseline, None)
self.assertIn("invalid 'level'", new[0]["reason"])
def test_malformed_value_fails(self) -> None:
baseline = _baseline(file_thresholds=[
{"path": "src/big.go", "metric": "file_loc", "level": "exception",
"value": "1000", "reason": "string value"},
])
new = ra.ratchet_check(_current([]), baseline, None)
self.assertIn("invalid 'value'", new[0]["reason"])
def test_unknown_key_fails(self) -> None:
baseline = _baseline(file_thresholds=[
{"path": "src/big.go", "metric": "file_loc", "level": "exception",
"value": 1000, "reason": "ok", "owner": "team"},
])
new = ra.ratchet_check(_current([]), baseline, None)
self.assertIn("unknown keys", new[0]["reason"])
def test_missing_function_key_fails(self) -> None:
baseline = _baseline(function_thresholds=[
{"path": "src/big.go", "metric": "function_loc", "level": "split_review",
"value": 130, "reason": "no function identity"},
])
new = ra.ratchet_check(_current([]), baseline, None)
self.assertIn("missing keys", new[0]["reason"])
# ============================================================
# TestTaskReadSetTotalsAndBudget [REVIEW_REVIEW_REFACTOR-3]
# ============================================================
class TestTaskReadSetTotalsAndBudget(_RepoFixture):
"""Read-set config failures, budget/reason, and baseline total ratchet."""
prefix = "ra-test-rs-"
def _config(self, files, max_total_loc=100, reason="test budget", task_id="test-task",
version="2.0", generated_at="2026-07-16",
description="fixture task"):
return {"version": version, "generated_at": generated_at, "tasks": [{
"task_id": task_id, "description": description,
"files": files,
"budget": {"max_total_loc": max_total_loc, "reason": reason},
}]}
def test_ordered_path_totals(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("a.go", "package p\n\nfunc main() {}\n") # 3 lines
self._write("b.py", "def f(): pass\n") # 1 line
self._write("c.md", "line1\nline2\n") # 2 lines
errors, task_read_sets = ra.audit_read_sets(
None, self._config(["b.py", "a.go", "c.md"]))
self.assertEqual(errors, [])
self.assertEqual(len(task_read_sets), 1)
entry = task_read_sets[0]
self.assertEqual(entry["task_id"], "test-task")
self.assertEqual([f["path"] for f in entry["files"]], ["b.py", "a.go", "c.md"])
self.assertEqual(entry["total_loc"], 6)
finally:
os.chdir(old_cwd)
def test_missing_path_is_configuration_failure(self) -> None:
"""A read-set path that does not exist is a configuration failure, not 0 LOC."""
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("exists.go", "package p\n")
errors, task_read_sets = ra.audit_read_sets(
None, self._config(["exists.go", "nonexistent.go"]))
self.assertEqual(len(errors), 1)
self.assertEqual(errors[0]["metric"], "read_set_config")
self.assertIn("missing read_set path 'nonexistent.go'", errors[0]["reason"])
self.assertEqual([f["path"] for f in task_read_sets[0]["files"]], ["exists.go"])
finally:
os.chdir(old_cwd)
def test_duplicate_path_error(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("a.go", "package p\n")
errors, _ = ra.audit_read_sets(None, self._config(["a.go", "a.go"]))
self.assertEqual(len(errors), 1)
self.assertIn("duplicate", errors[0]["reason"])
finally:
os.chdir(old_cwd)
def test_duplicate_task_id_error(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("a.go", "package p\n")
config = {"version": "2.0", "generated_at": "2026-07-16", "tasks": [
{"task_id": "dup", "description": "fixture task", "files": ["a.go"],
"budget": {"max_total_loc": 10, "reason": "r"}},
{"task_id": "dup", "description": "fixture task", "files": ["a.go"],
"budget": {"max_total_loc": 10, "reason": "r"}},
]}
errors, task_read_sets = ra.audit_read_sets(None, config)
self.assertEqual(len(errors), 1)
self.assertIn("duplicate task_id", errors[0]["reason"])
self.assertEqual(len(task_read_sets), 1)
finally:
os.chdir(old_cwd)
def test_read_set_schema_requires_typed_metadata(self) -> None:
"""Untyped/absent root metadata and task description are config errors."""
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("a.go", "package p\n")
task = {"task_id": "t", "description": "fixture task", "files": ["a.go"],
"budget": {"max_total_loc": 100, "reason": "r"}}
valid = {"version": "2.0", "generated_at": "2026-07-16", "tasks": [task]}
# The contrast case: the exact schema is satisfied, so no issue.
errors, task_read_sets = ra.audit_read_sets(None, valid)
self.assertEqual(errors, [])
self.assertEqual(len(task_read_sets), 1)
cases = [
("missing version",
{k: v for k, v in valid.items() if k != "version"},
"missing required root key 'version'"),
("missing generated_at",
{k: v for k, v in valid.items() if k != "generated_at"},
"missing required root key 'generated_at'"),
("wrong-type version", {**valid, "version": 2.0},
"root 'version' must be a non-empty string"),
("empty generated_at", {**valid, "generated_at": " "},
"root 'generated_at' must be a non-empty string"),
("unsupported version", {**valid, "version": "1.0"},
"unsupported version '1.0'"),
("missing description",
{**valid, "tasks": [{k: v for k, v in task.items()
if k != "description"}]},
"missing required non-empty 'description'"),
("wrong-type description",
{**valid, "tasks": [{**task, "description": ["fixture"]}]},
"missing required non-empty 'description'"),
("empty description",
{**valid, "tasks": [{**task, "description": ""}]},
"missing required non-empty 'description'"),
]
for label, config, expected in cases:
with self.subTest(case=label):
errors, _ = ra.audit_read_sets(None, config)
self.assertEqual(len(errors), 1, f"{label}: {errors}")
self.assertEqual(errors[0]["metric"], "read_set_config")
self.assertIn(expected, errors[0]["reason"])
finally:
os.chdir(old_cwd)
def test_invalid_budget_schema_error(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("a.go", "package p\n")
config = {"version": "2.0", "generated_at": "2026-07-16", "tasks": [
{"task_id": "t", "description": "fixture task", "files": ["a.go"],
"budget": {"max_total_loc": "100", "reason": "r"}}]}
errors, _ = ra.audit_read_sets(None, config)
self.assertEqual(len(errors), 1)
self.assertIn("invalid 'max_total_loc'", errors[0]["reason"])
finally:
os.chdir(old_cwd)
def test_unknown_task_key_error(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("a.go", "package p\n")
config = {"version": "2.0", "generated_at": "2026-07-16", "tasks": [
{"task_id": "t", "description": "fixture task", "files": ["a.go"], "total_loc": 1,
"budget": {"max_total_loc": 100, "reason": "r"}}]}
errors, _ = ra.audit_read_sets(None, config)
self.assertEqual(len(errors), 1)
self.assertIn("unknown keys ['total_loc']", errors[0]["reason"])
finally:
os.chdir(old_cwd)
def test_exact_budget_passes_and_one_over_without_reason_fails(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("ten.py", "\n".join(f"# {i}" for i in range(10)) + "\n")
errors, task_read_sets = ra.audit_read_sets(
None, self._config(["ten.py"], max_total_loc=10, reason=""))
self.assertEqual(errors, [])
self.assertEqual(task_read_sets[0]["total_loc"], 10)
errors, _ = ra.audit_read_sets(
None, self._config(["ten.py"], max_total_loc=9, reason=""))
self.assertEqual(len(errors), 1)
self.assertEqual(errors[0]["metric"], "read_set_budget")
self.assertEqual(errors[0]["value"], 10)
self.assertIn("empty reason", errors[0]["reason"])
finally:
os.chdir(old_cwd)
def test_over_budget_with_reason_needs_baseline_total(self) -> None:
"""Over budget with a reason still has to be allowlisted in the baseline."""
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("big.py", "\n".join(f"# {i}" for i in range(200)) + "\n")
config = self._config(["big.py"], max_total_loc=100, reason="known large file")
errors, task_read_sets = ra.audit_read_sets(None, config)
self.assertEqual(errors, [])
self.assertEqual(task_read_sets[0]["total_loc"], 200)
issues = ra.ratchet_read_set_totals(task_read_sets, _baseline())
self.assertEqual(len(issues), 1)
self.assertEqual(issues[0]["metric"], "read_set_total")
self.assertEqual(issues[0]["value"], 200)
self.assertIn("not allowlisted", issues[0]["reason"])
finally:
os.chdir(old_cwd)
def test_task_total_same_or_lower_than_baseline_passes(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("big.py", "\n".join(f"# {i}" for i in range(200)) + "\n")
config = self._config(["big.py"], max_total_loc=100, reason="known large file")
_, task_read_sets = ra.audit_read_sets(None, config)
same = _baseline(task_totals=[{"task_id": "test-task", "metric": "read_set_total",
"value": 200, "reason": "allowlisted total"}])
self.assertEqual(ra.ratchet_read_set_totals(task_read_sets, same), [])
higher = _baseline(task_totals=[{"task_id": "test-task", "metric": "read_set_total",
"value": 300, "reason": "allowlisted total"}])
self.assertEqual(ra.ratchet_read_set_totals(task_read_sets, higher), [])
finally:
os.chdir(old_cwd)
def test_task_total_increase_fails(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("big.py", "\n".join(f"# {i}" for i in range(200)) + "\n")
config = self._config(["big.py"], max_total_loc=100, reason="known large file")
_, task_read_sets = ra.audit_read_sets(None, config)
baseline = _baseline(task_totals=[{"task_id": "test-task", "metric": "read_set_total",
"value": 150, "reason": "allowlisted total"}])
issues = ra.ratchet_read_set_totals(task_read_sets, baseline)
self.assertEqual(len(issues), 1)
self.assertIn("task total increased from 150 to 200", issues[0]["reason"])
finally:
os.chdir(old_cwd)
def test_task_total_baseline_schema_validated(self) -> None:
baseline = _baseline(task_totals=[{"task_id": "t", "metric": "read_set_total",
"value": 10, "reason": ""}])
new = ra.ratchet_check(_current([]), baseline, None)
self.assertEqual(new[0]["path"], "<baseline>")
self.assertIn("task_read_set_totals[0]", new[0]["reason"])
def test_ratchet_check_covers_read_set_totals(self) -> None:
"""When read-sets are supplied, one ratchet_check call covers task totals."""
current = _current([])
current["task_read_sets"] = [{
"task_id": "test-task",
"files": [{"path": "big.py", "loc": 200}],
"total_loc": 200,
"budget": {"max_total_loc": 100, "reason": "known large file"},
"reason": "known large file",
}]
self.assertEqual(ra.ratchet_check(current, _baseline(), None), [])
new = ra.ratchet_check(current, _baseline(), {"tasks": []})
self.assertEqual(len(new), 1)
self.assertEqual(new[0]["path"], "<read-set:test-task>")
def test_required_config_load_reports_each_failure(self) -> None:
"""load_required_json_file separates missing, malformed, and valid configs."""
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
config, error = ra.load_required_json_file("absent.json")
self.assertIsNone(config)
self.assertIn("required config file not found", error)
self._write("broken.json", '{"tasks": [ truncated\n')
config, error = ra.load_required_json_file("broken.json")
self.assertIsNone(config)
self.assertIn("malformed JSON", error)
self._write("list.json", "[]\n")
config, error = ra.load_required_json_file("list.json")
self.assertIsNone(config)
self.assertIn("not a JSON object", error)
self._write("valid.json", '{"tasks": []}\n')
config, error = ra.load_required_json_file("valid.json")
self.assertEqual(config, {"tasks": []})
self.assertIsNone(error)
finally:
os.chdir(old_cwd)
def test_absent_config_is_a_configuration_failure(self) -> None:
"""A read-set config of None must fail rather than report zero issues."""
errors, task_read_sets = ra.audit_read_sets(None, None)
self.assertEqual(len(errors), 1)
self.assertEqual(errors[0]["metric"], "read_set_config")
self.assertEqual(task_read_sets, [])
def test_unknown_root_key_error(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("a.go", "package p\n")
config = dict(self._config(["a.go"]), task_totals=[])
errors, task_read_sets = ra.audit_read_sets(None, config)
self.assertEqual(len(errors), 1)
self.assertIn("unknown root keys ['task_totals']", errors[0]["reason"])
self.assertEqual(task_read_sets, [])
finally:
os.chdir(old_cwd)
def test_documented_root_keys_are_accepted(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("a.go", "package p\n")
config = dict(self._config(["a.go"]), version="2.0", generated_at="2026-07-16")
errors, task_read_sets = ra.audit_read_sets(None, config)
self.assertEqual(errors, [])
self.assertEqual(len(task_read_sets), 1)
finally:
os.chdir(old_cwd)
def test_read_set_determinism(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("a.go", "package p\nfunc main() {}\n")
self._write("b.py", "def f(): pass\n")
config = self._config(["b.py", "a.go"])
rendered = set()
for _ in range(3):
_, task_read_sets = ra.audit_read_sets(None, config)
rendered.add(json.dumps(task_read_sets, sort_keys=True))
self.assertEqual(len(rendered), 1)
finally:
os.chdir(old_cwd)
# ============================================================
# TestCliExitCodes [REVIEW_REVIEW_REFACTOR-3, REVIEW_REVIEW_REFACTOR-5]
# ============================================================
class TestCliExitCodes(_RepoFixture):
"""The CLI renders every failure and exits with a defined status."""
prefix = "ra-test-cli-"
def setUp(self):
super().setUp()
self._write("a.go", "package p\n\nfunc main() {}\n")
self._write_json("scripts/readability_baseline.json", _baseline())
self._write_json("scripts/readability_read_sets.json", {
"version": "2.0", "generated_at": "2026-07-16", "tasks": [{
"task_id": "t", "description": "fixture task",
"files": ["a.go"],
"budget": {"max_total_loc": 100, "reason": "fixture budget"},
}]})
self._git_init()
def _write_json(self, relpath: str, payload) -> None:
self._write(relpath, json.dumps(payload, indent=2) + "\n")
def _run_cli(self, *args: str) -> subprocess.CompletedProcess:
return subprocess.run([sys.executable, AUDIT_SCRIPT, *args],
cwd=self.tmpdir, capture_output=True, text=True)
def test_clean_check_exits_zero(self) -> None:
proc = self._run_cli("--check", "--input-mode", "worktree")
self.assertEqual(proc.returncode, 0, proc.stderr)
self.assertIn("RATCHET OK", proc.stderr)
self.assertIn("readability-audit:", proc.stdout)
def test_missing_read_set_path_exits_two(self) -> None:
self._write_json("scripts/readability_read_sets.json", {
"version": "2.0", "generated_at": "2026-07-16", "tasks": [{
"task_id": "t", "description": "fixture task",
"files": ["a.go", "gone.go"],
"budget": {"max_total_loc": 100, "reason": "fixture budget"},
}]})
proc = self._run_cli("--check", "--input-mode", "worktree")
self.assertEqual(proc.returncode, 2, proc.stderr)
self.assertIn("READ-SET CONFIGURATION FAIL", proc.stderr)
self.assertIn("missing read_set path 'gone.go'", proc.stderr)
def test_reasonless_over_budget_exits_four(self) -> None:
self._write_json("scripts/readability_read_sets.json", {
"version": "2.0", "generated_at": "2026-07-16", "tasks": [{
"task_id": "t", "description": "fixture task",
"files": ["a.go"],
"budget": {"max_total_loc": 1, "reason": ""},
}]})
proc = self._run_cli("--check", "--input-mode", "worktree")
self.assertEqual(proc.returncode, 4, proc.stderr)
self.assertIn("RATCHET FAIL", proc.stderr)
self.assertIn("read_set_budget=3", proc.stderr)
def test_unallowlisted_over_budget_total_exits_four(self) -> None:
self._write_json("scripts/readability_read_sets.json", {
"version": "2.0", "generated_at": "2026-07-16", "tasks": [{
"task_id": "t", "description": "fixture task",
"files": ["a.go"],
"budget": {"max_total_loc": 1, "reason": "documented"},
}]})
proc = self._run_cli("--check", "--input-mode", "worktree")
self.assertEqual(proc.returncode, 4, proc.stderr)
self.assertIn("<read-set:t>: read_set_total=3", proc.stderr)
def test_missing_read_set_config_exits_two(self) -> None:
"""Deleting the required config must not silently skip the total ratchet."""
os.remove(os.path.join(self.tmpdir, "scripts/readability_read_sets.json"))
proc = self._run_cli("--check", "--input-mode", "worktree")
self.assertEqual(proc.returncode, 2, proc.stderr)
self.assertIn("READ-SET CONFIGURATION FAIL", proc.stderr)
self.assertIn("required config file not found", proc.stderr)
self.assertNotIn("RATCHET OK", proc.stderr)
def test_malformed_read_set_config_exits_two(self) -> None:
self._write("scripts/readability_read_sets.json", '{"tasks": [ truncated\n')
proc = self._run_cli("--check", "--input-mode", "worktree")
self.assertEqual(proc.returncode, 2, proc.stderr)
self.assertIn("READ-SET CONFIGURATION FAIL", proc.stderr)
self.assertIn("malformed JSON", proc.stderr)
self.assertNotIn("RATCHET OK", proc.stderr)
def test_non_object_read_set_config_root_exits_two(self) -> None:
self._write_json("scripts/readability_read_sets.json", [{"task_id": "t"}])
proc = self._run_cli("--check", "--input-mode", "worktree")
self.assertEqual(proc.returncode, 2, proc.stderr)
self.assertIn("is not a JSON object", proc.stderr)
def test_unknown_read_set_root_key_exits_two(self) -> None:
self._write_json("scripts/readability_read_sets.json", {
"tasks": [{"task_id": "t", "files": ["a.go"],
"budget": {"max_total_loc": 100, "reason": "fixture budget"}}],
"task_totals": [],
})
proc = self._run_cli("--check", "--input-mode", "worktree")
self.assertEqual(proc.returncode, 2, proc.stderr)
self.assertIn("unknown root keys ['task_totals']", proc.stderr)
def test_valid_read_set_config_reaches_total_ratchet(self) -> None:
"""The contrast case: a valid config is always ratcheted, and can fail."""
self._write_json("scripts/readability_read_sets.json", {
"version": "2.0",
"generated_at": "2026-07-16",
"tasks": [{"task_id": "t", "description": "fixture task", "files": ["a.go"],
"budget": {"max_total_loc": 1, "reason": "documented"}}],
})
proc = self._run_cli("--check", "--input-mode", "worktree")
self.assertEqual(proc.returncode, 4, proc.stderr)
self.assertIn("<read-set:t>: read_set_total=3", proc.stderr)
def test_read_set_schema_error_exits_two(self) -> None:
"""Every schema error is a configuration failure, not a silent pass."""
valid = {
"version": "2.0", "generated_at": "2026-07-16",
"tasks": [{"task_id": "t", "description": "fixture task",
"files": ["a.go"],
"budget": {"max_total_loc": 100, "reason": "fixture budget"}}],
}
cases = [
("missing version", {k: v for k, v in valid.items() if k != "version"},
"missing required root key 'version'"),
("wrong-type generated_at", {**valid, "generated_at": 20260716},
"root 'generated_at' must be a non-empty string"),
("unsupported version", {**valid, "version": "3.0"},
"unsupported version '3.0'"),
("missing description", {**valid, "tasks": [
{"task_id": "t", "files": ["a.go"],
"budget": {"max_total_loc": 100, "reason": "fixture budget"}}]},
"missing required non-empty 'description'"),
("wrong-type description", {**valid, "tasks": [
{"task_id": "t", "description": 7, "files": ["a.go"],
"budget": {"max_total_loc": 100, "reason": "fixture budget"}}]},
"missing required non-empty 'description'"),
]
for label, config, expected in cases:
with self.subTest(case=label):
self._write_json("scripts/readability_read_sets.json", config)
proc = self._run_cli("--check", "--input-mode", "worktree")
self.assertEqual(proc.returncode, 2, proc.stderr)
self.assertIn("READ-SET CONFIGURATION FAIL:", proc.stderr)
self.assertIn(expected, proc.stderr)
def test_canonical_read_set_config_satisfies_exact_schema(self) -> None:
"""The tracked read-set config is itself a valid exact-schema config."""
with open(os.path.join(SCRIPTS_DIR, "readability_read_sets.json"),
encoding="utf-8") as handle:
config = json.load(handle)
self._write_json("scripts/readability_read_sets.json", {
**config,
"tasks": [{**task, "files": ["a.go"]} for task in config["tasks"]],
})
proc = self._run_cli("--input-mode", "worktree")
self.assertEqual(proc.returncode, 0, proc.stderr)
self.assertNotIn("READ-SET CONFIGURATION FAIL:", proc.stderr)
def test_missing_baseline_exits_three(self) -> None:
os.remove(os.path.join(self.tmpdir, "scripts/readability_baseline.json"))
proc = self._run_cli("--check", "--input-mode", "worktree")
self.assertEqual(proc.returncode, 3, proc.stderr)
self.assertIn("baseline not found", proc.stderr)
def test_report_is_written_even_when_ratchet_fails(self) -> None:
"""The projection is evidence, so it is written before the ratchet verdict."""
self._write_json("scripts/readability_read_sets.json", {
"version": "2.0", "generated_at": "2026-07-16", "tasks": [{
"task_id": "t", "description": "fixture task",
"files": ["a.go"],
"budget": {"max_total_loc": 1, "reason": ""},
}]})
out = os.path.join(self.tmpdir, "out", "report.json")
proc = self._run_cli("--check", "--input-mode", "worktree", "--output", out)
self.assertEqual(proc.returncode, 4, proc.stderr)
with open(out, encoding="utf-8") as handle:
report = json.load(handle)
self.assertEqual([e["path"] for e in report["files"]], ["a.go"])
self.assertEqual(report["input_mode"], "worktree")
# ============================================================
# TestInputModeTrackedVsWorktree [REVIEW_REVIEW_REFACTOR-1]
# ============================================================
class TestInputModeTrackedVsWorktree(_RepoFixture):
"""worktree is the post-commit projection: modified files stay, deletions go."""
prefix = "ra-test-mode-"
def setUp(self):
super().setUp()
self._write("tracked.go", "package p\nfunc main() {}\n")
self._write("other.go", "package p\n")
self._git_init()
def test_tracked_mode_only_index(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("untracked.go", "package q\n")
files = ra.collect_files("tracked")
self.assertIn("tracked.go", files)
self.assertNotIn("untracked.go", files)
finally:
os.chdir(old_cwd)
def test_worktree_mode_includes_untracked(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("untracked.go", "package q\n")
files = ra.collect_files("worktree")
self.assertIn("tracked.go", files)
self.assertIn("untracked.go", files)
finally:
os.chdir(old_cwd)
def test_worktree_mode_includes_modified_tracked(self) -> None:
"""A modified tracked file is still part of the projection."""
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("tracked.go", "package p\nfunc main() {}\nfunc extra() {}\n")
self._write("other.go", "package p\nfunc staged() {}\n")
self._git("add", "other.go")
files = ra.collect_files("worktree")
self.assertIn("tracked.go", files)
self.assertIn("other.go", files)
finally:
os.chdir(old_cwd)
def test_worktree_mode_excludes_unstaged_deletion(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
os.remove(os.path.join(self.tmpdir, "other.go"))
files = ra.collect_files("worktree")
self.assertIn("tracked.go", files)
self.assertNotIn("other.go", files)
# tracked mode still reports the index entry
self.assertIn("other.go", ra.collect_files("tracked"))
finally:
os.chdir(old_cwd)
def test_worktree_mode_excludes_staged_deletion(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._git("rm", "-q", "other.go")
files = ra.collect_files("worktree")
self.assertIn("tracked.go", files)
self.assertNotIn("other.go", files)
finally:
os.chdir(old_cwd)
def test_worktree_mode_excludes_deleted(self) -> None:
"""git rm --cached leaves the file on disk but stages its removal."""
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("todelete.go", "package r\n")
self._git("add", "todelete.go")
self._git("commit", "-q", "-m", "add")
self._git("rm", "--cached", "-q", "todelete.go")
files = ra.collect_files("worktree")
self.assertIn("tracked.go", files)
self.assertNotIn("todelete.go", files)
finally:
os.chdir(old_cwd)
def test_worktree_mode_excludes_excluded_patterns(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("proto/gen/generated.go", "package gen\n")
self.assertNotIn("proto/gen/generated.go", ra.collect_files("worktree"))
finally:
os.chdir(old_cwd)
def test_tracked_and_worktree_produce_same_for_clean_repo(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self.assertEqual(ra.collect_files("tracked"), ra.collect_files("worktree"))
finally:
os.chdir(old_cwd)
def test_worktree_matches_tracked_after_commit(self) -> None:
"""The worktree projection equals what tracked reports once committed."""
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
self._write("tracked.go", "package p\nfunc main() {}\nfunc extra() {}\n")
self._write("added.go", "package q\n")
os.remove(os.path.join(self.tmpdir, "other.go"))
projected = ra.collect_files("worktree")
self._git("add", "-A")
self._git("commit", "-q", "-m", "apply worktree")
self.assertEqual(ra.collect_files("tracked"), projected)
self.assertEqual(ra.collect_files("worktree"), projected)
finally:
os.chdir(old_cwd)
# ============================================================
# TestThresholdBoundaries
# ============================================================
class TestThresholdBoundaries(_RepoFixture):
"""Threshold enforcement at boundary values (production defaults)."""
def setUp(self):
super().setUp()
lines_1000 = "\n".join(f"line {i}" for i in range(1000)) + "\n"
self._write("src/at_boundary.go", lines_1000)
lines_1001 = "\n".join(f"line {i}" for i in range(1001)) + "\n"
self._write("src/over_boundary.go", lines_1001)
body_118 = "\n".join(f" line{i}" for i in range(118))
self._write("src/fn_at.go", f"func fn_at() {{\n{body_118}\n}}\n")
body_119 = "\n".join(f" line{i}" for i in range(119))
self._write("src/fn_over.go", f"func fn_over() {{\n{body_119}\n}}\n")
def test_exact_boundary_passes(self) -> None:
"""1000 LOC production → split_review only; 120 LOC function → warning only."""
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
result = ra.audit(["src/at_boundary.go", "src/fn_at.go"])
violations = result["violations"]
self.assertEqual(len(violations), 2)
vmap = {(v["path"], v["metric"]): v for v in violations}
self.assertEqual(vmap[("src/at_boundary.go", "file_loc")]["level"], "split_review")
self.assertEqual(vmap[("src/fn_at.go", "function_loc")]["level"], "warning")
finally:
os.chdir(old_cwd)
def test_one_over_violates(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
result = ra.audit(["src/over_boundary.go", "src/fn_over.go"])
file_violations = [v for v in result["violations"] if v["metric"] == "file_loc"]
func_violations = [v for v in result["violations"] if v["metric"] == "function_loc"]
self.assertEqual(len(file_violations), 1)
self.assertEqual(file_violations[0]["path"], "src/over_boundary.go")
self.assertEqual(file_violations[0]["level"], "exception")
self.assertEqual(len(func_violations), 1)
self.assertEqual(func_violations[0]["path"], "src/fn_over.go")
self.assertEqual(func_violations[0]["level"], "split_review")
finally:
os.chdir(old_cwd)
# ============================================================
# TestNewOrIncreasedViolationFails
# ============================================================
class TestNewOrIncreasedViolationFails(unittest.TestCase):
"""Ratchet: new or increased violations should FAIL."""
def test_new_violation_fails(self) -> None:
current = _current([
{"path": "src/new_big.go", "metric": "file_loc", "level": "exception",
"value": 1500, "reason": "new violation"},
])
new = ra.ratchet_check(current, _baseline(), None)
self.assertEqual(len(new), 1)
self.assertEqual(new[0]["path"], "src/new_big.go")
self.assertIn("new", new[0]["reason"])
def test_increased_violation_fails(self) -> None:
baseline = _baseline(file_thresholds=[
{"path": "src/big.go", "metric": "file_loc", "level": "exception",
"value": 1100, "reason": "old"},
])
current = _current([
{"path": "src/big.go", "metric": "file_loc", "level": "exception",
"value": 1200, "reason": "now bigger"},
])
new = ra.ratchet_check(current, baseline, None)
self.assertEqual(len(new), 1)
self.assertEqual(new[0]["path"], "src/big.go")
self.assertIn("increased", new[0]["reason"])
# ============================================================
# TestReadSetDeterministic
# ============================================================
class TestReadSetDeterministic(_RepoFixture):
"""Read-set (import) extraction is deterministic across runs."""
def setUp(self):
super().setUp()
self._write("pkg/a.go", textwrap.dedent("""\
package pkg
import "fmt"
import "io"
func A() {
fmt.Println("hello")
}
"""))
self._write("pkg/b.py", textwrap.dedent("""\
import os
import sys
def b_func():
pass
"""))
self._write("pkg/c.dart", textwrap.dedent("""\
import 'package:foo/bar.dart';
import 'utils.dart';
void c_func() {}
"""))
def test_read_set_totals_are_deterministic(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
files = ["pkg/a.go", "pkg/b.py", "pkg/c.dart"]
results = [ra.audit(files) for _ in range(3)]
self.assertEqual(len({json.dumps(r, sort_keys=True) for r in results}), 1)
entries = {e["path"]: e for e in results[0]["files"]}
self.assertEqual(entries["pkg/a.go"]["read_set"], ["fmt", "io"])
self.assertEqual(entries["pkg/b.py"]["read_set"], ["os", "sys"])
self.assertEqual(len(entries["pkg/c.dart"]["read_set"]), 2)
finally:
os.chdir(old_cwd)
# ============================================================
# TestFunctionExtraction
# ============================================================
class TestFunctionExtraction(unittest.TestCase):
"""Verify function extraction across languages."""
def test_python_function_extraction(self) -> None:
source = textwrap.dedent("""\
class Foo:
def bar(self):
pass
def baz():
x = 1
y = 2
return x + y
""")
funcs = ra._extract_functions_python(source, "test.py")
names = {f["name"] for f in funcs}
self.assertNotIn("Foo", names)
self.assertEqual(names, {"Foo.bar", "baz"})
baz = next(f for f in funcs if f["name"] == "baz")
self.assertEqual(baz["loc"], 4)
def test_go_function_extraction(self) -> None:
source = textwrap.dedent("""\
package main
func main() {
fmt.Println("hello")
}
func helper() {
x := 1
y := 2
z := 3
return x + y + z
}
""")
names = {f["name"] for f in ra._extract_functions_go(source, "main.go")}
self.assertEqual(names, {"main", "helper"})
def test_dart_function_extraction(self) -> None:
source = textwrap.dedent("""\
void main() {
print('hello');
}
int compute(int a, int b) {
return a + b;
}
""")
names = {f["name"] for f in ra._extract_functions_dart(source, "test.dart")}
self.assertEqual(names, {"main", "compute"})
# ============================================================
# TestJSONDeterminism
# ============================================================
class TestJSONDeterminism(_RepoFixture):
"""Full audit output is deterministic and byte-identical across runs."""
prefix = "ra-test-json-"
def setUp(self):
super().setUp()
self._write("a.go", "package p\n\nfunc main() {}\n")
self._write("b.py", "def f(): pass\n")
def test_audit_output_deterministic(self) -> None:
old_cwd = os.getcwd()
os.chdir(self.tmpdir)
try:
r1 = ra.audit(["a.go", "b.py"])
r2 = ra.audit(["a.go", "b.py"])
self.assertEqual(json.dumps(r1, sort_keys=True), json.dumps(r2, sort_keys=True))
finally:
os.chdir(old_cwd)
if __name__ == "__main__":
unittest.main()