Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions common/log_parser/acs-results-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,9 @@
"Test_case_description": {
"type": "string"
},
"test_result": {
"$ref": "#/definitions/sub_test_result_string"
},
"subtests": {
"type": "array",
"items": {
Expand Down Expand Up @@ -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"
},
Expand Down
37 changes: 31 additions & 6 deletions common/log_parser/apply_waivers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down Expand Up @@ -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', '')
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions common/log_parser/enrich_suite_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"ETHTOOL-TEST",
"NETWORK-BOOT",
"OS-TESTS",
"PCIE-OPTION-ROM-ARCH-AUDIT",
"PSCI",
"READ-WRITE-CHECK-BLK-DEVICES",
"RUNTIME-DEV-MAP",
Expand Down
2 changes: 1 addition & 1 deletion common/log_parser/generate_acs_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
40 changes: 28 additions & 12 deletions common/log_parser/main_log_parser.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
################################################################################
Expand Down
19 changes: 16 additions & 3 deletions common/log_parser/merge_jsons.py
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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.
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
98 changes: 72 additions & 26 deletions common/log_parser/standalone_tests/json_to_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -272,6 +273,10 @@ def generate_html(suite_summary, test_results_list, output_html_path,
<td>Failed</td>
<td class="fail">{{ total_failed }}</td>
</tr>
<tr>
<td>Skipped</td>
<td class="skipped">{{ total_skipped }}</td>
</tr>
<tr>
<td>Warnings</td>
<td class="warning">{{ total_warnings }}</td>
Expand Down Expand Up @@ -311,6 +316,13 @@ def generate_html(suite_summary, test_results_list, output_html_path,

<div class="test-case-header">Test Case: {{ test.Test_case }}</div>
<div class="test-case-description">Description: {{ test.Test_case_description }}</div>
{% set overall_status = get_test_status(test) %}
<div class="test-case-description">
Overall Result:
<span class="{% if overall_status == 'PASSED' %}pass{% elif overall_status == 'FAILED' %}fail{% elif overall_status == 'FAILED_WITH_WAIVER' %}waiver{% elif overall_status == 'WARNINGS' %}warning{% elif overall_status == 'SKIPPED' %}skipped{% endif %}">
{{ 'FAILED WITH WAIVER' if overall_status == 'FAILED_WITH_WAIVER' else overall_status }}
</span>
</div>

{# We ignore dynamic reason columns and use a single "Reason" + "Waiver Reason" #}
<table>
Expand Down Expand Up @@ -364,7 +376,13 @@ def generate_html(suite_summary, test_results_list, output_html_path,
{% endif %}
{% endfor %}
<td>
{{ all_reasons|join("<br>")|safe if all_reasons else "N/A" }}
{% if all_reasons %}
{% for reason in all_reasons %}
{{ reason|e }}{% if not loop.last %}<br>{% endif %}
{% endfor %}
{% else %}
N/A
{% endif %}
</td>
<td>
{{ r.waiver_reason if r.waiver_reason else "N/A" }}
Expand All @@ -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))

Expand All @@ -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")
Expand All @@ -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)')
Expand All @@ -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
}
Expand All @@ -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:
Expand Down
Loading
Loading