P24-02: 操作と通知の全受入
This commit is contained in:
@@ -65,18 +65,18 @@ def compute_sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def get_actual_git_head() -> str:
|
||||
env_head = os.environ.get("CANDIDATE_COMMIT", os.environ.get("TARGET_HEAD"))
|
||||
if env_head:
|
||||
cleaned = env_head.strip().lower()
|
||||
if HEX_COMMIT_PATTERN.match(cleaned) and not (set(cleaned) <= {"0"} or set(cleaned) <= {"f"}):
|
||||
return cleaned
|
||||
def get_actual_git_head(root: Path = None) -> str:
|
||||
root = root if root is not None else Path(__file__).resolve().parents[1]
|
||||
try:
|
||||
import subprocess
|
||||
res = subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True, text=True, timeout=5)
|
||||
res = subprocess.run(["git", "rev-parse", "HEAD"], cwd=root, capture_output=True, text=True, timeout=5)
|
||||
if res.returncode == 0:
|
||||
h = res.stdout.strip().lower()
|
||||
if HEX_COMMIT_PATTERN.match(h) and not (set(h) <= {"0"} or set(h) <= {"f"}):
|
||||
for name in ("CANDIDATE_COMMIT", "TARGET_HEAD"):
|
||||
if name in os.environ and os.environ[name].strip().lower() != h:
|
||||
print(f"ERROR: {name} mismatch with real git HEAD {h}", file=sys.stderr)
|
||||
return ""
|
||||
return h
|
||||
except Exception:
|
||||
pass
|
||||
@@ -84,15 +84,11 @@ def get_actual_git_head() -> str:
|
||||
|
||||
|
||||
def get_required_suite_roster(root: Path) -> list[str]:
|
||||
tests_dir = root / "tests"
|
||||
if not tests_dir.is_dir():
|
||||
return []
|
||||
roster = ["tests/run_tests.gd"]
|
||||
for p in sorted(tests_dir.rglob("test*.gd")):
|
||||
rel = p.relative_to(root).as_posix()
|
||||
if "export" not in p.relative_to(root).parts and rel != "tests/s0/test_kernel_and_save.gd":
|
||||
roster.append(rel)
|
||||
return sorted(list(set(roster)))
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location("p24_required_roster", root / "tools/run_all_tests.py")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module.roster(root)
|
||||
|
||||
|
||||
def find_index_file(custom_path: str = None) -> Path:
|
||||
@@ -376,53 +372,57 @@ def _validate_condition_artifact_content(
|
||||
required_suites = get_required_suite_roster(repo_root)
|
||||
total_required = len(required_suites)
|
||||
|
||||
reported_suites = full_roster.get("total_suites", full_roster.get("suites_count", full_roster.get("suites")))
|
||||
if isinstance(reported_suites, int):
|
||||
if reported_suites < total_required:
|
||||
return False, f"Full roster reports only {reported_suites} suites, but repository requires at least {total_required} suites"
|
||||
for field in ("total_suites", "suites_count"):
|
||||
if field in full_roster and (type(full_roster[field]) is not int or full_roster[field] != total_required):
|
||||
return False, f"Full roster {field} must equal {total_required}"
|
||||
|
||||
roster_failures = full_roster.get("failures")
|
||||
if roster_failures is not None and (isinstance(roster_failures, bool) or not isinstance(roster_failures, int) or roster_failures != 0):
|
||||
if "failures" in full_roster and (type(roster_failures) is not int or roster_failures != 0):
|
||||
return False, f"Full roster regression reported failures: {roster_failures!r}"
|
||||
|
||||
# Check suite entries if provided in full_roster or referenced all-tests artifact
|
||||
suite_entries = full_roster.get("results", full_roster.get("rows", full_roster.get("all_tests")))
|
||||
if not suite_entries and "rows" in full_roster and full_roster["rows"] in validated_artifacts:
|
||||
rows_art = validated_artifacts[full_roster["rows"]]
|
||||
if isinstance(suite_entries, str) and suite_entries in validated_artifacts:
|
||||
rows_art = validated_artifacts[suite_entries]
|
||||
try:
|
||||
suite_entries = json.loads(rows_art["file"].read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
suite_entries = None
|
||||
|
||||
if not isinstance(suite_entries, list) or not suite_entries:
|
||||
return False, "Full roster requires a non-empty suite list"
|
||||
if isinstance(suite_entries, list) and suite_entries:
|
||||
seen_suites = set()
|
||||
for entry in suite_entries:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
return False, "Full roster row must be a dict"
|
||||
s_path = entry.get("test", entry.get("suite", entry.get("path")))
|
||||
if not isinstance(s_path, str) or not s_path:
|
||||
return False, "Full roster row missing suite path"
|
||||
if s_path not in required_suites:
|
||||
return False, f"Unknown full roster suite: {s_path}"
|
||||
if s_path in seen_suites:
|
||||
return False, f"Duplicate full roster suite: {s_path}"
|
||||
if s_path:
|
||||
seen_suites.add(s_path)
|
||||
p = repo_root / s_path
|
||||
if not p.is_file():
|
||||
return False, f"Full roster suite does not exist on disk: {s_path}"
|
||||
expected_sha = entry.get("source_sha256")
|
||||
if expected_sha and HEX64_PATTERN.match(expected_sha):
|
||||
if not isinstance(expected_sha, str) or not HEX64_PATTERN.fullmatch(expected_sha):
|
||||
return False, f"Full roster missing or invalid source_sha256: {s_path}"
|
||||
if expected_sha:
|
||||
actual_sha = compute_sha256(p)
|
||||
if actual_sha != expected_sha:
|
||||
return False, f"Full roster source SHA256 mismatch for {s_path}: expected {expected_sha}, got {actual_sha}"
|
||||
e_fails = entry.get("failures", entry.get("reported_failures", 0))
|
||||
if isinstance(e_fails, int) and e_fails != 0:
|
||||
e_fails = entry.get("failures")
|
||||
if type(e_fails) is not int or e_fails != 0:
|
||||
return False, f"Full roster suite {s_path} failed with {e_fails} failures"
|
||||
e_passed = entry.get("passed", entry.get("completion", {}).get("passed") if isinstance(entry.get("completion"), dict) else None)
|
||||
if e_passed is False:
|
||||
return False, f"Full roster suite {s_path} marked passed: false"
|
||||
if entry.get("passed") is not True:
|
||||
return False, f"Full roster suite {s_path} passed must be True"
|
||||
|
||||
missing_from_roster = set(required_suites) - seen_suites
|
||||
if missing_from_roster and len(seen_suites) >= total_required:
|
||||
pass
|
||||
elif missing_from_roster and len(suite_entries) >= total_required:
|
||||
pass
|
||||
elif missing_from_roster:
|
||||
if seen_suites != set(required_suites):
|
||||
return False, f"Full roster execution missing required suites: {sorted(list(missing_from_roster))[:5]}"
|
||||
|
||||
return True, ""
|
||||
@@ -474,7 +474,7 @@ def verify_evidence(index_path: Path) -> bool:
|
||||
|
||||
actual_head = get_actual_git_head()
|
||||
if not actual_head:
|
||||
print("ERROR: Unable to resolve verified actual candidate git HEAD from environment (CANDIDATE_COMMIT/TARGET_HEAD) or git repository", file=sys.stderr)
|
||||
print("ERROR: Unable to verify real repository git HEAD; git failed or CANDIDATE_COMMIT/TARGET_HEAD mismatched", file=sys.stderr)
|
||||
return False
|
||||
|
||||
if candidate_commit_norm != actual_head:
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"suite_policies": {
|
||||
"tests/ui/test_operations_notification.gd": {
|
||||
"source_sha256": "9c26386c529b8c8135639e8d0f0ed390da7139d64ff1e268975b05948cab9147",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": ["single"],
|
||||
"tests/s4/test_original_relationship_model.gd": {
|
||||
"source_sha256": "c74b243dbbb54dba9bbc165e29508cca910f0beb63e32d25ebbb8ea606dccce9",
|
||||
"ordinary_cleanup_prepared": false,
|
||||
"ordinary_control_labels": [],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/s4/test_original_relationship_runtime.gd": {
|
||||
"source_sha256": "c56bdb99dd43a4e7ceb3ca2861e8db39972c5c4e805126a77e2f7da32ca5bdc9",
|
||||
"ordinary_cleanup_prepared": false,
|
||||
"ordinary_control_labels": [],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
@@ -398,6 +405,22 @@
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_p08_normal_placement_ops.gd": {
|
||||
"source_sha256": "d3c246d34f8fd66f6ed237e42a86df89ebaf481bc9dee143b4de8d654ce0eb2e",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": [
|
||||
"single"
|
||||
],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
},
|
||||
"tests/ui/test_operations_notification.gd": {
|
||||
"source_sha256": "9c26386c529b8c8135639e8d0f0ed390da7139d64ff1e268975b05948cab9147",
|
||||
"ordinary_cleanup_prepared": true,
|
||||
"ordinary_control_labels": ["single"],
|
||||
"old_bounded_teardown": false,
|
||||
"new_audio_QA": false
|
||||
}
|
||||
},
|
||||
"outside_routes": {
|
||||
|
||||
Reference in New Issue
Block a user