@@ -255,20 +255,106 @@ runs:
255255 lowered = str(name or "").strip().lower()
256256 return lowered.startswith("test 6") or "regression" in lowered
257257
258+ ANSI_RE = re.compile(r"\x1b\[[0-9;]*[A-Za-z]")
259+
260+ def strip_ansi(value):
261+ return ANSI_RE.sub("", str(value or ""))
262+
263+ def fetch_job_log(job):
264+ if not isinstance(job, dict):
265+ return ""
266+ job_id = str(job.get("id") or "").strip()
267+ if not job_id or not github_token:
268+ return ""
269+ try:
270+ return subprocess.check_output(
271+ ["gh", "run", "view", current_run_id, "--repo", repository, "--log", "--job", job_id],
272+ text=True,
273+ stderr=subprocess.DEVNULL,
274+ env={**os.environ, "GH_TOKEN": github_token},
275+ )
276+ except Exception:
277+ return ""
278+
279+ def extract_summary_statuses_from_log(log_text):
280+ statuses = {}
281+ for raw_line in str(log_text or "").splitlines():
282+ line = strip_ansi(raw_line)
283+ match = re.search(
284+ r'echo\s+"([1-6])\.\s+[^"]*?:\s*(passed|failed|skipped)"',
285+ line,
286+ re.IGNORECASE,
287+ )
288+ if not match:
289+ match = re.search(
290+ r"\b([1-6])\.\s+[^:\n]*?:\s*(passed|failed|skipped)\b",
291+ line,
292+ re.IGNORECASE,
293+ )
294+ if match:
295+ statuses[int(match.group(1))] = match.group(2).lower()
296+ return statuses
297+
298+ def detail_test_number(detail, fallback_index):
299+ name = str(detail.get("name") or "")
300+ lowered = name.lower()
301+ match = re.search(r"\btest\s*([1-6])\b", lowered)
302+ if match:
303+ return int(match.group(1))
304+ if lowered.startswith("regression applicability") or "regression" in lowered:
305+ return 6
306+ return fallback_index
307+
308+ def apply_summary_log_statuses(details, summary_statuses):
309+ if not summary_statuses:
310+ return details
311+ updated = []
312+ test_index = 0
313+ for detail in details:
314+ if is_test_detail_step(detail.get("name")):
315+ test_index += 1
316+ test_number = detail_test_number(detail, test_index)
317+ if test_number in summary_statuses:
318+ detail = {**detail, "status": summary_statuses[test_number]}
319+ updated.append(detail)
320+ return updated
321+
322+ def job_has_failed_step(job, step_name):
323+ target = str(step_name or "").strip().lower()
324+ if not isinstance(job, dict) or not target:
325+ return False
326+ for step in job.get("steps") or []:
327+ name = str(step.get("name") or "").strip().lower()
328+ conclusion = str(step.get("conclusion") or "").strip().lower()
329+ if name == target and conclusion == "failure":
330+ return True
331+ return False
332+
333+ def mark_core_details_failed(details):
334+ updated = []
335+ for detail in details:
336+ if is_test_detail_step(detail.get("name")) and not is_regression_detail_name(detail.get("name")):
337+ detail = {**detail, "status": "failed"}
338+ updated.append(detail)
339+ return updated
340+
258341 def extract_test_details(job, regression_details):
259342 details = []
260343 if isinstance(job, dict):
344+ job_url = str(job.get("html_url") or "").strip()
261345 for step in job.get("steps") or []:
262346 name = str(step.get("name") or "").strip()
263347 if not is_test_detail_step(name):
264348 continue
265- details.append(
266- {
267- "name": name,
268- "status": normalize_detail_status(step.get("conclusion")),
269- "duration_seconds": step_duration_seconds(step),
270- }
271- )
349+ detail = {
350+ "name": name,
351+ "status": normalize_detail_status(step.get("conclusion")),
352+ "duration_seconds": step_duration_seconds(step),
353+ }
354+ step_number = as_int(step.get("number"), 0)
355+ if job_url and step_number > 0:
356+ detail["url"] = f"{job_url}#step:{step_number}:1"
357+ details.append(detail)
272358
273359 if regression_details:
274360 regression_entry = dict(regression_details[0])
@@ -310,6 +396,8 @@ runs:
310396 core_failed += 1
311397 else:
312398 skipped += 1
399+ if not is_regression_detail_name(name):
400+ core_failed += 1
313401
314402 return {
315403 "passed": passed,
@@ -372,12 +460,34 @@ runs:
372460 return "skipped"
373461 return "skipped"
374462
375- def normalize_run_status(raw_status, tests_passed, tests_failed, tests_skipped, core_failed):
463+ def is_safe_regression_skip(decision):
464+ return str(decision or "").strip() in {
465+ "not_applicable_package_manager",
466+ "package_manager_regression_safe_skip",
467+ "no_newer_stable_available",
468+ "current_is_latest_stable",
469+ "pinned_is_latest",
470+ "already_current",
471+ "already_at_latest",
472+ "at_latest",
473+ "current_is_latest",
474+ "metadata_review_required",
475+ "runtime_validation_not_automated",
476+ "bounded_runtime_deferred",
477+ "arm64_desktop_artifact_unavailable",
478+ "hold_current_arm_dependency_gap",
479+ }
480+
481+ def normalize_run_status(raw_status, tests_passed, tests_failed, tests_skipped, core_failed, regression_decision):
376482 status = str(raw_status or "").strip().lower()
377483 if tests_failed > 0:
378484 return "failure"
379485 if core_failed > 0:
380486 return "failure"
487+ if tests_skipped > 0 and not (
488+ tests_skipped == 1 and is_safe_regression_skip(regression_decision)
489+ ):
490+ return "failure"
381491 if tests_passed > 0:
382492 return "success"
383493 if tests_skipped > 0:
@@ -389,12 +499,16 @@ runs:
389499 def default_regression_result(status, decision):
390500 if decision == "not_applicable_package_manager":
391501 return "Regression validation not applicable: tested package installed via package manager in Tests 1-5"
502+ if decision == "package_manager_regression_safe_skip":
503+ return "Regression validation not applicable: package-manager installed current version"
392504 if decision == "next_install_validated":
393505 return "Next version installed successfully on Arm64"
394506 if decision == "next_bundle_validated":
395507 return "Next bundle validated successfully on Arm64"
396508 if decision == "next_image_validated":
397509 return "Next image validated successfully on Arm64"
510+ if decision == "limited_cpu_smoke_validated":
511+ return "Limited CPU-side proof passed on Arm64"
398512 if decision == "next_install_failed":
399513 return "Next version install failed on Arm64"
400514 if decision in {
@@ -412,6 +526,14 @@ runs:
412526 return "Tests 1-5 baseline must be older than the latest stable release"
413527 if decision == "not_configured":
414528 return "Regression validation not configured for this workflow"
529+ if decision == "runtime_validation_not_automated":
530+ return "Package-specific runtime validation is not automated"
531+ if decision == "bounded_runtime_deferred":
532+ return "Next-version bounded runtime validation deferred"
533+ if decision == "arm64_desktop_artifact_unavailable":
534+ return "Arm64 desktop artifact runtime validation is not available on the generic runner"
535+ if decision == "hold_current_arm_dependency_gap":
536+ return "Regression validation deferred because the Arm dependency gap blocks a meaningful next-version check"
415537 if status == "passed":
416538 return "Regression validation passed"
417539 if status == "failed":
@@ -469,14 +591,18 @@ runs:
469591 "at_latest",
470592 "current_is_latest",
471593 "no_newer_stable_available",
594+ "runtime_validation_not_automated",
595+ "bounded_runtime_deferred",
596+ "arm64_desktop_artifact_unavailable",
597+ "hold_current_arm_dependency_gap",
472598 }:
473599 latest_version = current_version
474- if not latest_version and decision == "not_applicable_package_manager":
600+ if not latest_version and decision in { "not_applicable_package_manager", "package_manager_regression_safe_skip"} :
475601 latest_version = "not_applicable"
476602 if not latest_version:
477603 latest_version = "unknown"
478604 next_installed_version = meaningful_version(outputs.get("regression_next_installed_version")) or "not_installed"
479- if decision == "not_applicable_package_manager" and next_installed_version == "not_installed":
605+ if decision in { "not_applicable_package_manager", "package_manager_regression_safe_skip"} and next_installed_version == "not_installed":
480606 next_installed_version = "not_applicable"
481607 regression_result = str(outputs.get("regression_result") or "").strip()
482608 if not regression_result:
@@ -560,6 +686,34 @@ runs:
560686 if regression_policy_info["applicability"] == "applicable":
561687 regression_details.append(regression_detail(outputs, regression_policy_info))
562688 test_details = extract_test_details(resolved_job, regression_details)
689+ initial_detail_counts = summarize_detail_counts(test_details)
690+ initial_detail_total = (
691+ initial_detail_counts["passed"]
692+ + initial_detail_counts["failed"]
693+ + initial_detail_counts["skipped"]
694+ )
695+ emitted_total = emitted_tests_passed + emitted_tests_failed + emitted_tests_skipped
696+ if (
697+ initial_detail_total > 0
698+ and emitted_total > 0
699+ and (
700+ emitted_tests_failed > initial_detail_counts["failed"]
701+ or emitted_core_failed > initial_detail_counts["core_failed"]
702+ or emitted_tests_passed != initial_detail_counts["passed"]
703+ or emitted_tests_skipped != initial_detail_counts["skipped"]
704+ )
705+ ):
706+ summary_statuses = extract_summary_statuses_from_log(fetch_job_log(resolved_job))
707+ test_details = apply_summary_log_statuses(test_details, summary_statuses)
708+
709+ current_detail_counts = summarize_detail_counts(test_details)
710+ if (
711+ initial_detail_total > 0
712+ and str((resolved_job or {}).get("conclusion") or "").strip().lower() == "failure"
713+ and job_has_failed_step(resolved_job, "Calculate test summary")
714+ and current_detail_counts["core_failed"] == 0
715+ ):
716+ test_details = mark_core_details_failed(test_details)
563717 regression_detail_entry = next(
564718 (detail for detail in test_details if is_regression_detail_name(detail.get("name"))),
565719 None,
@@ -601,6 +755,10 @@ runs:
601755 use_detail_counts = True
602756 elif tests_passed != detail_counts["passed"]:
603757 use_detail_counts = True
758+ elif tests_skipped != detail_counts["skipped"]:
759+ use_detail_counts = True
760+ elif emitted_total != detail_total:
761+ use_detail_counts = True
604762 elif core_failed != detail_counts["core_failed"]:
605763 use_detail_counts = True
606764
@@ -620,15 +778,28 @@ runs:
620778 str(detail.get("status") or "").strip().lower() == "failed"
621779 for detail in test_details
622780 )
781+ safe_single_regression_skip = (
782+ tests_failed == 0
783+ and core_failed == 0
784+ and tests_skipped == 1
785+ and is_safe_regression_skip(regression_decision_for_counts)
786+ )
787+ safe_package_manager_skip = (
788+ tests_failed == 0
789+ and core_failed == 0
790+ and tests_skipped == 1
791+ and regression_policy_info["reason"] == "package_manager_installed"
792+ )
623793
624- if job_failed and not failed_detail_exists:
794+ if job_failed and not failed_detail_exists and not (safe_single_regression_skip or safe_package_manager_skip) :
625795 promoted_regression_failure = False
626796 if isinstance(regression_detail_entry, dict):
627797 regression_decision = str(regression_detail_entry.get("decision") or "").strip()
628798 successful_regression_decisions = {
629799 "next_install_validated",
630800 "next_bundle_validated",
631801 "next_image_validated",
802+ "limited_cpu_smoke_validated",
632803 }
633804 if regression_decision not in {
634805 "baseline_failed",
@@ -675,7 +846,17 @@ runs:
675846 tests_skipped = detail_counts["skipped"]
676847 core_failed = detail_counts["core_failed"]
677848
678- run_status = normalize_run_status(raw_run_status, tests_passed, tests_failed, tests_skipped, core_failed)
849+ regression_decision_for_status = regression_decision_for_counts or str(outputs.get("regression_decision") or "").strip()
850+ if not regression_decision_for_status and regression_policy_info["reason"] == "package_manager_installed":
851+ regression_decision_for_status = "not_applicable_package_manager"
852+ run_status = normalize_run_status(
853+ raw_run_status,
854+ tests_passed,
855+ tests_failed,
856+ tests_skipped,
857+ core_failed,
858+ regression_decision_for_status,
859+ )
679860 badge_status = "passing" if run_status == "success" else "failing"
680861
681862 result_payload = {
0 commit comments