diff --git a/atvm/watcher-service/atvm_run_watcher.py b/atvm/watcher-service/atvm_run_watcher.py index 0be05dc..a34cda4 100644 --- a/atvm/watcher-service/atvm_run_watcher.py +++ b/atvm/watcher-service/atvm_run_watcher.py @@ -339,6 +339,13 @@ def extract_completed_subrun_summaries(log_text: str, inventory: Dict[str, str]) return summaries +def extract_latest_run_summary(log_text: str, inventory: Dict[str, str]) -> Optional[Dict[str, object]]: + summaries = extract_completed_subrun_summaries(log_text, inventory) + if not summaries: + return None + return summaries[-1] + + def collect_host_results( reporter_root: Path, expected_hosts: List[str], @@ -1022,6 +1029,27 @@ def determine_state( } ) + # Non-categorized runs often only write a final check-xml reporter XML. + # Fall back to the parent "Cloud Run Finished" summary when host XML is absent. + if not parent_host_results: + latest_summary = extract_latest_run_summary(log_text, inventory) + if latest_summary: + summary_results = latest_summary["host_results"] + for host, result in summary_results.items(): + parent_host_results[host] = result + if subrun_states: + subrun = subrun_states[-1] + subrun["host_results"] = summary_results + if not subrun.get("currents_url") and latest_summary.get("currents_url"): + subrun["currents_url"] = latest_summary["currents_url"] + notes_list = list(subrun.get("notes", [])) + fallback_note = "Host result details were derived from the parent run log summary." + if fallback_note not in notes_list: + notes_list.append(fallback_note) + subrun["notes"] = notes_list + if subrun["state"] in {"UNKNOWN", "TERMINATED"}: + subrun["state"] = "FAILED" if any(result.failures for result in summary_results.values()) else "COMPLETED" + parent_start_candidates = [subrun["start_ts"] for subrun in subrun_states if subrun["start_ts"]] parent_end_candidates = [subrun["end_ts"] for subrun in subrun_states if subrun["end_ts"]] start_ts = min(parent_start_candidates) if parent_start_candidates else started_at