332 lines
12 KiB
Python
332 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
from opensquilla.engine.final_diff_contract import (
|
|
build_final_diff_contract_observation,
|
|
classify_final_diff_path,
|
|
final_diff_contract_recovery_message,
|
|
)
|
|
|
|
|
|
def test_classify_final_diff_paths_for_source_test_and_scratch() -> None:
|
|
assert classify_final_diff_path("src/app.py") == "source"
|
|
assert classify_final_diff_path("tests/test_app.py") == "test-like"
|
|
assert classify_final_diff_path("all_test_cases.php") == "test-like"
|
|
assert classify_final_diff_path("debug_case.php") == "scratch"
|
|
assert classify_final_diff_path("debug_issue.mjs") == "scratch"
|
|
assert classify_final_diff_path("repro_case.cjs") == "scratch"
|
|
assert classify_final_diff_path(".phpunit.cache/test-results") == "scratch"
|
|
assert classify_final_diff_path("src/debug/helpers.py") == "source"
|
|
assert classify_final_diff_path("packages/parser/src/index.mjs") == "source"
|
|
|
|
|
|
def test_observation_flags_polluted_final_diff_with_source_patch() -> None:
|
|
observation = build_final_diff_contract_observation(
|
|
diff_paths=[
|
|
"src/app.py",
|
|
"debug_case.php",
|
|
"tests/test_app.py",
|
|
],
|
|
write_records=[{"relative_path": "src/app.py"}],
|
|
mutation_records=[
|
|
{
|
|
"paths": [
|
|
{"relative_path": "debug_case.php", "classification": "scratch"},
|
|
]
|
|
}
|
|
],
|
|
)
|
|
|
|
assert observation.source_paths == ["src/app.py"]
|
|
assert observation.scratch_paths == ["debug_case.php"]
|
|
assert observation.test_like_paths == ["tests/test_app.py"]
|
|
assert observation.mutation_overlap_paths == ["debug_case.php"]
|
|
assert observation.suspicious is True
|
|
assert "scratch_artifact_in_final_diff" in observation.triggers
|
|
|
|
|
|
def test_observation_flags_candidate_source_drift_to_test_only_patch() -> None:
|
|
observation = build_final_diff_contract_observation(
|
|
diff_paths=["tests/test_issue.py"],
|
|
write_records=[{"relative_path": "src/parser.py"}],
|
|
)
|
|
|
|
assert observation.source_paths == []
|
|
assert observation.candidate_source_paths == ["src/parser.py"]
|
|
assert observation.candidate_source_missing_paths == ["src/parser.py"]
|
|
assert observation.triggers[:2] == [
|
|
"final_diff_without_source",
|
|
"candidate_source_drift",
|
|
]
|
|
message = final_diff_contract_recovery_message(observation)
|
|
assert "current repository diff looks suspicious" in message
|
|
assert "src/parser.py" in message
|
|
assert "tests/test_issue.py" in message
|
|
|
|
|
|
def test_observation_flags_root_debug_module_with_source_patch() -> None:
|
|
observation = build_final_diff_contract_observation(
|
|
diff_paths=[
|
|
"debug_issue.mjs",
|
|
"packages/babel-parser/src/util/expression-scope.js",
|
|
"test_detailed.mjs",
|
|
"test_issue.mjs",
|
|
"test_trace.mjs",
|
|
],
|
|
write_records=[
|
|
{"relative_path": "packages/babel-parser/src/util/expression-scope.js"}
|
|
],
|
|
)
|
|
|
|
assert observation.source_paths == [
|
|
"packages/babel-parser/src/util/expression-scope.js"
|
|
]
|
|
assert observation.scratch_paths == ["debug_issue.mjs"]
|
|
assert observation.test_like_paths == [
|
|
"test_detailed.mjs",
|
|
"test_issue.mjs",
|
|
"test_trace.mjs",
|
|
]
|
|
assert observation.triggers == [
|
|
"scratch_artifact_in_final_diff",
|
|
"test_like_heavy_final_diff",
|
|
]
|
|
|
|
|
|
def test_observation_accepts_source_only_diff() -> None:
|
|
observation = build_final_diff_contract_observation(
|
|
diff_paths=["src/parser.py"],
|
|
write_records=[{"relative_path": "src/parser.py"}],
|
|
)
|
|
|
|
assert observation.suspicious is False
|
|
assert observation.primary_reason == "final_diff_contract_ok"
|
|
|
|
|
|
def test_observation_flags_changed_source_receipt_lost_before_final() -> None:
|
|
observation = build_final_diff_contract_observation(
|
|
diff_paths=[],
|
|
mutation_receipts=[
|
|
{
|
|
"relative_path": "src/parser.py",
|
|
"classification": "source",
|
|
"changed": True,
|
|
}
|
|
],
|
|
)
|
|
|
|
assert observation.suspicious is True
|
|
assert observation.changed_source_receipt_paths == ["src/parser.py"]
|
|
assert observation.lost_source_mutation_paths == ["src/parser.py"]
|
|
assert observation.primary_reason == "source_mutation_lost_before_final"
|
|
assert "source_mutation_lost_before_final" in observation.triggers
|
|
assert (
|
|
observation.to_event_details()["lost_source_mutation_paths"] == ["src/parser.py"]
|
|
)
|
|
|
|
|
|
def test_observation_includes_lost_candidate_recovery() -> None:
|
|
observation = build_final_diff_contract_observation(
|
|
diff_paths=[],
|
|
mutation_receipts=[
|
|
{
|
|
"relative_path": "src/parser.py",
|
|
"classification": "source",
|
|
"changed": True,
|
|
}
|
|
],
|
|
source_diff_candidates=[
|
|
{
|
|
"candidate_id": "srcdiff-1",
|
|
"paths": ["src/parser.py"],
|
|
"lost": True,
|
|
"restored": False,
|
|
"patch_sha256": "abc",
|
|
}
|
|
],
|
|
)
|
|
|
|
assert observation.primary_reason == "source_mutation_lost_before_final"
|
|
assert observation.recoverable_candidate_ids == ["srcdiff-1"]
|
|
assert observation.to_event_details()["recoverable_candidate_ids"] == ["srcdiff-1"]
|
|
message = final_diff_contract_recovery_message(observation)
|
|
assert "Recoverable source diff candidate(s): srcdiff-1" in message
|
|
assert "restore_source_diff_candidate" not in message
|
|
|
|
|
|
def test_observation_omits_candidate_recovery_when_source_diff_remains() -> None:
|
|
observation = build_final_diff_contract_observation(
|
|
diff_paths=["src/parser.py"],
|
|
mutation_receipts=[
|
|
{
|
|
"relative_path": "src/parser.py",
|
|
"classification": "source",
|
|
"changed": True,
|
|
}
|
|
],
|
|
source_diff_candidates=[
|
|
{
|
|
"candidate_id": "srcdiff-1",
|
|
"paths": ["src/parser.py"],
|
|
"lost": True,
|
|
"restored": False,
|
|
}
|
|
],
|
|
)
|
|
|
|
assert observation.lost_source_mutation_paths == []
|
|
assert observation.recoverable_candidate_ids == []
|
|
|
|
|
|
def test_observation_ignores_unrelated_lost_candidate() -> None:
|
|
observation = build_final_diff_contract_observation(
|
|
diff_paths=[],
|
|
mutation_receipts=[
|
|
{
|
|
"relative_path": "src/parser.py",
|
|
"classification": "source",
|
|
"changed": True,
|
|
}
|
|
],
|
|
source_diff_candidates=[
|
|
{
|
|
"candidate_id": "srcdiff-2",
|
|
"paths": ["src/other.py"],
|
|
"lost": True,
|
|
"restored": False,
|
|
}
|
|
],
|
|
)
|
|
|
|
assert observation.lost_source_mutation_paths == ["src/parser.py"]
|
|
assert observation.recoverable_candidate_ids == []
|
|
|
|
|
|
def test_observation_does_not_flag_source_receipt_when_source_diff_remains() -> None:
|
|
observation = build_final_diff_contract_observation(
|
|
diff_paths=["src/parser.py"],
|
|
mutation_receipts=[
|
|
{
|
|
"relative_path": "src/parser.py",
|
|
"classification": "source",
|
|
"changed": True,
|
|
}
|
|
],
|
|
)
|
|
|
|
assert "source_mutation_lost_before_final" not in observation.triggers
|
|
assert observation.lost_source_mutation_paths == []
|
|
|
|
|
|
def test_observation_ignores_test_like_receipt_for_source_loss() -> None:
|
|
observation = build_final_diff_contract_observation(
|
|
diff_paths=[],
|
|
mutation_receipts=[
|
|
{
|
|
"relative_path": "tests/test_parser.py",
|
|
"classification": "test-like",
|
|
"changed": True,
|
|
}
|
|
],
|
|
)
|
|
|
|
assert "source_mutation_lost_before_final" not in observation.triggers
|
|
assert observation.changed_source_receipt_paths == []
|
|
|
|
|
|
def test_observation_records_read_source_paths_missing_from_final_diff() -> None:
|
|
observation = build_final_diff_contract_observation(
|
|
diff_paths=["src/app.py"],
|
|
read_records=[
|
|
{"relative_path": "src/app.py"},
|
|
{"relative_path": "src/config.py"},
|
|
{"relative_path": "tests/test_app.py"},
|
|
],
|
|
write_records=[{"relative_path": "src/app.py"}],
|
|
)
|
|
|
|
assert observation.suspicious is False
|
|
assert observation.read_source_paths == ["src/app.py", "src/config.py"]
|
|
assert observation.read_source_missing_paths == ["src/config.py"]
|
|
assert observation.to_event_details()["read_source_missing_paths"] == [
|
|
"src/config.py"
|
|
]
|
|
|
|
|
|
def test_observation_flags_diagnostic_source_like_paths_with_source_patch() -> None:
|
|
observation = build_final_diff_contract_observation(
|
|
diff_paths=[
|
|
"src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php",
|
|
".php_cs.php",
|
|
"check_whitespace.php",
|
|
"long_test_path/a/b/c/d/e/file.txt",
|
|
"this/is/a/very/deep/path/structure/with/file.txt",
|
|
],
|
|
write_records=[
|
|
{"relative_path": "src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php"}
|
|
],
|
|
)
|
|
|
|
assert observation.source_paths == [
|
|
"src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php",
|
|
"php_cs.php",
|
|
"check_whitespace.php",
|
|
"long_test_path/a/b/c/d/e/file.txt",
|
|
"this/is/a/very/deep/path/structure/with/file.txt",
|
|
]
|
|
assert observation.diagnostic_source_like_paths == [
|
|
"php_cs.php",
|
|
"check_whitespace.php",
|
|
"long_test_path/a/b/c/d/e/file.txt",
|
|
"this/is/a/very/deep/path/structure/with/file.txt",
|
|
]
|
|
assert observation.actionable_source_paths == [
|
|
"src/Fixer/PhpTag/BlankLineAfterOpeningTagFixer.php"
|
|
]
|
|
assert observation.triggers == ["diagnostic_source_like_in_final_diff"]
|
|
assert observation.primary_reason == "diagnostic_source_like_in_final_diff"
|
|
assert observation.to_event_details()["diagnostic_source_like_count"] == 4
|
|
assert observation.to_event_details()["actionable_source_count"] == 1
|
|
assert observation.to_event_details()["diagnostic_source_like_only"] is False
|
|
message = final_diff_contract_recovery_message(observation)
|
|
assert "diagnostic/source-like paths" in message
|
|
assert "long_test_path/a/b/c/d/e/file.txt" in message
|
|
|
|
|
|
def test_observation_flags_diagnostic_source_like_only_diff() -> None:
|
|
observation = build_final_diff_contract_observation(
|
|
diff_paths=[
|
|
"analyze_output.py",
|
|
"this/is/a/very/deep/path/structure/that/goes/on/file.txt",
|
|
],
|
|
read_records=[
|
|
{"relative_path": "src/printer.rs"},
|
|
{"relative_path": "src/input.rs"},
|
|
],
|
|
)
|
|
|
|
assert observation.source_paths == [
|
|
"analyze_output.py",
|
|
"this/is/a/very/deep/path/structure/that/goes/on/file.txt",
|
|
]
|
|
assert observation.diagnostic_source_like_paths == [
|
|
"analyze_output.py",
|
|
"this/is/a/very/deep/path/structure/that/goes/on/file.txt",
|
|
]
|
|
assert observation.actionable_source_paths == []
|
|
assert observation.triggers == ["diagnostic_source_like_in_final_diff"]
|
|
assert observation.primary_reason == "diagnostic_source_like_in_final_diff"
|
|
details = observation.to_event_details()
|
|
assert details["diagnostic_source_like_count"] == 2
|
|
assert details["actionable_source_count"] == 0
|
|
assert details["diagnostic_source_like_only"] is True
|
|
assert details["candidate_actionable_source_missing_paths"] == [
|
|
"src/printer.rs",
|
|
"src/input.rs",
|
|
]
|
|
assert details["read_actionable_source_missing_paths"] == [
|
|
"src/printer.rs",
|
|
"src/input.rs",
|
|
]
|
|
assert details["candidate_actionable_source_missing_count"] == 2
|
|
assert details["read_actionable_source_missing_count"] == 2
|