|
|
|
@@ -77,6 +77,10 @@ def assert_has_actionable_violation(stderr: str, path: str) -> None:
|
|
|
|
|
assert "no-new-getattr" in stderr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main_branch_rev(repo: Path) -> str:
|
|
|
|
|
return git(repo, "rev-parse", "main")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_resolve_ast_grep_command_prefers_ast_grep(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
|
|
|
module = load_guard_module()
|
|
|
|
|
monkeypatch.setattr(
|
|
|
|
@@ -130,8 +134,46 @@ def test_resolve_ast_grep_command_raises_without_explicit_binary(monkeypatch: py
|
|
|
|
|
module.resolve_ast_grep_command()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_cli_requires_explicit_diff_source(tmp_path: Path) -> None:
|
|
|
|
|
result = run_script(tmp_path)
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 2
|
|
|
|
|
assert "one of the arguments --staged --base-rev is required" in result.stderr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_cli_rejects_mixed_diff_sources(tmp_path: Path) -> None:
|
|
|
|
|
result = run_script(tmp_path, "--staged", "--base-rev", "deadbeef")
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 2
|
|
|
|
|
assert "not allowed with argument" in result.stderr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_cli_help_exposes_only_new_diff_source_flags(tmp_path: Path) -> None:
|
|
|
|
|
help_result = run_script(tmp_path, "--help")
|
|
|
|
|
|
|
|
|
|
assert help_result.returncode == 0
|
|
|
|
|
assert "--staged" in help_result.stdout
|
|
|
|
|
assert "--base-rev" in help_result.stdout
|
|
|
|
|
assert "--mode" not in help_result.stdout
|
|
|
|
|
assert "--merge-target" not in help_result.stdout
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--staged", "--mode", "ci")
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 2
|
|
|
|
|
assert "unrecognized arguments: --mode ci" in result.stderr
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", "deadbeef", "--merge-target", "main")
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 2
|
|
|
|
|
assert "unrecognized arguments: --merge-target main" in result.stderr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_style_workflow_wires_no_new_getattr_guard() -> None:
|
|
|
|
|
workflow = (REPO_ROOT / ".github" / "workflows" / "style.yml").read_text(encoding="utf-8")
|
|
|
|
|
assert re.search(
|
|
|
|
|
r"(?ms)^on:\n workflow_call:\n inputs:\n base-rev:\n required: true\n type: string\n",
|
|
|
|
|
workflow,
|
|
|
|
|
)
|
|
|
|
|
python_style_job = re.search(
|
|
|
|
|
r"(?ms)^ python-style:\n(?P<job>.*?)(?=^ [a-z0-9-]+:\n|\Z)",
|
|
|
|
|
workflow,
|
|
|
|
@@ -157,8 +199,9 @@ def test_style_workflow_wires_no_new_getattr_guard() -> None:
|
|
|
|
|
assert "scripts/check_no_new_getattr.py\n" in files_block
|
|
|
|
|
assert "scripts/ast_grep_rules/no_new_getattr.yml\n" in files_block
|
|
|
|
|
assert ".github/workflows/style.yml\n" in files_block
|
|
|
|
|
assert ".github/workflows/main-ci.yml\n" in files_block
|
|
|
|
|
|
|
|
|
|
guard_command = "scripts/check_no_new_getattr.py --mode ci --merge-target main"
|
|
|
|
|
guard_command = 'scripts/check_no_new_getattr.py --base-rev "${{ inputs.base-rev }}"'
|
|
|
|
|
assert guard_command in job_text
|
|
|
|
|
|
|
|
|
|
guard_step = re.search(
|
|
|
|
@@ -168,55 +211,34 @@ def test_style_workflow_wires_no_new_getattr_guard() -> None:
|
|
|
|
|
)
|
|
|
|
|
assert guard_step is not None
|
|
|
|
|
|
|
|
|
|
pre_guard_text = job_text[: guard_step.start()]
|
|
|
|
|
step_pattern = r"(?ms)^ - name: [^\n]*\n(?P<step>.*?)(?=^ - name: |\Z)"
|
|
|
|
|
fetch_step_text = next(
|
|
|
|
|
(
|
|
|
|
|
match.group("step")
|
|
|
|
|
for match in re.finditer(step_pattern, pre_guard_text)
|
|
|
|
|
if any(
|
|
|
|
|
re.search(pattern, line)
|
|
|
|
|
for line in match.group("step").splitlines()
|
|
|
|
|
for pattern in (
|
|
|
|
|
r"git fetch .*refs/heads/main:refs/remotes/origin/main",
|
|
|
|
|
r"git fetch .*main:refs/remotes/origin/main",
|
|
|
|
|
r"git fetch .*refs/remotes/origin/main",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
),
|
|
|
|
|
"",
|
|
|
|
|
)
|
|
|
|
|
assert fetch_step_text
|
|
|
|
|
assert "git fetch" in fetch_step_text
|
|
|
|
|
assert "origin" in fetch_step_text
|
|
|
|
|
assert any(
|
|
|
|
|
re.search(pattern, line)
|
|
|
|
|
for line in fetch_step_text.splitlines()
|
|
|
|
|
for pattern in (
|
|
|
|
|
r"git fetch .*refs/heads/main:refs/remotes/origin/main",
|
|
|
|
|
r"git fetch .*main:refs/remotes/origin/main",
|
|
|
|
|
r"git fetch .*refs/remotes/origin/main",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
bind_step = re.search(
|
|
|
|
|
r"(?ms)^ - name: Bind merge target branch for getattr guard\n(?P<step>.*?)(?=^ - name: |\Z)",
|
|
|
|
|
pre_guard_text,
|
|
|
|
|
)
|
|
|
|
|
assert bind_step is not None
|
|
|
|
|
bind_step_text = bind_step.group("step")
|
|
|
|
|
assert any(
|
|
|
|
|
command in bind_step_text
|
|
|
|
|
for command in (
|
|
|
|
|
"git branch main origin/main",
|
|
|
|
|
"git checkout -B main origin/main",
|
|
|
|
|
"git switch -C main origin/main",
|
|
|
|
|
"git update-ref refs/heads/main refs/remotes/origin/main",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
assert "GITHUB_BASE_SHA" not in guard_step.group("step")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_ci_mode_passes_when_only_legacy_getattr_exists(tmp_path: Path) -> None:
|
|
|
|
|
def test_main_ci_passes_style_base_rev_input() -> None:
|
|
|
|
|
workflow = (REPO_ROOT / ".github" / "workflows" / "main-ci.yml").read_text(encoding="utf-8")
|
|
|
|
|
style_job = re.search(
|
|
|
|
|
r"(?ms)^ style-check:\n(?P<job>.*?)(?=^ [a-z0-9-]+:\n|\Z)",
|
|
|
|
|
workflow,
|
|
|
|
|
)
|
|
|
|
|
assert style_job is not None
|
|
|
|
|
assert "uses: ./.github/workflows/style.yml" in style_job.group("job")
|
|
|
|
|
assert 'base-rev: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }}' in style_job.group(
|
|
|
|
|
"job"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
api_filter = re.search(
|
|
|
|
|
r"(?ms)^ api:\n(?P<filter>(?:^ - '[^']+'\n)+)",
|
|
|
|
|
workflow,
|
|
|
|
|
)
|
|
|
|
|
assert api_filter is not None
|
|
|
|
|
filter_text = api_filter.group("filter")
|
|
|
|
|
assert "scripts/check_no_new_getattr.py" in filter_text
|
|
|
|
|
assert "scripts/ast_grep_rules/no_new_getattr.yml" in filter_text
|
|
|
|
|
assert ".github/workflows/style.yml" in filter_text
|
|
|
|
|
assert ".github/workflows/main-ci.yml" in filter_text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_base_rev_mode_passes_when_only_legacy_getattr_exists(tmp_path: Path) -> None:
|
|
|
|
|
init_repo(tmp_path)
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
@@ -239,12 +261,12 @@ def test_ci_mode_passes_when_only_legacy_getattr_exists(tmp_path: Path) -> None:
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "unrelated change")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "ci", "--merge-target", "main")
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", main_branch_rev(tmp_path))
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_ci_mode_fails_for_new_file_with_getattr(tmp_path: Path) -> None:
|
|
|
|
|
def test_base_rev_mode_fails_for_new_file_with_getattr(tmp_path: Path) -> None:
|
|
|
|
|
init_repo(tmp_path)
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
@@ -267,13 +289,13 @@ def test_ci_mode_fails_for_new_file_with_getattr(tmp_path: Path) -> None:
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "add new getattr usage")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "ci", "--merge-target", "main")
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", main_branch_rev(tmp_path))
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 1
|
|
|
|
|
assert_has_actionable_violation(result.stderr, "pkg/new_usage.py")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_ci_mode_fails_for_new_file_with_two_arg_getattr(tmp_path: Path) -> None:
|
|
|
|
|
def test_base_rev_mode_fails_for_new_file_with_two_arg_getattr(tmp_path: Path) -> None:
|
|
|
|
|
init_repo(tmp_path)
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
@@ -296,13 +318,13 @@ def test_ci_mode_fails_for_new_file_with_two_arg_getattr(tmp_path: Path) -> None
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "add new two-arg getattr usage")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "ci", "--merge-target", "main")
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", main_branch_rev(tmp_path))
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 1
|
|
|
|
|
assert_has_actionable_violation(result.stderr, "pkg/new_usage.py")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_ci_mode_fails_for_new_file_with_builtins_getattr(tmp_path: Path) -> None:
|
|
|
|
|
def test_base_rev_mode_fails_for_new_file_with_builtins_getattr(tmp_path: Path) -> None:
|
|
|
|
|
init_repo(tmp_path)
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
@@ -328,13 +350,13 @@ def test_ci_mode_fails_for_new_file_with_builtins_getattr(tmp_path: Path) -> Non
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "add new builtins getattr usage")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "ci", "--merge-target", "main")
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", main_branch_rev(tmp_path))
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 1
|
|
|
|
|
assert_has_actionable_violation(result.stderr, "pkg/new_usage.py")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_ci_mode_fails_for_new_file_with_two_arg_builtins_getattr(tmp_path: Path) -> None:
|
|
|
|
|
def test_base_rev_mode_fails_for_new_file_with_two_arg_builtins_getattr(tmp_path: Path) -> None:
|
|
|
|
|
init_repo(tmp_path)
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
@@ -360,13 +382,13 @@ def test_ci_mode_fails_for_new_file_with_two_arg_builtins_getattr(tmp_path: Path
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "add new two-arg builtins getattr usage")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "ci", "--merge-target", "main")
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", main_branch_rev(tmp_path))
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 1
|
|
|
|
|
assert_has_actionable_violation(result.stderr, "pkg/new_usage.py")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_ci_mode_fails_for_new_file_with_dunder_builtins_getattr(tmp_path: Path) -> None:
|
|
|
|
|
def test_base_rev_mode_fails_for_new_file_with_dunder_builtins_getattr(tmp_path: Path) -> None:
|
|
|
|
|
init_repo(tmp_path)
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
@@ -389,13 +411,13 @@ def test_ci_mode_fails_for_new_file_with_dunder_builtins_getattr(tmp_path: Path)
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "add new dunder builtins getattr usage")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "ci", "--merge-target", "main")
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", main_branch_rev(tmp_path))
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 1
|
|
|
|
|
assert_has_actionable_violation(result.stderr, "pkg/new_usage.py")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_ci_mode_fails_for_new_file_with_two_arg_dunder_builtins_getattr(tmp_path: Path) -> None:
|
|
|
|
|
def test_base_rev_mode_fails_for_new_file_with_two_arg_dunder_builtins_getattr(tmp_path: Path) -> None:
|
|
|
|
|
init_repo(tmp_path)
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
@@ -418,13 +440,13 @@ def test_ci_mode_fails_for_new_file_with_two_arg_dunder_builtins_getattr(tmp_pat
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "add new two-arg dunder builtins getattr usage")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "ci", "--merge-target", "main")
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", main_branch_rev(tmp_path))
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 1
|
|
|
|
|
assert_has_actionable_violation(result.stderr, "pkg/new_usage.py")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_ci_mode_uses_merge_base_against_main_not_just_head_parent(tmp_path: Path) -> None:
|
|
|
|
|
def test_base_rev_mode_uses_provided_base_revision_not_head_parent(tmp_path: Path) -> None:
|
|
|
|
|
init_repo(tmp_path)
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
@@ -435,6 +457,7 @@ def test_ci_mode_uses_merge_base_against_main_not_just_head_parent(tmp_path: Pat
|
|
|
|
|
""",
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "baseline")
|
|
|
|
|
base_rev = main_branch_rev(tmp_path)
|
|
|
|
|
checkout_feature_branch(tmp_path)
|
|
|
|
|
|
|
|
|
|
write_repo_file(
|
|
|
|
@@ -457,13 +480,77 @@ def test_ci_mode_uses_merge_base_against_main_not_just_head_parent(tmp_path: Pat
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "later feature commit does not touch violating file")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "ci", "--merge-target", "main")
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", base_rev)
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 1
|
|
|
|
|
assert_has_actionable_violation(result.stderr, "pkg/introduced_earlier.py")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_pre_commit_mode_reads_staged_content_only(tmp_path: Path) -> None:
|
|
|
|
|
def test_base_rev_mode_works_without_local_main_branch(tmp_path: Path) -> None:
|
|
|
|
|
init_repo(tmp_path)
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
|
"pkg/existing.py",
|
|
|
|
|
"""
|
|
|
|
|
def stable() -> str:
|
|
|
|
|
return "ok"
|
|
|
|
|
""",
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "baseline")
|
|
|
|
|
base_rev = main_branch_rev(tmp_path)
|
|
|
|
|
checkout_feature_branch(tmp_path)
|
|
|
|
|
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
|
"pkg/other.py",
|
|
|
|
|
"""
|
|
|
|
|
def meaning() -> int:
|
|
|
|
|
return 42
|
|
|
|
|
""",
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "feature change")
|
|
|
|
|
|
|
|
|
|
git(tmp_path, "checkout", "--detach", "HEAD")
|
|
|
|
|
git(tmp_path, "branch", "-D", "main")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", base_rev)
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_base_rev_mode_ignores_github_base_sha_environment(
|
|
|
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
|
|
|
) -> None:
|
|
|
|
|
init_repo(tmp_path)
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
|
"pkg/existing.py",
|
|
|
|
|
"""
|
|
|
|
|
def stable() -> str:
|
|
|
|
|
return "ok"
|
|
|
|
|
""",
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "baseline")
|
|
|
|
|
base_rev = main_branch_rev(tmp_path)
|
|
|
|
|
checkout_feature_branch(tmp_path)
|
|
|
|
|
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
|
"pkg/other.py",
|
|
|
|
|
"""
|
|
|
|
|
def meaning() -> int:
|
|
|
|
|
return 42
|
|
|
|
|
""",
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "feature change")
|
|
|
|
|
monkeypatch.setenv("GITHUB_BASE_SHA", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", base_rev)
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_staged_mode_reads_staged_content_only(tmp_path: Path) -> None:
|
|
|
|
|
init_repo(tmp_path)
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
@@ -494,12 +581,12 @@ def test_pre_commit_mode_reads_staged_content_only(tmp_path: Path) -> None:
|
|
|
|
|
""",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "pre-commit")
|
|
|
|
|
result = run_script(tmp_path, "--staged")
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_pre_commit_mode_fails_for_staged_two_arg_getattr(tmp_path: Path) -> None:
|
|
|
|
|
def test_staged_mode_fails_for_staged_two_arg_getattr(tmp_path: Path) -> None:
|
|
|
|
|
init_repo(tmp_path)
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
@@ -521,13 +608,13 @@ def test_pre_commit_mode_fails_for_staged_two_arg_getattr(tmp_path: Path) -> Non
|
|
|
|
|
)
|
|
|
|
|
git(tmp_path, "add", "pkg/module.py")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "pre-commit")
|
|
|
|
|
result = run_script(tmp_path, "--staged")
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 1
|
|
|
|
|
assert_has_actionable_violation(result.stderr, "pkg/module.py")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_pre_commit_mode_fails_for_staged_builtins_getattr(tmp_path: Path) -> None:
|
|
|
|
|
def test_staged_mode_fails_for_staged_builtins_getattr(tmp_path: Path) -> None:
|
|
|
|
|
init_repo(tmp_path)
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
@@ -552,13 +639,13 @@ def test_pre_commit_mode_fails_for_staged_builtins_getattr(tmp_path: Path) -> No
|
|
|
|
|
)
|
|
|
|
|
git(tmp_path, "add", "pkg/module.py")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "pre-commit")
|
|
|
|
|
result = run_script(tmp_path, "--staged")
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 1
|
|
|
|
|
assert_has_actionable_violation(result.stderr, "pkg/module.py")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_pre_commit_mode_fails_for_staged_two_arg_builtins_getattr(tmp_path: Path) -> None:
|
|
|
|
|
def test_staged_mode_fails_for_staged_two_arg_builtins_getattr(tmp_path: Path) -> None:
|
|
|
|
|
init_repo(tmp_path)
|
|
|
|
|
write_repo_file(
|
|
|
|
|
tmp_path,
|
|
|
|
@@ -583,7 +670,7 @@ def test_pre_commit_mode_fails_for_staged_two_arg_builtins_getattr(tmp_path: Pat
|
|
|
|
|
)
|
|
|
|
|
git(tmp_path, "add", "pkg/module.py")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "pre-commit")
|
|
|
|
|
result = run_script(tmp_path, "--staged")
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 1
|
|
|
|
|
assert_has_actionable_violation(result.stderr, "pkg/module.py")
|
|
|
|
@@ -603,6 +690,7 @@ def test_modified_hunk_with_same_getattr_count_is_allowed(tmp_path: Path) -> Non
|
|
|
|
|
""",
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "baseline")
|
|
|
|
|
base_rev = main_branch_rev(tmp_path)
|
|
|
|
|
checkout_feature_branch(tmp_path)
|
|
|
|
|
|
|
|
|
|
write_repo_file(
|
|
|
|
@@ -618,7 +706,7 @@ def test_modified_hunk_with_same_getattr_count_is_allowed(tmp_path: Path) -> Non
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "touch legacy getattr hunk")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "ci", "--merge-target", "main")
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", base_rev)
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
|
|
|
|
|
|
|
@@ -635,6 +723,7 @@ def test_modified_hunk_with_decreased_getattr_count_is_allowed(tmp_path: Path) -
|
|
|
|
|
""",
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "baseline")
|
|
|
|
|
base_rev = main_branch_rev(tmp_path)
|
|
|
|
|
checkout_feature_branch(tmp_path)
|
|
|
|
|
|
|
|
|
|
write_repo_file(
|
|
|
|
@@ -647,7 +736,7 @@ def test_modified_hunk_with_decreased_getattr_count_is_allowed(tmp_path: Path) -
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "remove one legacy getattr")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "ci", "--merge-target", "main")
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", base_rev)
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
|
|
|
|
|
|
|
@@ -663,6 +752,7 @@ def test_modified_hunk_with_increased_getattr_count_fails(tmp_path: Path) -> Non
|
|
|
|
|
""",
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "baseline")
|
|
|
|
|
base_rev = main_branch_rev(tmp_path)
|
|
|
|
|
checkout_feature_branch(tmp_path)
|
|
|
|
|
|
|
|
|
|
write_repo_file(
|
|
|
|
@@ -676,7 +766,7 @@ def test_modified_hunk_with_increased_getattr_count_fails(tmp_path: Path) -> Non
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "add one more getattr")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "ci", "--merge-target", "main")
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", base_rev)
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 1
|
|
|
|
|
assert_has_actionable_violation(result.stderr, "pkg/sample.py")
|
|
|
|
@@ -694,6 +784,7 @@ def test_inline_noqa_suppression_with_explanatory_text_skips_added_getattr(tmp_p
|
|
|
|
|
""",
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "baseline")
|
|
|
|
|
base_rev = main_branch_rev(tmp_path)
|
|
|
|
|
checkout_feature_branch(tmp_path)
|
|
|
|
|
|
|
|
|
|
write_repo_file(
|
|
|
|
@@ -706,7 +797,7 @@ def test_inline_noqa_suppression_with_explanatory_text_skips_added_getattr(tmp_p
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "add suppressed getattr")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "ci", "--merge-target", "main")
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", base_rev)
|
|
|
|
|
|
|
|
|
|
assert "no-new-getattr needed for plugin-defined attributes" in (tmp_path / "pkg/existing.py").read_text(
|
|
|
|
|
encoding="utf-8"
|
|
|
|
@@ -725,6 +816,7 @@ def test_inline_noqa_without_explanatory_text_is_not_sufficient(tmp_path: Path)
|
|
|
|
|
""",
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "baseline")
|
|
|
|
|
base_rev = main_branch_rev(tmp_path)
|
|
|
|
|
checkout_feature_branch(tmp_path)
|
|
|
|
|
|
|
|
|
|
write_repo_file(
|
|
|
|
@@ -737,7 +829,7 @@ def test_inline_noqa_without_explanatory_text_is_not_sufficient(tmp_path: Path)
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "add bare noqa getattr")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "ci", "--merge-target", "main")
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", base_rev)
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 1
|
|
|
|
|
assert_has_actionable_violation(result.stderr, "pkg/existing.py")
|
|
|
|
@@ -753,6 +845,7 @@ def test_non_python_file_with_getattr_text_does_not_fail_guard(tmp_path: Path) -
|
|
|
|
|
""",
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "baseline")
|
|
|
|
|
base_rev = main_branch_rev(tmp_path)
|
|
|
|
|
checkout_feature_branch(tmp_path)
|
|
|
|
|
|
|
|
|
|
write_repo_file(
|
|
|
|
@@ -764,6 +857,6 @@ def test_non_python_file_with_getattr_text_does_not_fail_guard(tmp_path: Path) -
|
|
|
|
|
)
|
|
|
|
|
commit_all(tmp_path, "document getattr example")
|
|
|
|
|
|
|
|
|
|
result = run_script(tmp_path, "--mode", "ci", "--merge-target", "main")
|
|
|
|
|
result = run_script(tmp_path, "--base-rev", base_rev)
|
|
|
|
|
|
|
|
|
|
assert result.returncode == 0, result.stderr
|
|
|
|
|