diff --git a/common/log_parser/acs-results-schema.json b/common/log_parser/acs-results-schema.json index 5f99b359..3d863057 100644 --- a/common/log_parser/acs-results-schema.json +++ b/common/log_parser/acs-results-schema.json @@ -1194,6 +1194,9 @@ "Test_case_description": { "type": "string" }, + "test_result": { + "$ref": "#/definitions/sub_test_result_string" + }, "subtests": { "type": "array", "items": { @@ -1687,6 +1690,9 @@ "Suite_Name: Recommended : PSCI_compliance": { "type": "string" }, + "Suite_Name: Recommended : PCIE_OPTION_ROM_ARCH_AUDIT_compliance": { + "type": "string" + }, "Suite_Name: Recommended : RUNTIME_DEV_MAP_compliance": { "type": "string" }, diff --git a/common/log_parser/apply_waivers.py b/common/log_parser/apply_waivers.py index d79599da..6b73c587 100755 --- a/common/log_parser/apply_waivers.py +++ b/common/log_parser/apply_waivers.py @@ -158,6 +158,30 @@ def propagate_subtest(subtest): 'All failed nested subtests were waived.' ) + +def _propagate_standalone_waiver(test_suite_entry): + """Mark an explicit standalone result waived when all failures are waived.""" + test_result = test_suite_entry.get('test_result') + if not _is_failed_result(test_result) or _has_waiver_result(test_result): + return + + has_unwaived_failure = False + has_waived_failure = False + for subtest in _iter_nested_subtests(test_suite_entry.get('subtests', [])): + subtest_result = subtest.get('sub_test_result') + if isinstance(subtest_result, dict): + has_unwaived_failure |= subtest_result.get('FAILED', 0) > 0 + has_waived_failure |= subtest_result.get('FAILED_WITH_WAIVER', 0) > 0 + elif _is_failed_result(subtest_result): + if _has_waiver_result(subtest_result): + has_waived_failure = True + else: + has_unwaived_failure = True + + if has_waived_failure and not has_unwaived_failure: + test_suite_entry['test_result'] = _append_waiver_to_result(test_result) + + def load_waivers(waiver_data, suite_name): """Collect waiver entries by scope for the requested suite.""" suite_level_waivers = [] @@ -608,12 +632,11 @@ def apply_subtest_level_waivers(test_suite_entry, subtest_waivers, suite_name): failed = sub_test_result.get('FAILED', 0) failed_with_waiver = sub_test_result.get('FAILED_WITH_WAIVER', 0) - if failed > 0: - sub_test_result['FAILED'] = failed - 1 - sub_test_result['FAILED_WITH_WAIVER'] = failed_with_waiver + 1 - else: - # Edge case: FAILED is already 0 - sub_test_result['FAILED_WITH_WAIVER'] = failed_with_waiver + 1 + if failed <= 0: + continue + + sub_test_result['FAILED'] = failed - 1 + sub_test_result['FAILED_WITH_WAIVER'] = failed_with_waiver + 1 # Add waiver_reason inside sub_test_result reason = waiver.get('Reason', '') @@ -892,6 +915,8 @@ def apply_waivers(suite_name, json_file, waiver_file='waiver.json', output_json_ if suite_name.upper() in ('BSA', 'SBSA'): for testcase in test_suite_entry.get('testcases', []): _propagate_nested_bsa_waivers(testcase) + elif suite_name.upper() == 'STANDALONE': + _propagate_standalone_waiver(test_suite_entry) # Update test suite summary # Determine the summary field based on suite name diff --git a/common/log_parser/enrich_suite_json.py b/common/log_parser/enrich_suite_json.py index 8d488307..3d7b8eaf 100644 --- a/common/log_parser/enrich_suite_json.py +++ b/common/log_parser/enrich_suite_json.py @@ -65,6 +65,7 @@ "ETHTOOL-TEST", "NETWORK-BOOT", "OS-TESTS", + "PCIE-OPTION-ROM-ARCH-AUDIT", "PSCI", "READ-WRITE-CHECK-BLK-DEVICES", "RUNTIME-DEV-MAP", diff --git a/common/log_parser/generate_acs_summary.py b/common/log_parser/generate_acs_summary.py index 56550a7e..ae205ee2 100644 --- a/common/log_parser/generate_acs_summary.py +++ b/common/log_parser/generate_acs_summary.py @@ -559,7 +559,7 @@ def _summary_cards(merged_data, sources, output_dir): standalone_keys = { _compliance_identity(item.get("requirement_key", item["canonical"])) for item in registry - if item["canonical"] in get_suite("STANDALONE", registry)["included_suites"] + if item.get("summary_html") == "standalone_tests_summary.html" } cards = [] for section_id, label, content, detail, candidates in sources: diff --git a/common/log_parser/main_log_parser.sh b/common/log_parser/main_log_parser.sh index 64f6b07e..b6799adf 100755 --- a/common/log_parser/main_log_parser.sh +++ b/common/log_parser/main_log_parser.sh @@ -514,9 +514,9 @@ fi ################################################################################ # STANDALONE TESTS PARSING (including Capsule) ################################################################################ +Standalone_JSONS=() if [ $YOCTO_FLAG_PRESENT -eq 1 ]; then LINUX_TOOLS_LOGS_PATH="$LOGS_PATH/linux_tools" - Standalone_JSONS=() # 1) DT_KSELFTEST DT_KSELFTEST_LOG="$LINUX_TOOLS_LOGS_PATH/dt_kselftest.log" @@ -677,21 +677,37 @@ if [ $YOCTO_FLAG_PRESENT -eq 1 ]; then echo -e "${RED}ERROR: DTB alignment log parsing to json failed.${NC}" fi fi +fi - # Now generate a single STANDALONE HTML - if [ ${#Standalone_JSONS[@]} -gt 0 ]; then - Standalone_PROCESSED=1 - Standalone_DETAILED_HTML="$HTMLS_DIR/standalone_tests_detailed.html" - Standalone_SUMMARY_HTML="$HTMLS_DIR/standalone_tests_summary.html" - - python3 "$SCRIPTS_PATH/standalone_tests/json_to_html.py" \ - "${Standalone_JSONS[@]}" \ - "$Standalone_DETAILED_HTML" \ - "$Standalone_SUMMARY_HTML" \ - --include-drop-down +# 12) SR-only PCIe Option ROM architecture audit +if [ $YOCTO_FLAG_PRESENT -eq 0 ]; then + PCIE_OPTION_ROM_AUDIT_LOG="$LOGS_PATH/uefi_dump/PcieOptionRomArchAudit.log" + PCIE_OPTION_ROM_AUDIT_JSON="$JSONS_DIR/pcie_option_rom_arch_audit.json" + if check_file "$PCIE_OPTION_ROM_AUDIT_LOG"; then + if python3 "$SCRIPTS_PATH/standalone_tests/logs_to_json.py" \ + "$PCIE_OPTION_ROM_AUDIT_LOG" \ + "$PCIE_OPTION_ROM_AUDIT_JSON"; then + apply_waivers "Standalone" "$PCIE_OPTION_ROM_AUDIT_JSON" + Standalone_JSONS+=("$PCIE_OPTION_ROM_AUDIT_JSON") + else + echo -e "${RED}ERROR: PCIe Option ROM audit log parsing to json failed.${NC}" + fi fi fi +# Now generate a single STANDALONE HTML +if [ ${#Standalone_JSONS[@]} -gt 0 ]; then + Standalone_PROCESSED=1 + Standalone_DETAILED_HTML="$HTMLS_DIR/standalone_tests_detailed.html" + Standalone_SUMMARY_HTML="$HTMLS_DIR/standalone_tests_summary.html" + + python3 "$SCRIPTS_PATH/standalone_tests/json_to_html.py" \ + "${Standalone_JSONS[@]}" \ + "$Standalone_DETAILED_HTML" \ + "$Standalone_SUMMARY_HTML" \ + --include-drop-down +fi + ################################################################################ # OS TESTS PARSING ################################################################################ diff --git a/common/log_parser/merge_jsons.py b/common/log_parser/merge_jsons.py index e35884d6..983691b0 100755 --- a/common/log_parser/merge_jsons.py +++ b/common/log_parser/merge_jsons.py @@ -146,7 +146,7 @@ def is_recommended_test_case(suite_entry): srs_scope = str(suite_entry.get("SRS scope", "")).strip().lower() return srs_scope == "recommended" -def count_fails_in_json(data, skip_recommended=False): +def count_fails_in_json(data, skip_recommended=False, suite_key=None): """ Inspect JSON data and count how many tests are 'FAILED' vs 'FAILED_WITH_WAIVER'. Returns (failed, failed_with_waiver). @@ -170,6 +170,14 @@ def count_fails_in_json(data, skip_recommended=False): return (0, 0) for suite_entry in test_results: + if suite_key == "PCIE_OPTION_ROM_ARCH_AUDIT": + overall_result = str(suite_entry.get("test_result", "")).upper() + if "FAIL" in overall_result: + if "WITH WAIVER" in overall_result: + total_failed_with_waiver += 1 + else: + total_failed += 1 + continue if skip_recommended and is_recommended_test_case(suite_entry): continue # If testcases exist, count only testcase-level results to avoid double counting. @@ -443,6 +451,9 @@ def merge_json_files(json_files, output_file): elif "PSCI" in fn: section_name = "Suite_Name: PSCI" suite_key = "PSCI" + elif "PCIE_OPTION_ROM_ARCH_AUDIT" in fn: + section_name = "Suite_Name: PcieOptionRomArchAudit" + suite_key = "PCIE_OPTION_ROM_ARCH_AUDIT" elif "PFDI" in fn: section_name = "Suite_Name: PFDI" suite_key = "PFDI" @@ -480,7 +491,7 @@ def merge_json_files(json_files, output_file): standalone_aliases = { "dt_kselftest", "dt_validate", "ethtool_test", "read_write_check_blk_devices", "psci", "capsule update", "network_boot", "smbios", "runtime_dev_map" , - "reserved_memory_map", "dtb_alignment" + "reserved_memory_map", "dtb_alignment", "pcie_option_rom_arch_audit" } if lookup_suite_key in standalone_aliases or lookup_suite_key.startswith("os_"): lookup_suite_key = "standalone" @@ -545,7 +556,8 @@ def merge_json_files(json_files, output_file): merged_results[section_name] = data f, fw = count_fails_in_json( - data, skip_recommended=(DT_OR_SR_MODE == "SR" and suite_key == "OS_TEST") + data, skip_recommended=(DT_OR_SR_MODE == "SR" and suite_key == "OS_TEST"), + suite_key=suite_key, ) if suite_key in suite_fail_data: suite_fail_data[suite_key]["Failed"] += f @@ -826,6 +838,7 @@ def _is_missing(val: str) -> bool: "Suite_Name: Runtime device mapping": "Suite_Name: Standalone", "Suite_Name: Reserved Memory Map": "Suite_Name: Standalone", "Suite_Name: DTB Alignment": "Suite_Name: Standalone", + "Suite_Name: PcieOptionRomArchAudit": "Suite_Name: Standalone", } def _entry_to_list(entry): diff --git a/common/log_parser/standalone_tests/json_to_html.py b/common/log_parser/standalone_tests/json_to_html.py index 1e6df040..d02f8019 100755 --- a/common/log_parser/standalone_tests/json_to_html.py +++ b/common/log_parser/standalone_tests/json_to_html.py @@ -69,14 +69,15 @@ def detect_columns_used(subtests): # Function to generate bar chart for test results def generate_bar_chart(suite_summary): - labels = ['Passed', 'Failed', 'Warnings', 'Failed with Waiver'] # We track "Failed with Waiver" separately + labels = ['Passed', 'Failed', 'Skipped', 'Warnings', 'Failed with Waiver'] sizes = [ suite_summary.get('total_passed', 0), suite_summary.get('total_failed', 0), + suite_summary.get('total_skipped', 0), suite_summary.get('total_warnings', 0), suite_summary.get('total_failed_with_waiver', 0) ] - colors = ['#d4edda', '#f8d7da', '#fff3cd', '#f39c12'] + colors = ['#d4edda', '#f8d7da', '#ffe0b2', '#fff3cd', '#f39c12'] plt.figure(figsize=(8, 6)) bars = plt.bar(labels, sizes, color=colors, edgecolor='black') @@ -272,6 +273,10 @@ def generate_html(suite_summary, test_results_list, output_html_path,
|
- {{ all_reasons|join(" ")|safe if all_reasons else "N/A" }} + {% if all_reasons %} + {% for reason in all_reasons %} + {{ reason|e }}{% if not loop.last %} {% endif %} + {% endfor %} + {% else %} + N/A + {% endif %} |
{{ r.waiver_reason if r.waiver_reason else "N/A" }}
@@ -384,6 +402,7 @@ def generate_html(suite_summary, test_results_list, output_html_path,
# Compute total tests for summary
total_tests = (suite_summary.get('total_passed', 0) +
suite_summary.get('total_failed', 0) +
+ suite_summary.get('total_skipped', 0) +
suite_summary.get('total_warnings', 0) +
suite_summary.get('total_failed_with_waiver', 0))
@@ -398,13 +417,15 @@ def generate_html(suite_summary, test_results_list, output_html_path,
total_tests=total_tests,
total_passed=suite_summary.get("total_passed", 0),
total_failed=suite_summary.get("total_failed", 0),
+ total_skipped=suite_summary.get("total_skipped", 0),
total_warnings=suite_summary.get("total_warnings", 0),
total_failed_with_waiver=suite_summary.get("total_failed_with_waiver", 0),
test_results_list=test_results_list,
is_summary_page=is_summary_page,
include_drop_down=include_drop_down,
chart_data=chart_data,
- enumerate=enumerate
+ enumerate=enumerate,
+ get_test_status=get_test_status
)
html_content = enhance_html_report(html_content, suite_type="standalone")
@@ -429,6 +450,40 @@ def get_subtest_status(subtest_result):
return 'UNKNOWN'
+def get_test_status(test):
+ """Return a testcase status, preferring an explicit overall result."""
+ explicit = str(test.get('test_result', test.get('Test_result', ''))).strip().upper()
+ explicit_aliases = {
+ 'PASS': 'PASSED',
+ 'PASSED': 'PASSED',
+ 'FAIL': 'FAILED',
+ 'FAILED': 'FAILED',
+ 'SKIP': 'SKIPPED',
+ 'SKIPPED': 'SKIPPED',
+ 'WARNING': 'WARNINGS',
+ 'WARNINGS': 'WARNINGS',
+ 'FAILED WITH WAIVER': 'FAILED_WITH_WAIVER',
+ 'FAILED_WITH_WAIVER': 'FAILED_WITH_WAIVER',
+ 'FAILED (WITH WAIVER)': 'FAILED_WITH_WAIVER',
+ }
+ if explicit in explicit_aliases:
+ return explicit_aliases[explicit]
+
+ statuses = [
+ get_subtest_status(subtest.get('sub_test_result', {}))
+ for subtest in test.get('subtests', [])
+ ]
+ if 'FAILED' in statuses:
+ return 'FAILED'
+ if 'WARNINGS' in statuses:
+ return 'WARNINGS'
+ if 'FAILED_WITH_WAIVER' in statuses:
+ return 'FAILED_WITH_WAIVER'
+ if statuses and all(status == 'SKIPPED' for status in statuses):
+ return 'SKIPPED'
+ return 'PASSED'
+
+
def main():
parser = argparse.ArgumentParser(description='Generate HTML report from JSON data.')
parser.add_argument('input_json_files', nargs='+', help='Input JSON file(s)')
@@ -442,6 +497,7 @@ def main():
combined_suite_summary = {
'total_passed': 0,
'total_failed': 0,
+ 'total_skipped': 0,
'total_warnings':0,
'total_failed_with_waiver': 0
}
@@ -463,32 +519,22 @@ def main():
test_results_list.append(test_results)
- # Determine overall pass/fail
+ # Determine each standalone testcase's overall result. New parsers
+ # may provide an explicit result when one designated subtest is
+ # authoritative; older parsers retain subtest-based aggregation.
for test in test_results:
- has_failed_without_waiver = False
- has_failed_with_waiver = False
- has_warnings = False
-
- for subtest in test.get('subtests', []):
- st_status = get_subtest_status(subtest.get('sub_test_result', {}))
- if st_status == 'WARNINGS':
- has_warnings = True
- elif st_status == 'FAILED':
- has_failed_without_waiver = True
- elif st_status == 'FAILED_WITH_WAIVER':
- has_failed_with_waiver = True
-
- if has_failed_without_waiver:
- combined_suite_summary['total_failed'] += 1
- elif has_warnings:
- combined_suite_summary['total_warnings'] += 1
- elif has_failed_with_waiver:
- combined_suite_summary['total_failed_with_waiver'] += 1
- else:
- combined_suite_summary['total_passed'] += 1
+ status_key = {
+ 'PASSED': 'total_passed',
+ 'FAILED': 'total_failed',
+ 'SKIPPED': 'total_skipped',
+ 'WARNINGS': 'total_warnings',
+ 'FAILED_WITH_WAIVER': 'total_failed_with_waiver',
+ }[get_test_status(test)]
+ combined_suite_summary[status_key] += 1
total_standalones = (combined_suite_summary['total_passed'] +
combined_suite_summary['total_failed'] +
+ combined_suite_summary['total_skipped'] +
combined_suite_summary['total_warnings'] +
combined_suite_summary['total_failed_with_waiver'])
if total_standalones == 0:
diff --git a/common/log_parser/standalone_tests/logs_to_json.py b/common/log_parser/standalone_tests/logs_to_json.py
index b1d4dd90..06a8d610 100755
--- a/common/log_parser/standalone_tests/logs_to_json.py
+++ b/common/log_parser/standalone_tests/logs_to_json.py
@@ -77,6 +77,14 @@
"Test_suite_description": "Validates that the Devicetree Blob address satisfies the required alignment constraints.",
"Test_case_description": "Checks that the DTB table address is non-zero and aligned to an 8-byte boundary."
},
+ "pcie_option_rom_arch_audit": {
+ "Test_suite": "PCIeOptionRomArchAudit",
+ "Test_suite_description": "PCIe Option ROM architecture validation",
+ "Test_case_description": (
+ "Verify that PCIe Option ROM images contain a supported AArch64 UEFI "
+ "image for the platform."
+ )
+ },
}
def create_subtest(subtest_number, description, status, reason=""):
@@ -1617,6 +1625,150 @@ def parse_runtime_dev_map_conflict(log_data):
}
+PCIE_OPTION_ROM_BANNER = re.compile(
+ r'^\s*PcieOptionRomArchAudit:\s*',
+ re.IGNORECASE | re.MULTILINE,
+)
+PCIE_OPTION_ROM_FOUND = re.compile(
+ r'Scanned\s+\d+\s+EFI_PCI_IO\s+handles\s+and\s+found\s+(\d+)\s+'
+ r'PCIe\s+devices\s+with\s+Option\s+ROM\s+images\s*:',
+ re.IGNORECASE,
+)
+PCIE_OPTION_ROM_TOTAL = re.compile(
+ r'^\s*PCIe\s+devices\s+with\s+ROM\s*:\s*(\d+)\s*$',
+ re.IGNORECASE,
+)
+PCIE_OPTION_ROM_NON_COMPLIANT = re.compile(
+ r'^\s*Non-compliant\s+devices\s*:\s*(\d+)\s*$',
+ re.IGNORECASE,
+)
+
+
+def _single_pcie_count(lines, pattern, field_name):
+ """Return one required non-negative PCIe audit count."""
+ matches = [int(match.group(1)) for line in lines if (match := pattern.search(line))]
+ if len(matches) != 1:
+ raise ValueError(
+ f"PcieOptionRomArchAudit record must contain exactly one {field_name}; "
+ f"found {len(matches)}."
+ )
+ return matches[0]
+
+
+def _remove_empty_reason_lists(subtest):
+ result = subtest["sub_test_result"]
+ for key in (
+ "pass_reasons",
+ "fail_reasons",
+ "abort_reasons",
+ "skip_reasons",
+ "warning_reasons",
+ ):
+ if not result.get(key):
+ result.pop(key, None)
+
+
+def parse_pcie_option_rom_arch_audit_log(log_data):
+ """Parse one or more complete PcieOptionRomArchAudit result records."""
+ clean_lines = [re.sub(ansi_escape, "", line).rstrip("\r\n") for line in log_data]
+ record_starts = [
+ index for index, line in enumerate(clean_lines) if PCIE_OPTION_ROM_BANNER.match(line)
+ ]
+ if not record_starts:
+ raise ValueError("PcieOptionRomArchAudit record banner was not found.")
+
+ records = []
+ for index, start in enumerate(record_starts):
+ end = record_starts[index + 1] if index + 1 < len(record_starts) else len(clean_lines)
+ records.append(clean_lines[start:end])
+
+ mapping = test_suite_mapping["pcie_option_rom_arch_audit"]
+ suite_summary = {
+ "total_passed": 0,
+ "total_failed": 0,
+ "total_skipped": 0,
+ "total_aborted": 0,
+ "total_warnings": 0,
+ "total_failed_with_waiver": 0,
+ }
+ test_results = []
+
+ for record in records:
+ devices_with_rom = _single_pcie_count(
+ record,
+ PCIE_OPTION_ROM_FOUND,
+ "'Scanned ... found |