Source code for fvm.generate_test_cases

# Copyright 2024-2026 Universidad de Sevilla
# SPDX-License-Identifier: Apache-2.0

"""Generate test case structures for reports."""
import uuid
import json
import os
import shutil
import re

[docs] def generate_test_case(design_name, prefix, step, results_dir, status="passed", outdir=None, start_time=None, stop_time=None, friendliness_score=None, properties = None, step_summary_html = None, html_files=None, drom_svg_path=None): """ Generate a test case structure for reports. :param design_name: Name of the design. :type design_name: str :param prefix: Prefix for the test case ID. :type prefix: str :param step: Name of the step. :type step: str :param results_dir: Directory to store results. :type results_dir: str :param status: Status of the test case (default is "passed"). :type status: str :param start_time: Start time in milliseconds since epoch. :type start_time: int :param stop_time: Stop time in milliseconds since epoch. :type stop_time: int :param friendliness_score: Friendliness score (optional). :type friendliness_score: float or None :param properties: JSON string of properties (optional). :type properties: str or None :param step_summary_html: Path to HTML summary file (optional). :type step_summary_html: str or None :param html_files: List of additional HTML files to attach (optional). :type html_files: list of str or None """ test_case_uuid = str(uuid.uuid4()) history_id = f"{prefix}.{design_name}.{step}" test_case_id = f"{prefix}.{design_name}.{step}_id" full_name = os.path.join(outdir, design_name, step, f"{step}.log") name = f"{design_name}.{step}" if step_summary_html is not None: desc_key = "descriptionHtml" description = html_to_oneline(step_summary_html) else: desc_key = "description" description=f"This is the step {step} of FVM" links = [ { "type": "link", "name": "Documentation", "url": "https://docs.example.com/testUserLogin" } ] labels = [ { "name": "framework", "value": "FVM" }, { "name": "epic", "value": step }, { "name": "parentSuite", "value": f"{prefix}.{design_name}" }, { "name": "package", "value": step } ] status_details = None if status.lower() == "skipped": status_details = { "known": False, "muted": False, "flaky": False, "message": f"The FVM step {step} was skipped for design '{design_name}'.", "trace": "Skipped due to unmet conditions or manual override." } else: if status.lower() == "failed": status_details = { "known": False, "muted": False, "flaky": False, "message": f"The FVM step {step} failed for design '{design_name}'.", "trace": "Failure due to critical issues or build errors." } elif (status.lower() == "passed" and step == "friendliness" and friendliness_score is not None): friendliness_score = round(friendliness_score, 2) status_details = { "known": False, "muted": False, "flaky": False, "message": f"The friendliness score for design " f"'{design_name}' is {friendliness_score}%." } # Attachments attachments = [] # Add standard output attachment attachment_uuid = str(uuid.uuid4()) attachment = os.path.join(results_dir, f"{attachment_uuid}-attachment.log") original_file = os.path.join(outdir, design_name, step, f"{step}.log") if os.path.exists(original_file): shutil.copy(original_file, attachment) attachments.append( { "name": f"{step}.log", "source": f"{attachment_uuid}-attachment.log", "type": "text/plain" } ) # Add HTML attachments if html_files: for original_file in html_files: attachment_uuid = str(uuid.uuid4()) attachment = os.path.join(results_dir, f"{attachment_uuid}-attachment.html") if os.path.exists(original_file): shutil.copy(original_file, attachment) attachments.append( { "name": os.path.basename(original_file), "source": f"{attachment_uuid}-attachment.html", "type": "text/html" } ) # Add drom SVG attachments if drom_svg_path: for svg_file in drom_svg_path: attachment_uuid = str(uuid.uuid4()) attachment = os.path.join(results_dir, f"{attachment_uuid}-attachment.svg") if os.path.exists(svg_file): shutil.copy(svg_file, attachment) attachments.append( { "name": os.path.basename(svg_file), "source": f"{attachment_uuid}-attachment.svg", "type": "image/svg+xml" } ) steps = [] if step == "prove": if properties is not None: properties = json.loads(properties) for entry in properties.get("Proven", []): assertion_name = entry.get("assertion", "unknown") vacuity_check = entry.get("vacuity_check", "unknown") entry_time = entry.get("time", 0) step_in_steps ={ "name": "Assertion: " + assertion_name, "status": "broken" if vacuity_check == "failed" else "passed", "start": start_time, "stop": start_time + entry_time * 1000, "steps": [] } steps.append(step_in_steps) for entry in properties.get("Fired", []): steps.append({ "name": "Assertion: " + entry["assertion"], "status": "failed", "start": start_time, "stop": start_time + entry["time"] * 1000 }) for entry in properties.get("Covered", []): steps.append({ "name": "Cover: " + entry["assertion"], "status": "passed", "start": start_time, "stop": start_time + entry["time"] * 1000 }) for entry in properties.get("Uncoverable", []): steps.append({ "name": "Cover: " + entry["assertion"], "status": "failed", "start": start_time, "stop": start_time + entry["time"] * 1000 }) test_case = { "uuid": test_case_uuid, "historyId": history_id, "testCaseId": test_case_id, "fullName": full_name, "name": name, desc_key: description, "links": links, "labels": labels, "status": status, "start": start_time, "stop": stop_time, "steps": steps } if status_details: test_case["statusDetails"] = status_details if status.lower() != "skipped": test_case["attachments"] = attachments output_file = os.path.join(results_dir, f"{test_case_uuid}-result.json") with open(output_file, 'w', encoding="utf-8") as json_file: json.dump(test_case, json_file, indent=2)
[docs] def html_to_oneline(html_file): """Convert an HTML file to a single line string, preserving <pre> blocks.""" with open(html_file, "r", encoding="utf-8") as f: html = f.read() html = re.sub(r"<pre.*?>.*?</pre>", lambda m: m.group(0), html, flags=re.DOTALL) def remove_outside_pre(text): parts = re.split(r"(<pre.*?>.*?</pre>)", text, flags=re.DOTALL) for i, part in enumerate(parts): if not part.startswith("<pre"): parts[i] = part.replace("\n", "") return "".join(parts) html_oneline = remove_outside_pre(html) return html_oneline