Source code for crazyhusk.reports

"""Utilities for working with report formats generated by Unreal Engine."""

# Standard Library
import datetime
import json
import os
from typing import Any, Dict, List, Optional
from xml.dom import minidom
from xml.etree import ElementTree
from xml.etree.ElementTree import Element


[docs]def report_timestamp_to_iso8601_timestamp(timestamp: str) -> str: """Convert Unreal JSON report formatted timestamp to ISO8601 timestamp.""" return datetime.datetime.strptime(timestamp, "%Y.%m.%d-%H.%M.%S").isoformat()
[docs]def report_entry_to_entry_xml(entry: Dict[str, Any]) -> Element: """Convert Unreal JSON report entry into jUnit failure XML.""" failure = Element("failure") failure.set("type", entry.get("event", {}).get("type", "")) timestamp = entry.get("timestamp") if timestamp is not None: failure.set("timestamp", report_timestamp_to_iso8601_timestamp(timestamp)) failure.set( "message", f'{entry.get("filename","")}:{entry.get("lineNumber","")} {entry.get("event",{}).get("message","")}', ) return failure
[docs]def report_test_to_testcase_xml(test: Dict[str, Any]) -> Element: """Convert Unreal JSON report test into jUnit testcase.""" test_case = Element("testcase") test_case.set("name", test.get("testDisplayName", "")) test_case.set("classname", test.get("fullTestPath", "")) test_case.set("status", test.get("state", "")) for entry in test.get("entries", []): test_case.append(report_entry_to_entry_xml(entry)) return test_case
[docs]def report_object_to_testsuite_xml(report: Dict[str, Any]) -> Element: """Convert Unreal JSON report into jUnit testsuite.""" test_suite = Element("testsuite") test_suite.set("tests", str(len(report.get("tests", [])))) test_suite.set("failures", str(report.get("failed", 0))) test_suite.set("skipped", str(report.get("notRun", 0))) test_suite.set("time", str(report.get("totalDuration", 0.0))) timestamp = report.get("reportCreatedOn") if timestamp is not None: test_suite.set( "timestamp", report_timestamp_to_iso8601_timestamp(timestamp), ) branch, changelist, platform = report.get("clientDescriptor", " - - ").split(" - ") properties = Element("properties") branch_property = Element("property") branch_property.set("name", "branch") branch_property.set("value", branch) properties.append(branch_property) changelist_property = Element("property") changelist_property.set("name", "changelist") changelist_property.set("value", changelist) properties.append(changelist_property) platform_property = Element("property") platform_property.set("name", "platform") platform_property.set("value", platform) properties.append(platform_property) test_suite.append(properties) for test in report.get("tests", []): test_suite.append(report_test_to_testcase_xml(test)) return test_suite
[docs]def json_report_to_dict(report_file: str) -> Dict[str, Any]: """Deserialize Unreal JSON report file into a dictionary.""" if not os.path.isfile(report_file): raise ValueError(f"JSON report not found: {report_file}") if not os.path.splitext(report_file)[-1] == ".json": raise ValueError(f"Report file is not JSON: {report_file}") with open(report_file, "r", encoding="utf-8-sig") as json_report: report_dct = json.load(json_report) if not isinstance(report_dct, dict): raise ValueError(f"JSON report returns non-object: {report_file}") return report_dct
[docs]def write_junit_xml_report(report_file: str, test_suites: Element) -> None: """Write XML test suites to a file.""" if not os.path.splitext(report_file)[-1] == ".xml": raise ValueError(f"Report file is not XML: {report_file}") report_dir = os.path.dirname(report_file) if not os.path.isdir(report_dir): os.makedirs(report_dir, exist_ok=True) with open(report_file, "w", encoding="utf-8") as xml_report: xml_report.write( minidom.parseString(ElementTree.tostring(test_suites, "utf-8")).toprettyxml( indent=" " * 4 ) )
[docs]def json_reports_to_junit_xml(junit_file: str, *json_reports: str) -> None: """Convert a JSON report from Unreal automation to jUnit XML format.""" test_suites = Element("testsuites") test_suites.set("name", "Unreal Automation Tests") total_tests = 0 total_failures = 0 total_errors = 0 total_time = 0.0 for report in json_reports: test_suite = report_object_to_testsuite_xml(json_report_to_dict(report)) test_suite.set("name", os.path.splitext(os.path.basename(report))[0]) total_tests += int(test_suite.get("tests", 0)) total_failures += int(test_suite.get("failures", 0)) total_errors += int(test_suite.get("errors", 0)) total_time += float(test_suite.get("time", 0.0)) test_suites.append(test_suite) test_suites.set("tests", str(total_tests)) test_suites.set("failures", str(total_failures)) test_suites.set("errors", str(total_errors)) test_suites.set("time", str(total_time)) write_junit_xml_report(junit_file, test_suites)