From 596e2dc9574a3f6420f2aab31913fa40cfe145af Mon Sep 17 00:00:00 2001 From: LongNTH5 Date: Tue, 16 Jun 2026 15:34:06 +0700 Subject: [PATCH 1/2] Revert "Merge branch 'main' of https://github.com/ezrafield/c2testcase" This reverts commit 822d3edd783588207170a93173bd3d555fcd9377, reversing changes made to 0be987c0663bc42f60b3ce0e3f422efc46c0f4f2. --- src/api/app.py | 6 +++--- tests/integration/test_fastapi_app.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/app.py b/src/api/app.py index 9f4f0c1..b80f810 100644 --- a/src/api/app.py +++ b/src/api/app.py @@ -353,12 +353,12 @@ def render_index_html() -> str: .export-action { width: auto; min-width: 150px; - background: #f4b183; + background: #99ccff; color: #1f2937; - border: 1px solid #c98245; + border: 1px solid #6699cc; } .export-action:hover { - background: #e6a06c; + background: #7fb5e8; } .export-status { margin-top: 12px; diff --git a/tests/integration/test_fastapi_app.py b/tests/integration/test_fastapi_app.py index 033ecc1..63c1c0c 100644 --- a/tests/integration/test_fastapi_app.py +++ b/tests/integration/test_fastapi_app.py @@ -33,7 +33,7 @@ def test_web_app_exposes_table_and_excel_controls() -> None: assert '' in response.text assert '' in response.text assert '' in response.text - assert "#f4b183" in response.text + assert "#99ccff" in response.text assert "testcase-table" in response.text assert "excel-export-panel" in response.text assert "excel_export_submit" in response.text From 2e657a44873599ffa5c167e4f102e1e52a9cd359 Mon Sep 17 00:00:00 2001 From: LongNTH5 Date: Wed, 1 Jul 2026 15:36:00 +0700 Subject: [PATCH 2/2] update --- src/api/app.py | 32 +- src/cli.py | 30 +- src/services/mcdc_generator.py | 605 +++++++++++++-- tests/evaluation/SIL_SV_ATG_1 (1).xlsx | Bin 0 -> 100585 bytes tests/evaluation/SIL_SV_ATG_1_sample.xlsx | Bin 0 -> 18385 bytes tests/evaluation/ft-hfs_failinform-fh4-4.c | 809 +++++++++++++++++++++ tests/integration/test_fastapi_app.py | 54 ++ tests/unit/test_mcdc_generator.py | 243 +++++++ 8 files changed, 1713 insertions(+), 60 deletions(-) create mode 100644 tests/evaluation/SIL_SV_ATG_1 (1).xlsx create mode 100644 tests/evaluation/SIL_SV_ATG_1_sample.xlsx create mode 100644 tests/evaluation/ft-hfs_failinform-fh4-4.c diff --git a/src/api/app.py b/src/api/app.py index 9b76608..25b35fe 100644 --- a/src/api/app.py +++ b/src/api/app.py @@ -13,6 +13,7 @@ MCDC_MODES, excel_export_rows, generate_mcdc_report, + parse_support_template, safe_excel_filename, testcase_table_rows_from_dict, testcase_table_rows_to_csv, @@ -40,14 +41,15 @@ def index() -> str: async def generate_cases( source: UploadFile = File(...), headers: list[UploadFile] | None = File(default=None), + support_template: UploadFile | None = File(default=None), target_function: str = Form(default=""), input_variables: str = Form(default=""), output_variables: str = Form(default=""), compile_flags: str = Form(default=""), - excel_format_version: str = Form(default="1.3"), + excel_format_version: str = Form(default=""), excel_architecture: str = Form(default=""), excel_scope: str = Form(default=""), - excel_name: str = Form(default="mcdc_testcases"), + excel_name: str = Form(default=""), max_conditions: int = Form(default=12), mcdc_mode: str = Form(default="unique-cause"), ) -> dict[str, object]: @@ -73,6 +75,18 @@ async def generate_cases( header_path.write_bytes(await header.read()) header_paths.append(header_path) + interface_override = None + template_metadata: ExcelExportMetadata | None = None + if support_template is not None and support_template.filename: + if not support_template.filename.lower().endswith(".xlsx"): + raise HTTPException(status_code=400, detail="Support template must be a .xlsx file.") + template_path = workspace / safe_name(support_template.filename) + template_path.write_bytes(await support_template.read()) + try: + interface_override, template_metadata = parse_support_template(template_path) + except (ValueError, KeyError) as error: + raise HTTPException(status_code=400, detail=f"Could not parse support template: {error}") + output_dir = workspace / "out" parsed_input_variables, manual_inputs = parse_variable_setup(input_variables) parsed_output_variables, manual_outputs = parse_variable_setup(output_variables) @@ -88,12 +102,16 @@ async def generate_cases( output_variables=parsed_output_variables, manual_outputs=manual_outputs, mcdc_mode=mcdc_mode, + interface_override=interface_override, ) + # When a support template carries metadata, use it as the default for any blank + # Excel export field so the generated sheet preserves the template's identity. + template_metadata = template_metadata or ExcelExportMetadata() excel_metadata = ExcelExportMetadata( - format_version=excel_format_version.strip() or "1.3", - architecture=excel_architecture.strip(), - scope=excel_scope.strip(), - name=excel_name.strip() or "mcdc_testcases", + format_version=excel_format_version.strip() or template_metadata.format_version or "1.3", + architecture=excel_architecture.strip() or template_metadata.architecture, + scope=excel_scope.strip() or template_metadata.scope, + name=excel_name.strip() or template_metadata.name or "mcdc_testcases", ) json_path, harness_path, gap_report_path, excel_path = write_report_artifacts( report, @@ -580,6 +598,8 @@ def render_index_html() -> str: + + diff --git a/src/cli.py b/src/cli.py index a929326..5545d99 100644 --- a/src/cli.py +++ b/src/cli.py @@ -3,7 +3,13 @@ import argparse from pathlib import Path -from src.services.mcdc_generator import MCDC_MODES, generate_mcdc_report, write_report_artifacts +from src.services.mcdc_generator import ( + MCDC_MODES, + ExcelExportMetadata, + generate_mcdc_report, + parse_support_template, + write_report_artifacts, +) ManualValue = int | float | bool | str @@ -28,6 +34,11 @@ def build_parser() -> argparse.ArgumentParser: default=[], help="Optional .h file recorded for future harness/build integration.", ) + parser.add_argument( + "--support-template", + type=Path, + help="Optional support Excel (.xlsx) template defining the Input/Parameter/Output columns to generate.", + ) parser.add_argument( "--target-function", help="Optional function name to record in generated reports.", @@ -88,6 +99,18 @@ def main(argv: list[str] | None = None) -> int: if not include_dir.exists() or not include_dir.is_dir(): raise SystemExit(f"Include directory not found: {include_dir}") + interface_override = None + excel_metadata: ExcelExportMetadata | None = None + if args.support_template is not None: + if not args.support_template.exists(): + raise SystemExit(f"Support template not found: {args.support_template}") + if args.support_template.suffix.lower() != ".xlsx": + raise SystemExit("Support template must be a .xlsx file.") + try: + interface_override, excel_metadata = parse_support_template(args.support_template) + except (ValueError, KeyError) as error: + raise SystemExit(f"Could not parse support template: {error}") + input_variables, manual_inputs = parse_variable_setup(args.input_variable) output_variables, manual_outputs = parse_variable_setup(args.output_variable) report = generate_mcdc_report( @@ -102,8 +125,11 @@ def main(argv: list[str] | None = None) -> int: output_variables=output_variables, manual_outputs=manual_outputs, mcdc_mode=args.mcdc_mode, + interface_override=interface_override, + ) + json_path, harness_path, gap_report_path, excel_path = write_report_artifacts( + report, args.output_dir, excel_metadata=excel_metadata ) - json_path, harness_path, gap_report_path, excel_path = write_report_artifacts(report, args.output_dir) print(f"Generated MC/DC target score ({report.mcdc_mode}): {report.score:.1%}") print(f"Cases: {json_path}") diff --git a/src/services/mcdc_generator.py b/src/services/mcdc_generator.py index ceacb38..8c2ff58 100644 --- a/src/services/mcdc_generator.py +++ b/src/services/mcdc_generator.py @@ -11,10 +11,11 @@ import shutil import subprocess from tempfile import TemporaryDirectory +from xml.etree import ElementTree from xml.sax.saxutils import escape, quoteattr from zipfile import ZIP_DEFLATED, ZipFile from pathlib import Path -from typing import Any +from typing import Any, Callable BOOLEAN_OPERATORS = {"&&", "||"} @@ -95,6 +96,7 @@ class InterfaceAnalysis: array_sizes: dict[str, int] initial_values: dict[str, Any] assignment_sources: dict[str, tuple[str, ...]] + assignment_expressions: dict[str, str] assignment_targets: tuple[str, ...] condition_input_roots: tuple[str, ...] condition_output_roots: tuple[str, ...] @@ -286,6 +288,7 @@ class MCDCReport: coverage_status: str = "not checked" warnings: tuple[str, ...] = field(default_factory=tuple) interface_graph: dict[str, Any] = field(default_factory=dict) + interface_override: "InterfaceOverride | None" = None @property def score(self) -> float: @@ -358,6 +361,184 @@ class ExcelExportMetadata: name: str = "mcdc_testcases" +@dataclass(frozen=True) +class InterfaceOverride: + """A test interface (column layout) supplied by a support Excel template. + + The names define exactly which Input / Parameter / Output columns the generated + sheet contains and in what order; ``array_sizes`` records how many element columns a + repeated header expanded to (defaults to 1 for scalars).""" + + input_names: tuple[str, ...] = field(default_factory=tuple) + parameter_names: tuple[str, ...] = field(default_factory=tuple) + output_names: tuple[str, ...] = field(default_factory=tuple) + array_sizes: dict[str, int] = field(default_factory=dict) + + +_XLSX_NS = "{http://schemas.openxmlformats.org/spreadsheetml/2006/main}" + + +def column_index_from_reference(reference: str) -> int: + """Convert an A1-style cell reference to a 0-based column index (``A`` -> 0).""" + letters = "".join(character for character in reference if character.isalpha()) + index = 0 + for character in letters: + index = index * 26 + (ord(character.upper()) - ord("A") + 1) + return index - 1 + + +def read_xlsx_grid(path: Path) -> list[list[Any]]: + """Read the first worksheet of an .xlsx file into a dense 2-D grid of cell values. + + Mirrors the project's hand-rolled xlsx writer (zip of XML, no openpyxl dependency). + Resolves shared strings, inline strings, booleans, and numbers; missing cells become + ``None``.""" + + with ZipFile(path) as archive: + shared_strings = _read_shared_strings(archive) + worksheets = sorted( + name for name in archive.namelist() if re.fullmatch(r"xl/worksheets/sheet\d+\.xml", name) + ) + if not worksheets: + raise ValueError(f"No worksheet found in {path}.") + sheet_xml = archive.read(worksheets[0]) + + root = ElementTree.fromstring(sheet_xml) + rows_by_number: dict[int, list[Any]] = {} + for row_element in root.iter(f"{_XLSX_NS}row"): + row_number = int(row_element.get("r", "0") or 0) + cells: dict[int, Any] = {} + for cell in row_element.iter(f"{_XLSX_NS}c"): + reference = cell.get("r") + if not reference: + continue + cells[column_index_from_reference(reference)] = _read_cell_value(cell, shared_strings) + width = max(cells) + 1 if cells else 0 + rows_by_number[row_number] = [cells.get(index) for index in range(width)] + + if not rows_by_number: + return [] + max_row = max(rows_by_number) + return [rows_by_number.get(number, []) for number in range(1, max_row + 1)] + + +def _read_shared_strings(archive: ZipFile) -> list[str]: + try: + data = archive.read("xl/sharedStrings.xml") + except KeyError: + return [] + root = ElementTree.fromstring(data) + return [ + "".join(node.text or "" for node in si.iter(f"{_XLSX_NS}t")) + for si in root.iter(f"{_XLSX_NS}si") + ] + + +def _read_cell_value(cell: ElementTree.Element, shared_strings: list[str]) -> Any: + cell_type = cell.get("t") + if cell_type == "inlineStr": + inline = cell.find(f"{_XLSX_NS}is") + if inline is None: + return None + return "".join(node.text or "" for node in inline.iter(f"{_XLSX_NS}t")) + value_node = cell.find(f"{_XLSX_NS}v") + if value_node is None or value_node.text is None: + return None + text = value_node.text + if cell_type == "s": + index = int(text) + return shared_strings[index] if 0 <= index < len(shared_strings) else None + if cell_type == "b": + return text.strip() not in ("0", "") + if cell_type in ("str", "e"): + return text + return _coerce_numeric(text) + + +def _coerce_numeric(text: str) -> Any: + cleaned = text.strip() + try: + if re.fullmatch(r"[+-]?\d+", cleaned): + return int(cleaned) + return float(cleaned) + except ValueError: + return text + + +def parse_support_template(path: Path) -> tuple[InterfaceOverride, ExcelExportMetadata]: + """Parse a support Excel template into an interface override and export metadata. + + The template uses the ATG "Format Version 1.3" layout: rows 1-4 carry metadata + (label in column A, value in column B); a ``Mode`` row carries the group label at the + start of each group; the following ``Step`` row carries the column variable names. + Data rows below are ignored. Consecutive repeated headers collapse to an array size.""" + + grid = read_xlsx_grid(Path(path)) + + def label_of(row: list[Any]) -> Any: + value = row[0] if row else None + return value.strip() if isinstance(value, str) else value + + metadata_values: dict[str, Any] = {} + mode_row_index: int | None = None + header_row_index: int | None = None + for index, row in enumerate(grid): + label = label_of(row) + if label in ("Format Version", "Architecture", "Scope", "Name"): + metadata_values[label] = row[1] if len(row) > 1 else None + elif label == "Mode": + mode_row_index = index + elif label == "Step": + header_row_index = index + + if mode_row_index is None or header_row_index is None: + raise ValueError("Support template is missing the 'Mode' and 'Step' header rows.") + + mode_row = grid[mode_row_index] + header_row = grid[header_row_index] + groups: dict[str, list[str]] = {"Inputs": [], "Parameters": [], "Outputs": []} + array_sizes: dict[str, int] = {} + current_group: str | None = None + width = max(len(mode_row), len(header_row)) + for column in range(1, width): # column 0 holds "Mode"/"Step" + mode_value = mode_row[column] if column < len(mode_row) else None + if isinstance(mode_value, str) and mode_value.strip(): + current_group = mode_value.strip() + header_value = header_row[column] if column < len(header_row) else None + if not isinstance(header_value, str) or not header_value.strip(): + continue + name = header_value.strip() + bucket = groups.get(current_group or "") + if bucket is None: # Comment or any other non-interface group + continue + if bucket and bucket[-1] == name: # consecutive repeat => array element + array_sizes[name] = array_sizes.get(name, 1) + 1 + else: + bucket.append(name) + + override = InterfaceOverride( + input_names=tuple(groups["Inputs"]), + parameter_names=tuple(groups["Parameters"]), + output_names=tuple(groups["Outputs"]), + array_sizes=array_sizes, + ) + metadata = ExcelExportMetadata( + format_version=_metadata_text(metadata_values.get("Format Version"), "1.3"), + architecture=_metadata_text(metadata_values.get("Architecture"), ""), + scope=_metadata_text(metadata_values.get("Scope"), ""), + name=_metadata_text(metadata_values.get("Name"), "mcdc_testcases"), + ) + return override, metadata + + +def _metadata_text(value: Any, default: str) -> str: + if value is None or value == "": + return default + if isinstance(value, float) and value.is_integer(): + return str(int(value)) + return str(value) + + def generate_mcdc_report( source_path: Path, max_conditions: int = MAX_ENUMERATED_CONDITIONS, @@ -370,11 +551,15 @@ def generate_mcdc_report( output_variables: tuple[str, ...] = (), manual_outputs: dict[str, TableValue] | None = None, mcdc_mode: str = "unique-cause", + interface_override: InterfaceOverride | None = None, ) -> MCDCReport: if mcdc_mode not in MCDC_MODES: raise ValueError(f"Unsupported MC/DC mode: {mcdc_mode}") - source = source_path.read_text(encoding="utf-8") + try: + source = source_path.read_text(encoding="utf-8") + except UnicodeDecodeError: + source = source_path.read_text(encoding="latin-1") clean_source = strip_comments_and_strings(source) decisions = extract_decisions(clean_source) detected_input_variables = extract_function_parameters(clean_source, target_function) @@ -386,11 +571,12 @@ def generate_mcdc_report( toolchain_details = detect_toolchain_details() coverage_ready, coverage_status = summarize_coverage_readiness(toolchain_details) + solver_ctx = build_solver_context(interface_analysis) return MCDCReport( source=str(source_path), source_text=source, - decisions=tuple(generate_decision_result(decision, max_conditions, mcdc_mode) for decision in decisions), + decisions=tuple(generate_decision_result(decision, max_conditions, mcdc_mode, solver_ctx) for decision in decisions), input_variables=tuple(dict.fromkeys((*detected_input_variables, *input_variables))), manual_inputs=manual_inputs or {}, output_variables=tuple(dict.fromkeys(output_variables)), @@ -406,6 +592,7 @@ def generate_mcdc_report( coverage_status=coverage_status, warnings=tuple(warnings), interface_graph=interface_analysis.variable_graph.to_dict(), + interface_override=interface_override, ) @@ -760,12 +947,21 @@ def testcase_table(report: MCDCReport) -> dict[str, Any]: def targetlink_logged_interface_table(report: MCDCReport) -> dict[str, Any] | None: - interface = extract_log_var_interface(report.source_text) - if interface is None: - return None - - input_names, parameter_names, output_names, array_sizes, initial_values = interface - output_names = targetlink_output_order(output_names) + if report.interface_override is not None: + override = report.interface_override + # A user-supplied support template defines the exact column layout and order; + # parameter/array defaults still come from the C source's initial values. + initial_values = dict(analyze_c_interface(report.source_text).initial_values) + input_names = list(override.input_names) + parameter_names = list(override.parameter_names) + output_names = list(override.output_names) + array_sizes = dict(override.array_sizes) + else: + interface = extract_log_var_interface(report.source_text) + if interface is None: + return None + input_names, parameter_names, output_names, array_sizes, initial_values = interface + output_names = targetlink_output_order(output_names) input_columns, input_keys = expand_interface_columns(input_names, array_sizes) parameter_columns, parameter_keys = expand_interface_columns(parameter_names, array_sizes) output_columns, output_keys = expand_interface_columns(output_names, array_sizes) @@ -979,7 +1175,7 @@ def analyze_c_interface(source: str) -> InterfaceAnalysis: initial_values = extract_top_level_initial_values(source, global_order) global_names = set(global_order) local_names = frozenset(extract_local_variables(function_source, global_names)) - assignment_sources, assignment_targets = extract_assignment_dependencies(function_source, global_names, set(function_parameters)) + assignment_sources, assignment_targets, assignment_expressions = extract_assignment_dependencies(function_source, global_names, set(function_parameters)) variable_graph = build_variable_graph( clean_source, function_source, @@ -1035,6 +1231,7 @@ def analyze_c_interface(source: str) -> InterfaceAnalysis: array_sizes=dict(array_sizes), initial_values=initial_values, assignment_sources=assignment_sources, + assignment_expressions=assignment_expressions, assignment_targets=tuple(ordered_assignment_targets), condition_input_roots=tuple(order_known_roots(condition_input_roots, global_order, function_parameters)), condition_output_roots=tuple(order_known_roots(condition_output_roots, global_order, function_parameters)), @@ -1132,9 +1329,11 @@ def extract_assignment_dependencies( source: str, global_names: set[str], parameter_names: set[str], -) -> tuple[dict[str, tuple[str, ...]], set[str]]: +) -> tuple[dict[str, tuple[str, ...]], set[str], dict[str, str]]: assignment_sources: dict[str, tuple[str, ...]] = {} assignment_targets: set[str] = set() + assignment_expressions: dict[str, str] = {} + multiply_assigned: set[str] = set() assignment_pattern = re.compile( r"(?])(?P[A-Za-z_]\w*(?:\s*(?:->|\.)\s*[A-Za-z_]\w*)?(?:\s*\[[^\]]+\])?|\*\s*[A-Za-z_]\w*|\(\s*\*\s*[A-Za-z_]\w*\s*\))\s*=(?!=)\s*(?P[^;]+);" ) @@ -1146,7 +1345,16 @@ def extract_assignment_dependencies( roots = expression_root_variables(rhs, global_names, parameter_names, assignment_sources) if roots: assignment_sources[lhs] = tuple(dict.fromkeys(roots)) - return assignment_sources, assignment_targets + # Record the RHS expression so the input solver can expand intermediates. + # Only single-assignment names are expandable; a name written more than once + # is ambiguous (depends on control flow), so it is dropped and treated as a + # directly settable variable instead. + if lhs in assignment_expressions or lhs in multiply_assigned: + multiply_assigned.add(lhs) + assignment_expressions.pop(lhs, None) + else: + assignment_expressions[lhs] = rhs.strip() + return assignment_sources, assignment_targets, assignment_expressions def build_variable_graph( @@ -1512,7 +1720,9 @@ def targetlink_generic_interface_inputs(report: MCDCReport, input_names: list[st if not input_names: return None analysis = analyze_c_interface(report.source_text) - root_names = set(analysis.global_order) | set(analysis.function_parameters) + settable, expr_map, assignment_sources = build_solver_context(analysis, set(input_names)) + graph_root_names = set(analysis.global_order) | set(analysis.function_parameters) + trace_chain = lambda name: analysis.variable_graph.trace_chain(name, graph_root_names) scenarios: list[dict[str, Any]] = [] seen: set[tuple[str, tuple[tuple[str, TableValue], ...], bool]] = set() @@ -1520,34 +1730,25 @@ def targetlink_generic_interface_inputs(report: MCDCReport, input_names: list[st for row in decision.cases: inputs = {name: report.manual_inputs.get(name, "MANUAL") for name in input_names} notes = list(row.notes) - for condition_index, (condition, desired) in enumerate(zip(decision.decision.conditions, row.values)): - candidate = value_for_condition(condition, desired) - if candidate is None: - continue - candidate_name, value = candidate - trace_key = f"{decision.decision.id}:{condition_index}" - trace = analysis.variable_graph.condition_traces.get(trace_key) - roots = tuple(trace.get("roots", ())) if trace else () - if not roots: - roots = resolve_root_variables( - candidate_name, - set(analysis.global_order), - set(analysis.function_parameters), - analysis.assignment_sources, - ) - assignable_roots = [root for root in roots if root in inputs] - if len(assignable_roots) != 1: - if len(assignable_roots) > 1: - notes.append( - f"MANUAL: ambiguous roots [{', '.join(assignable_roots)}] for `{candidate_name}`." - ) - continue - root = assignable_roots[0] - if root in inputs: - inputs[root] = value - if root != candidate_name: - chain = trace.get("chain", []) if trace else analysis.variable_graph.trace_chain(candidate_name, root_names) - notes.append(f"`{condition}` traced {' -> '.join(chain)}.") + for condition, desired in zip(decision.decision.conditions, row.values): + # Expand the condition (which usually tests an intermediate variable) + # back to the external inputs that drive it, assigning the concrete + # literal values needed to satisfy the desired truth value. + solved, solve_notes = solve_condition_inputs( + condition, + desired, + settable=settable, + expr_map=expr_map, + assignment_sources=assignment_sources, + trace_chain=trace_chain, + prior=inputs, + ) + for name, value in solved.items(): + if name in inputs: + inputs[name] = value + for note in solve_notes: + if note not in notes: + notes.append(note) key = ( decision.decision.id, tuple((name, inputs[name]) for name in input_names), @@ -2168,7 +2369,12 @@ def split_parameter_list(raw_parameters: str) -> list[str]: return parameters -def generate_decision_result(decision: Decision, max_conditions: int, mcdc_mode: str = "unique-cause") -> DecisionResult: +def generate_decision_result( + decision: Decision, + max_conditions: int, + mcdc_mode: str = "unique-cause", + solver_ctx: SolverContext | None = None, +) -> DecisionResult: warnings: list[str] = [] condition_count = len(decision.conditions) @@ -2177,10 +2383,10 @@ def generate_decision_result(decision: Decision, max_conditions: int, mcdc_mode: if condition_count > max_conditions: if mcdc_mode in {"unique-cause", "masking"}: - direct_result = generate_direct_chain_result(decision) + direct_result = generate_direct_chain_result(decision, solver_ctx) if direct_result is not None: return direct_result - grouped_result = generate_direct_grouped_and_result(decision) + grouped_result = generate_direct_grouped_and_result(decision, solver_ctx) if grouped_result is not None: return grouped_result message = f"Decision has {condition_count} conditions; capped at {max_conditions} to avoid path explosion." @@ -2204,7 +2410,7 @@ def generate_decision_result(decision: Decision, max_conditions: int, mcdc_mode: for values in truth_rows } if mcdc_mode == "multicondition": - return generate_multicondition_result(decision, truth_rows, outcomes) + return generate_multicondition_result(decision, truth_rows, outcomes, solver_ctx) independence_pairs = find_independence_pairs(decision, truth_rows, outcomes, mcdc_mode) @@ -2221,7 +2427,7 @@ def generate_decision_result(decision: Decision, max_conditions: int, mcdc_mode: environment_gaps: list[Gap] = [] seen_note_gaps: set[tuple[str, str]] = set() for values, covers in sorted(selected_rows.items()): - assignments, notes = concretize_conditions(decision.conditions, values) + assignments, notes = concretize_conditions(decision.conditions, values, solver_ctx) rows.append( MCDCRow( values=values, @@ -2259,7 +2465,7 @@ def generate_decision_result(decision: Decision, max_conditions: int, mcdc_mode: ) -def generate_direct_chain_result(decision: Decision) -> DecisionResult | None: +def generate_direct_chain_result(decision: Decision, solver_ctx: SolverContext | None = None) -> DecisionResult | None: operator = detect_uniform_chain_operator(decision.expression, decision.conditions) if operator is None or has_duplicate_conditions(decision.conditions): return None @@ -2287,7 +2493,7 @@ def generate_direct_chain_result(decision: Decision) -> DecisionResult | None: environment_gaps: list[Gap] = [] seen_note_gaps: set[tuple[str, str]] = set() for values, covers in sorted(selected_rows.items()): - assignments, notes = concretize_conditions(decision.conditions, values) + assignments, notes = concretize_conditions(decision.conditions, values, solver_ctx) rows.append( MCDCRow( values=values, @@ -2317,7 +2523,7 @@ def generate_direct_chain_result(decision: Decision) -> DecisionResult | None: ) -def generate_direct_grouped_and_result(decision: Decision) -> DecisionResult | None: +def generate_direct_grouped_and_result(decision: Decision, solver_ctx: SolverContext | None = None) -> DecisionResult | None: groups = parse_top_level_and_groups(decision.expression, decision.conditions) if groups is None or has_duplicate_conditions(decision.conditions): return None @@ -2350,7 +2556,7 @@ def generate_direct_grouped_and_result(decision: Decision) -> DecisionResult | N outcome = evaluate_decision(decision.expression, decision.conditions, values) if outcome is None: return None - assignments, notes = concretize_conditions(decision.conditions, values) + assignments, notes = concretize_conditions(decision.conditions, values, solver_ctx) rows.append( MCDCRow( values=values, @@ -2458,11 +2664,12 @@ def generate_multicondition_result( decision: Decision, truth_rows: list[tuple[bool, ...]], outcomes: dict[tuple[bool, ...], bool | None], + solver_ctx: SolverContext | None = None, ) -> DecisionResult: rows: list[MCDCRow] = [] covered_conditions = set() for values in truth_rows: - assignments, notes = concretize_conditions(decision.conditions, values) + assignments, notes = concretize_conditions(decision.conditions, values, solver_ctx) rows.append( MCDCRow( values=values, @@ -2654,11 +2861,305 @@ def replace_condition_occurrences( return replaced -def concretize_conditions(conditions: tuple[str, ...], values: tuple[bool, ...]) -> tuple[dict[str, TableValue], tuple[str, ...]]: +SolverContext = tuple[set[str], dict[str, str], dict[str, tuple[str, ...]]] + + +def build_solver_context(analysis: "InterfaceAnalysis", settable: set[str] | None = None) -> SolverContext: + """Build the (settable, expr_map, assignment_sources) triple the input solver needs. + + ``settable`` defaults to every global and function parameter (the variables that + can be driven directly). ``expr_map`` is the set of single-assignment intermediates + (locals such as ``Sa9_LogicalOperator1``) that must be expanded to reach the real + inputs; names that are themselves settable are excluded so feedback state variables + are set directly rather than expanded. ``assignment_sources`` lets the solver fall + back to single-root resolution for arithmetic intermediates compared to a literal. + """ + + if settable is None: + settable = set(analysis.global_order) | set(analysis.function_parameters) + expr_map = {name: rhs for name, rhs in analysis.assignment_expressions.items() if name not in settable} + return settable, expr_map, analysis.assignment_sources + + +def clean_boolean_expression(expression: str) -> str: + value = expression.strip() + while True: + candidate = strip_enclosing_parentheses(value) + candidate = strip_simple_casts(candidate).strip() + candidate = strip_enclosing_parentheses(candidate) + if candidate == value: + return candidate + value = candidate + + +def split_top_level(expression: str, operator: str) -> list[str]: + parts: list[str] = [] + depth = 0 + start = 0 + index = 0 + while index < len(expression): + char = expression[index] + if char == "(": + depth += 1 + elif char == ")": + depth -= 1 + elif depth == 0 and expression[index : index + 2] == operator: + parts.append(expression[start:index].strip()) + start = index + 2 + index += 2 + continue + index += 1 + parts.append(expression[start:].strip()) + return [part for part in parts if part] + + +def parse_condition_atom(expression: str) -> tuple[str | None, str | None, int | float | None]: + """Parse a leaf condition into (name, operator, literal). + + A bare identifier (or pointer dereference) returns operator/literal as ``None`` and + is interpreted by the caller as a truthiness test (``name != 0``).""" + + cleaned = clean_boolean_expression(expression) + numeric_literal = r"([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)(?:[fFlLuU]*)" + match = re.fullmatch(rf"(.+?)\s*(==|!=|>=|<=|>|<)\s*{numeric_literal}", cleaned) + if match: + name, operator, literal_text = match.groups() + return canonical_lvalue(name), operator, parse_c_numeric_literal(literal_text) + if re.fullmatch(r"[A-Za-z_]\w*(?:\s*(?:->|\.)\s*[A-Za-z_]\w*)?(?:\s*\[[^\]]+\])?", cleaned): + return canonical_lvalue(cleaned), None, None + pointer_match = re.fullmatch(r"\(?\s*\*\s*([A-Za-z_]\w*)\s*\)?", cleaned) + if pointer_match: + return "*" + pointer_match.group(1), None, None + return None, None, None + + +def value_satisfies_atom( + value: TableValue, operator: str | None, literal: int | float | None, want: bool +) -> bool: + """Return True if ``value`` makes the atom (``name operator literal``) evaluate to ``want``. + + A ``None`` operator means a bare truthiness test (``name != 0``). Used so the solver can + keep an already-assigned input when it still satisfies a later condition instead of + overwriting it with a conflicting value.""" + + if isinstance(value, bool): + numeric: float = 1.0 if value else 0.0 + else: + try: + numeric = float(value) + except (TypeError, ValueError): + return False + if operator is None: + holds = numeric != 0 + else: + comparisons = { + "==": numeric == literal, + "!=": numeric != literal, + ">": numeric > literal, + ">=": numeric >= literal, + "<": numeric < literal, + "<=": numeric <= literal, + } + if operator not in comparisons: + return False + holds = comparisons[operator] + return holds == want + + +def solve_condition_inputs( + condition: str, + desired: bool, + *, + settable: set[str], + expr_map: dict[str, str], + assignment_sources: dict[str, tuple[str, ...]] | None = None, + trace_chain: Callable[[str], list[str]] | None = None, + prior: dict[str, TableValue] | None = None, +) -> tuple[dict[str, TableValue], list[str]]: + """Expand a decision condition down to concrete external-input values. + + ``settable`` is the set of variable names backed by input/parameter columns that can + be assigned directly. ``expr_map`` maps an intermediate variable to the right-hand + side of its single defining assignment so the solver can recurse into the logic that + computes it (e.g. ``Sa4_LogicalOperator2`` -> ``(x == 10) || (y == 10)``). + ``assignment_sources`` lets the solver fall back to single-root resolution for + arithmetic intermediates compared with a literal (e.g. an aliased input). ``prior`` + holds the values already chosen by earlier conditions in the same decision row; the + solver keeps a prior value when it still satisfies the current condition and prefers + operands that don't conflict with it, so two conditions sharing one input don't fight + (e.g. one needing ``hydact == 30`` and another needing ``hydact != 20``). Returns the + inferred input assignments plus notes for anything that could not be resolved. + """ + + assignment_sources = assignment_sources or {} + prior = prior or {} + notes: list[str] = [] + + def note_once(message: str) -> None: + if message not in notes: + notes.append(message) + + def conflicts_with_prior(result: dict[str, TableValue]) -> bool: + for name, value in result.items(): + existing = prior.get(name) + if existing is not None and existing != "MANUAL" and existing != value: + return True + return False + + def merge_all(parts: list[str], want: bool, seen: frozenset[str]) -> dict[str, TableValue] | None: + combined: dict[str, TableValue] = {} + solved_any = False + for part in parts: + result = solve(part, want, seen) + if result is None: + continue + solved_any = True + for name, value in result.items(): + if name in combined and combined[name] != value: + note_once(f"Conflicting inferred values for `{name}`; kept first value {combined[name]!r}.") + continue + combined[name] = value + return combined if solved_any else None + + def solve_any(parts: list[str], want: bool, seen: frozenset[str]) -> dict[str, TableValue] | None: + # Only one operand needs to hold (OR true / AND false). Prefer one that does not + # conflict with inputs already chosen by earlier conditions; fall back to the first + # solvable operand otherwise. Notes from operands we do not adopt are rolled back so + # they don't pollute the row's setup notes. + fallback: dict[str, TableValue] | None = None + fallback_notes: list[str] = [] + leftover_notes: list[str] = [] + for part in parts: + mark = len(notes) + result = solve(part, want, seen) + added = notes[mark:] + del notes[mark:] + if result: + if not conflicts_with_prior(result): + notes.extend(added) + return result + if fallback is None: + fallback, fallback_notes = result, added + else: + leftover_notes.extend(added) + if fallback is not None: + notes.extend(fallback_notes) + return fallback + notes.extend(leftover_notes) + return None + + def resolve_single_root(name: str) -> str | None: + """Return the sole settable input driving ``name``. + + Returns the root variable name, ``""`` when several inputs drive the value + (ambiguous), or ``None`` when no settable root exists. + """ + + roots = resolve_root_variables(name, settable, set(), assignment_sources) + assignable = [root for root in roots if root in settable] + if len(assignable) == 1: + return assignable[0] + if len(assignable) > 1: + note_once(f"MANUAL: ambiguous roots [{', '.join(assignable)}] for `{name}`.") + return "" + return None + + def solve_atom(expression: str, want: bool, seen: frozenset[str]) -> dict[str, TableValue] | None: + name, operator, literal = parse_condition_atom(expression) + if name is None: + note_once(f"MANUAL: could not parse condition `{expression.strip()}`.") + return None + if name in settable: + existing = prior.get(name) + if existing is not None and existing != "MANUAL" and value_satisfies_atom(existing, operator, literal, want): + return {name: existing} + if operator is None: + return {name: 1 if want else 0} + return {name: candidate_number(operator, literal, want)} + + is_truthiness = operator is None or (literal == 0 and operator in ("!=", "==")) + # Boolean intermediates tested for truthiness are expanded through their defining + # expression so every driving input is constrained. + if is_truthiness and name in expr_map and name not in seen: + inner_want = want if operator != "==" else not want + return solve(expr_map[name], inner_want, seen | {name}) + + # Otherwise (a comparison against a literal, or a truthiness test of a variable we + # cannot expand) fall back to single-root resolution: an arithmetic/aliased + # intermediate driven by exactly one input maps the comparison onto that input. + root = resolve_single_root(name) + if root == "": # ambiguous (multiple driving inputs) + return None + if root is None: + note_once(f"MANUAL: no settable input found for `{name}`.") + return None + if root != name and trace_chain is not None: + chain = trace_chain(name) + if len(chain) > 1: + note_once(f"`{name}` traced {' -> '.join(chain)}.") + resolve_op = operator or "!=" + resolve_literal = literal if literal is not None else 0 + return {root: candidate_number(resolve_op, resolve_literal, want)} + + def solve(expression: str, want: bool, seen: frozenset[str]) -> dict[str, TableValue] | None: + expr = clean_boolean_expression(expression) + while expr.startswith("!") and not expr.startswith("!="): + want = not want + expr = clean_boolean_expression(expr[1:]) + if not expr: + return None + + or_parts = split_top_level(expr, "||") + if len(or_parts) > 1: + # OR is true when any operand is true; false only when all are false. + return solve_any(or_parts, True, seen) if want else merge_all(or_parts, False, seen) + + and_parts = split_top_level(expr, "&&") + if len(and_parts) > 1: + # AND is true when all operands are true; false when any one is false. + return merge_all(and_parts, True, seen) if want else solve_any(and_parts, False, seen) + + return solve_atom(expr, want, seen) + + result = solve(condition, desired, frozenset()) + return (result or {}), notes + + +def concretize_conditions( + conditions: tuple[str, ...], + values: tuple[bool, ...], + solver_ctx: SolverContext | None = None, +) -> tuple[dict[str, TableValue], tuple[str, ...]]: assignments: dict[str, TableValue] = {} notes: list[str] = [] + def record(name: str, value: TableValue) -> None: + if name in assignments and assignments[name] != value: + notes.append(f"Conflicting inferred values for `{name}`; kept first value {assignments[name]!r}.") + return + assignments[name] = value + for condition, desired in zip(conditions, values): + if solver_ctx is not None: + settable, expr_map, assignment_sources = solver_ctx + solved, solve_notes = solve_condition_inputs( + condition, + desired, + settable=settable, + expr_map=expr_map, + assignment_sources=assignment_sources, + prior=assignments, + ) + if solved: + for name, value in solved.items(): + record(name, value) + continue + # Fall through to the simple per-condition inference below, preserving the + # previous behaviour for conditions the solver could not expand. + for note in solve_notes: + if note not in notes: + notes.append(note) + candidate = value_for_condition(condition, desired) if candidate is None: notes.append(f"No simple concrete value inferred for `{condition}` = {desired}.") diff --git a/tests/evaluation/SIL_SV_ATG_1 (1).xlsx b/tests/evaluation/SIL_SV_ATG_1 (1).xlsx new file mode 100644 index 0000000000000000000000000000000000000000..587a4b4ab7ba671abd7ed0a7e0ea0f25826e695e GIT binary patch literal 100585 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 literal 0 HcmV?d00001 diff --git a/tests/evaluation/SIL_SV_ATG_1_sample.xlsx b/tests/evaluation/SIL_SV_ATG_1_sample.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..7eba6cf3f523256adbb27a51e419815c5d34b101 GIT binary patch literal 18385 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 K{X~BJ^*;bOMOE_v literal 0 HcmV?d00001 diff --git a/tests/evaluation/ft-hfs_failinform-fh4-4.c b/tests/evaluation/ft-hfs_failinform-fh4-4.c new file mode 100644 index 0000000..d94d908 --- /dev/null +++ b/tests/evaluation/ft-hfs_failinform-fh4-4.c @@ -0,0 +1,809 @@ +/* CONFIDENTIAL */ +/*_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_*/ +/*_/_/_/ $DWG-No.:HFS_FAILINFORM-FH4 _/_/_/_*/ +/*_/_/_/ $Content:油圧回路デバイス故障・正常通知処理(2段LU油圧回路用) _/_/_/_*/ +/*_/_/_/ $Category:FT _/_/_/_*/ +/*_/_/_/ $Date:2024/07/18 _/_/_/_*/ +/*_/_/_/ $Design:高松 SCSK _/_/_/_*/ +/*_/_/_/ $Check: _/_/_/_*/ +/*_/_/_/ $Header: _/_/_/_*/ +/*_/_/_/ $Copyright(C) 2024 HONDA MOTOR CO., LTD. _/_/_/_*/ +/*_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_*/ + +/*###############################################################################################*/ +/*### $INCLUDE FILES$ ####*/ +/*###############################################################################################*/ + +#include "hos.h" +#include "def.h" +#include "hgsub.h" +#include "tl_basetypes.h" + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +/*+++ $RAM_EXTERN$ +++*/ +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +extern VS15 VS15tmpatfact_hf; +extern VFLG VFLGf_hydonfaildetinh_hf; +extern VFLG VFLGf_phydoffsta_tm; +extern VFLG VFLGf_psenofffaildetinh_hf; +extern VFLG VFLGf_psenonfaildetinh_hf; +extern VFLG VFLGf_psenonluhioff_hf; +extern VFLG VFLGf_psenonluoff_hf; +extern VFLG VFLGf_psenonluonrdy_hf; +extern VFLG VFLGf_shaofffaildetinh_hf; +extern VFLG VFLGf_shaonfaildetinh_hf; +extern VFLG VFLGf_shbofffaildetinh_hf; +extern VFLG VFLGf_shbonfaildetinh_hf; +extern VFLG VFLGf_shcofffaildetinh_hf; +extern VFLG VFLGf_shconfaildetinh_hf; +extern VU08 VU08xhydact_hf; +extern VU08 VU08xpsenelefailsta_hf; +extern VU08 VU08xpsenfailsta_hf; +extern VU08 VU08xshaelefailsta_hf; +extern VU08 VU08xshafailsta_hf; +extern VU08 VU08xshahissta_hf; +extern VU08 VU08xshbelefailsta_hf; +extern VU08 VU08xshbfailsta_hf; +extern VU08 VU08xshbhissta_hf; +extern VU08 VU08xshcelefailsta_hf; +extern VU08 VU08xshcfailsta_hf; +extern VU08 VU08xshchissta_hf; + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +/*+++ $RAM_PUBLIC$ +++*/ +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +#pragma section TM10M +VTIM VTIMthydoffok_hf; +#pragma section +#pragma section TM10M +VTIM VTIMtpsenoffok_hf; +#pragma section +#pragma section TM10M +VTIM VTIMtpsenoffokhydoff_hf; +#pragma section +#pragma section TM10M +VTIM VTIMtpsenoffokhydon_hf; +#pragma section +#pragma section TM10M +VTIM VTIMtpsenonluhi_hf; +#pragma section +#pragma section TM10M +VTIM VTIMtpsenonlulo_hf; +#pragma section +#pragma section TM10M +VTIM VTIMtshaoffok_hf; +#pragma section +#pragma section TM10M +VTIM VTIMtshaonok_hf; +#pragma section +#pragma section TM10M +VTIM VTIMtshboffok_hf; +#pragma section +#pragma section TM10M +VTIM VTIMtshbonok_hf; +#pragma section +#pragma section TM10M +VTIM VTIMtshcoffok_hf; +#pragma section +#pragma section TM10M +VTIM VTIMtshconok_hf; +#pragma section +VFLG VFLGf_hydfailfsapmt_tm; +VFLG VFLGf_hydoffok_hf; +VFLG VFLGf_hydoffok_tm; +VFLG VFLGf_hydonfail_tm; +VFLG VFLGf_phydcironok_tm; +VFLG VFLGf_psenofffail_tm; +VFLG VFLGf_psenoffok_tm; +VFLG VFLGf_psenonfail_tm; +VFLG VFLGf_shaofffail_tm; +VFLG VFLGf_shaoffok_tm; +VFLG VFLGf_shaonfail_tm; +VFLG VFLGf_shaonok_tm; +VFLG VFLGf_shboffchkok_hf; +VFLG VFLGf_shbofffail_tm; +VFLG VFLGf_shboffok_tm; +VFLG VFLGf_shbonfail_tm; +VFLG VFLGf_shbonok_tm; +VFLG VFLGf_shcoffchkok_hf; +VFLG VFLGf_shcofffail_tm; +VFLG VFLGf_shcoffok_tm; +VFLG VFLGf_shconfail_tm; +VFLG VFLGf_shconok_tm; + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +/*+++ $RAM_STATIC$ +++*/ +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +static VFLG X_Sa50_UnitDelay; +static VFLG X_Sa57_UnitDelay; +static VFLG X_Sa64_UnitDelay; +static VFLG X_Sa65_UnitDelay; +static VFLG X_Sa73_UnitDelay; +static VFLG X_Sa78_UnitDelay; +static VFLG X_Sa83_UnitDelay; +static VFLG X_Sa88_UnitDelay; +static VFLG X_Sa93_UnitDelay; +static VFLG X_Sa98_UnitDelay; + + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +/*+++ $DATA_EXTERN$ +++*/ +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +/*+++ $DATA_PUBLIC$ +++*/ +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +CS15 XS15tmp_thydoffok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CS15 XS15tmp_tplostbl[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CS15 XS15tmp_tpsenonok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CS15 XS15tmp_tshaoffok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CS15 XS15tmp_tshaonok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CS15 XS15tmp_tshboffok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CS15 XS15tmp_tshbonok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CS15 XS15tmp_tshcoffok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CS15 XS15tmp_tshconok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CU15 XU15thydoffok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CU15 XU15tplostbl[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CU15 XU15tpsenonok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CU15 XU15tshaoffok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CU15 XU15tshaonok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CU15 XU15tshboffok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CU15 XU15tshbonok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CU15 XU15tshcoffok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CU15 XU15tshconok[10] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + +}; +CU15 CU15tpsenoffokhydoff = 1; +CU15 CU15tpsenoffokhydon = 1; +TBL S_TBLthydoffok = +{ + 10, + SIZE_S15, + SIZE_U15, +(void *) XS15tmp_thydoffok, +(void *) XU15thydoffok +}; +TBL S_TBLtplostbl = +{ + 10, + SIZE_S15, + SIZE_U15, +(void *) XS15tmp_tplostbl, +(void *) XU15tplostbl +}; +TBL S_TBLtpsenonok = +{ + 10, + SIZE_S15, + SIZE_U15, +(void *) XS15tmp_tpsenonok, +(void *) XU15tpsenonok +}; +TBL S_TBLtshaoffok = +{ + 10, + SIZE_S15, + SIZE_U15, +(void *) XS15tmp_tshaoffok, +(void *) XU15tshaoffok +}; +TBL S_TBLtshaonok = +{ + 10, + SIZE_S15, + SIZE_U15, +(void *) XS15tmp_tshaonok, +(void *) XU15tshaonok +}; +TBL S_TBLtshboffok = +{ + 10, + SIZE_S15, + SIZE_U15, +(void *) XS15tmp_tshboffok, +(void *) XU15tshboffok +}; +TBL S_TBLtshbonok = +{ + 10, + SIZE_S15, + SIZE_U15, +(void *) XS15tmp_tshbonok, +(void *) XU15tshbonok +}; +TBL S_TBLtshcoffok = +{ + 10, + SIZE_S15, + SIZE_U15, +(void *) XS15tmp_tshcoffok, +(void *) XU15tshcoffok +}; +TBL S_TBLtshconok = +{ + 10, + SIZE_S15, + SIZE_U15, +(void *) XS15tmp_tshconok, +(void *) XU15tshconok +}; + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +/*+++ $DATA_STATIC$ +++*/ +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ + +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +/*+++ $FUNCTION PROTOTYPE$ +++*/ +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +void J_hfs_failinform(void); +void JINIT_hfs_failinform(void); +void JDTCCLR_hfs_failinform(void); + +/*************************************************************************************************/ +/*************************************************************************************************/ +/**** $Function:hfs_failinform ****/ +/**** $Content:油圧回路デバイス故障・正常を通知する ****/ +/**** ****/ +/**** $argument: なし ****/ +/**** $return value: なし ****/ +/*************************************************************************************************/ +/*************************************************************************************************/ +void J_hfs_failinform(void) +{ + + Int16 Sa11_TSHAOFFOK; + Int16 Sa13_TSHAONOK; + Int16 Sa15_TSHBOFFOK; + Int16 Sa17_TSHBONOK; + Int16 Sa19_TSHCOFFOK; + Int16 Sa21_TSHCONOK; + Int16 Sa4_THYDOFFOK; + Int16 Sa61_out___Non__old___immediate; + Int16 Sa62_out___Non__old___immediate; + Int16 Sa7_TPLOSTBL; + Int16 Sa9_TPSENONOK; + + FLG Sa11_LogicalOperator2; + FLG Sa13_LogicalOperator; + FLG Sa15_LogicalOperator2; + FLG Sa15_RelationalOperator2; + FLG Sa17_LogicalOperator; + FLG Sa19_LogicalOperator; + FLG Sa21_LogicalOperator; + FLG Sa4_LogicalOperator2; + FLG Sa60_LogicalOperator; + FLG Sa7_LogicalOperator2; + FLG Sa7_LogicalOperator3; + FLG Sa7_LogicalOperator5; + FLG Sa86_LogicalOperator; + FLG Sa9_LogicalOperator1; + FLG Sa9_LogicalOperator3; + + VFLGf_hydonfail_tm = (Int8) (((!(VFLGf_hydonfaildetinh_hf != 0)) && ((VFLGf_psenonluhioff_hf != 0) || (VFLGf_psenonluonrdy_hf != 0) || (VFLGf_psenonluoff_hf != 0))) || (VFLGf_hydonfail_tm != 0)); + + Sa4_THYDOFFOK = (Int16) GE_Ipl_tbl(&S_TBLthydoffok, (Int32) VS15tmpatfact_hf); + + Sa4_LogicalOperator2 = (Int8) (((VU08xshbhissta_hf == 10) || (VU08xshchissta_hf == 10)) && (!(VFLGf_hydonfaildetinh_hf != 0))); + + if (Sa4_LogicalOperator2 != 0) { + + if (!(X_Sa50_UnitDelay != 0)) { + + VTIMthydoffok_hf = Sa4_THYDOFFOK; + } + } + else { + + VTIMthydoffok_hf = Sa4_THYDOFFOK; + } + + X_Sa50_UnitDelay = 1; + + if (VFLGf_hydonfail_tm != 0) { + + VFLGf_hydoffok_tm = 0; + } + else { + + if ((Sa4_LogicalOperator2 != 0) && (VTIMthydoffok_hf <= 0)) { + + VFLGf_hydoffok_tm = 1; + } + else { + + VFLGf_hydoffok_tm = VFLGf_hydoffok_hf; + } + } + + VFLGf_hydoffok_hf = VFLGf_hydoffok_tm; + + Sa9_TPSENONOK = (Int16) GE_Ipl_tbl(&S_TBLtpsenonok, (Int32) VS15tmpatfact_hf); + + Sa9_LogicalOperator1 = (Int8) ((VU08xhydact_hf == 20) && (!(VFLGf_psenofffaildetinh_hf != 0))); + + if (Sa9_LogicalOperator1 != 0) { + + if (!(X_Sa65_UnitDelay != 0)) { + + VTIMtpsenonlulo_hf = Sa9_TPSENONOK; + } + } + else { + + VTIMtpsenonlulo_hf = Sa9_TPSENONOK; + } + + X_Sa65_UnitDelay = 1; + + Sa7_TPLOSTBL = (Int16) GE_Ipl_tbl(&S_TBLtplostbl, (Int32) VS15tmpatfact_hf); + + Sa7_LogicalOperator5 = (Int8) ((VU08xhydact_hf == 0) && (!(VFLGf_psenonfaildetinh_hf != 0))); + + if (Sa7_LogicalOperator5 != 0) { + + if (!(X_Sa57_UnitDelay != 0)) { + + VTIMtpsenoffok_hf = Sa7_TPLOSTBL; + } + } + else { + + VTIMtpsenoffok_hf = Sa7_TPLOSTBL; + } + + X_Sa57_UnitDelay = 1; + + Sa60_LogicalOperator = (Int8) ((Sa7_LogicalOperator5 != 0) && (VTIMtpsenoffok_hf <= 0)); + + Sa7_LogicalOperator2 = (Int8) ((Sa60_LogicalOperator != 0) && (VFLGf_phydoffsta_tm != 0)); + + if (Sa7_LogicalOperator2 != 0) { + + Sa61_out___Non__old___immediate = VTIMtpsenoffokhydoff_hf; + } + else { + + Sa61_out___Non__old___immediate = CU15tpsenoffokhydoff; + } + + VTIMtpsenoffokhydoff_hf = Sa61_out___Non__old___immediate; + + Sa7_LogicalOperator3 = (Int8) ((Sa60_LogicalOperator != 0) && (!(VFLGf_phydoffsta_tm != 0))); + + if (Sa7_LogicalOperator3 != 0) { + + Sa62_out___Non__old___immediate = VTIMtpsenoffokhydon_hf; + } + else { + + Sa62_out___Non__old___immediate = CU15tpsenoffokhydon; + } + + VTIMtpsenoffokhydon_hf = Sa62_out___Non__old___immediate; + + if (!(VFLGf_shcofffaildetinh_hf != 0)) { + + VFLGf_shcofffail_tm = (Int8) (VU08xshcfailsta_hf == 240); + } + + if (!(VFLGf_shaofffaildetinh_hf != 0)) { + + VFLGf_shaofffail_tm = (Int8) (VU08xshafailsta_hf == 240); + } + + Sa9_LogicalOperator3 = (Int8) ((VU08xhydact_hf == 30) && (!(VFLGf_psenofffaildetinh_hf != 0))); + + if (Sa9_LogicalOperator3 != 0) { + + if (!(X_Sa64_UnitDelay != 0)) { + + VTIMtpsenonluhi_hf = Sa9_TPSENONOK; + } + } + else { + + VTIMtpsenonluhi_hf = Sa9_TPSENONOK; + } + + X_Sa64_UnitDelay = 1; + + if (!(VFLGf_psenofffaildetinh_hf != 0)) { + + VFLGf_psenofffail_tm = (Int8) (VU08xpsenfailsta_hf == 240); + } + + if (VFLGf_psenofffail_tm != 0) { + + VFLGf_phydcironok_tm = 0; + } + else { + + if (((Sa9_LogicalOperator3 != 0) && (VTIMtpsenonluhi_hf <= 0)) || ((Sa9_LogicalOperator1 != 0) && (VTIMtpsenonlulo_hf <= 0))) { + + VFLGf_phydcironok_tm = 1; + } + } + + if (!(VFLGf_psenonfaildetinh_hf != 0)) { + + VFLGf_psenonfail_tm = (Int8) (VU08xpsenfailsta_hf == 230); + } + + if (VFLGf_psenonfail_tm != 0) { + + VFLGf_psenoffok_tm = 0; + } + else { + + if (((Sa7_LogicalOperator2 != 0) && (Sa61_out___Non__old___immediate <= 0)) || ((Sa7_LogicalOperator3 != 0) && (Sa62_out___Non__old___immediate <= 0))) { + + VFLGf_psenoffok_tm = 1; + } + } + + Sa21_TSHCONOK = (Int16) GE_Ipl_tbl(&S_TBLtshconok, (Int32) VS15tmpatfact_hf); + + Sa21_LogicalOperator = (Int8) ((VU08xshchissta_hf == 20) && (!(VFLGf_shcofffaildetinh_hf != 0))); + + if (Sa21_LogicalOperator != 0) { + + if (!(X_Sa98_UnitDelay != 0)) { + + VTIMtshconok_hf = Sa21_TSHCONOK; + } + } + else { + + VTIMtshconok_hf = Sa21_TSHCONOK; + } + + X_Sa98_UnitDelay = 1; + + if (VFLGf_shcofffail_tm != 0) { + + VFLGf_shconok_tm = 0; + } + else { + + if ((Sa21_LogicalOperator != 0) && (VTIMtshconok_hf <= 0)) { + + VFLGf_shconok_tm = 1; + } + } + + Sa19_TSHCOFFOK = (Int16) GE_Ipl_tbl(&S_TBLtshcoffok, (Int32) VS15tmpatfact_hf); + + Sa19_LogicalOperator = (Int8) ((VU08xshchissta_hf == 10) && (!(VFLGf_shconfaildetinh_hf != 0))); + + if (Sa19_LogicalOperator != 0) { + + if (!(X_Sa93_UnitDelay != 0)) { + + VTIMtshcoffok_hf = Sa19_TSHCOFFOK; + } + } + else { + + VTIMtshcoffok_hf = Sa19_TSHCOFFOK; + } + + X_Sa93_UnitDelay = 1; + + if (!(VFLGf_shconfaildetinh_hf != 0)) { + + VFLGf_shconfail_tm = (Int8) (VU08xshcfailsta_hf == 230); + } + + VFLGf_shcoffchkok_hf = (Int8) ((Sa19_LogicalOperator != 0) && (VTIMtshcoffok_hf <= 0)); + + if (VFLGf_shconfail_tm != 0) { + + VFLGf_shcoffok_tm = 0; + } + else { + + if (VFLGf_shcoffchkok_hf != 0) { + + VFLGf_shcoffok_tm = 1; + } + } + + Sa17_TSHBONOK = (Int16) GE_Ipl_tbl(&S_TBLtshbonok, (Int32) VS15tmpatfact_hf); + + Sa17_LogicalOperator = (Int8) ((VU08xshbhissta_hf == 20) && (!(VFLGf_shbofffaildetinh_hf != 0))); + + if (Sa17_LogicalOperator != 0) { + + if (!(X_Sa88_UnitDelay != 0)) { + + VTIMtshbonok_hf = Sa17_TSHBONOK; + } + } + else { + + VTIMtshbonok_hf = Sa17_TSHBONOK; + } + + X_Sa88_UnitDelay = 1; + + if (!(VFLGf_shbofffaildetinh_hf != 0)) { + + VFLGf_shbofffail_tm = (Int8) (VU08xshbfailsta_hf == 240); + } + + if (VFLGf_shbofffail_tm != 0) { + + VFLGf_shbonok_tm = 0; + } + else { + + if ((Sa17_LogicalOperator != 0) && (VTIMtshbonok_hf <= 0)) { + + VFLGf_shbonok_tm = 1; + } + } + + Sa15_TSHBOFFOK = (Int16) GE_Ipl_tbl(&S_TBLtshboffok, (Int32) VS15tmpatfact_hf); + + Sa15_RelationalOperator2 = (Int8) (VU08xshbhissta_hf == 10); + + Sa15_LogicalOperator2 = (Int8) (((Sa15_RelationalOperator2 != 0) || (VU08xshbhissta_hf == 20)) && (!(VFLGf_shbonfaildetinh_hf != 0))); + + if (Sa15_LogicalOperator2 != 0) { + + if (!(X_Sa83_UnitDelay != 0)) { + + VTIMtshboffok_hf = Sa15_TSHBOFFOK; + } + } + else { + + VTIMtshboffok_hf = Sa15_TSHBOFFOK; + } + + X_Sa83_UnitDelay = 1; + + if (!(VFLGf_shbonfaildetinh_hf != 0)) { + + VFLGf_shbonfail_tm = (Int8) (VU08xshbfailsta_hf == 230); + } + + Sa86_LogicalOperator = (Int8) ((Sa15_LogicalOperator2 != 0) && (VTIMtshboffok_hf <= 0)); + + if (VFLGf_shbonfail_tm != 0) { + + VFLGf_shboffok_tm = 0; + } + else { + + if (Sa86_LogicalOperator != 0) { + + VFLGf_shboffok_tm = 1; + } + } + + Sa13_TSHAONOK = (Int16) GE_Ipl_tbl(&S_TBLtshaonok, (Int32) VS15tmpatfact_hf); + + Sa13_LogicalOperator = (Int8) ((VU08xshahissta_hf == 20) && (!(VFLGf_shaofffaildetinh_hf != 0))); + + if (Sa13_LogicalOperator != 0) { + + if (!(X_Sa78_UnitDelay != 0)) { + + VTIMtshaonok_hf = Sa13_TSHAONOK; + } + } + else { + + VTIMtshaonok_hf = Sa13_TSHAONOK; + } + + X_Sa78_UnitDelay = 1; + + if (VFLGf_shaofffail_tm != 0) { + + VFLGf_shaonok_tm = 0; + } + else { + + if ((Sa13_LogicalOperator != 0) && (VTIMtshaonok_hf <= 0)) { + + VFLGf_shaonok_tm = 1; + } + } + + Sa11_TSHAOFFOK = (Int16) GE_Ipl_tbl(&S_TBLtshaoffok, (Int32) VS15tmpatfact_hf); + + Sa11_LogicalOperator2 = (Int8) (((VU08xshahissta_hf == 10) || (VU08xshahissta_hf == 20)) && (!(VFLGf_shaonfaildetinh_hf != 0))); + + if (Sa11_LogicalOperator2 != 0) { + + if (!(X_Sa73_UnitDelay != 0)) { + + VTIMtshaoffok_hf = Sa11_TSHAOFFOK; + } + } + else { + + VTIMtshaoffok_hf = Sa11_TSHAOFFOK; + } + + X_Sa73_UnitDelay = 1; + + if (!(VFLGf_shaonfaildetinh_hf != 0)) { + + VFLGf_shaonfail_tm = (Int8) (VU08xshafailsta_hf == 230); + } + + if (VFLGf_shaonfail_tm != 0) { + + VFLGf_shaoffok_tm = 0; + } + else { + + if ((Sa11_LogicalOperator2 != 0) && (VTIMtshaoffok_hf <= 0)) { + + VFLGf_shaoffok_tm = 1; + } + } + + VFLGf_hydfailfsapmt_tm = (Int8) ((VFLGf_shaonfail_tm != 0) || (VFLGf_shaofffail_tm != 0) || (VFLGf_shbonfail_tm != 0) || (VFLGf_shbofffail_tm != 0) || (VFLGf_shconfail_tm != 0) || + (VFLGf_shcofffail_tm != 0) || (VFLGf_psenonfail_tm != 0) || (VFLGf_psenofffail_tm != 0) || (VFLGf_hydonfail_tm != 0) || ((VU08xshaelefailsta_hf == 240) || (VU08xshaelefailsta_hf == 230) || + (VU08xshbelefailsta_hf == 240) || (VU08xshbelefailsta_hf == 230) || (VU08xshcelefailsta_hf == 240) || (VU08xshcelefailsta_hf == 230) || (VU08xpsenelefailsta_hf == 240) || (VU08xpsenelefailsta_hf + == 230))); + + VFLGf_shboffchkok_hf = (Int8) ((Sa15_RelationalOperator2 != 0) && (Sa86_LogicalOperator != 0)); +} + +/*************************************************************************************************/ +/**** $Function: hfs_failinform(イニシャル処理) ****/ +/**** $Content: イニシャル処理 ****/ +/*************************************************************************************************/ +void JINIT_hfs_failinform(void) +{ + VTIMtshconok_hf = 0x0064; + VTIMthydoffok_hf = 0x0064; + VTIMtpsenoffok_hf = 0x0064; + VTIMtshcoffok_hf = 0x0064; + VTIMtpsenonluhi_hf = 0x0064; + VTIMtshaonok_hf = 0x0064; + VTIMtshboffok_hf = 0x0064; + VTIMtshaoffok_hf = 0x0064; + VTIMtshbonok_hf = 0x0064; + VTIMtpsenonlulo_hf = 0x0064; + VTIMtpsenoffokhydon_hf = CU15tpsenoffokhydon; + VTIMtpsenoffokhydoff_hf = CU15tpsenoffokhydoff; +} +/*************************************************************************************************/ +/**** $Function: hfs_failinform(テスタA処理) ****/ +/**** $Content: DTC クリア処理 ****/ +/*************************************************************************************************/ +void JDTCCLR_hfs_failinform(void) +{ + VTIMtshconok_hf = 0x0064; + VTIMthydoffok_hf = 0x0064; + VFLGf_psenonfail_tm = 0x0; + VTIMtpsenoffok_hf = 0x0064; + VFLGf_shbofffail_tm = 0x0; + VFLGf_shcoffok_tm = 0x0; + VTIMtshcoffok_hf = 0x0064; + VTIMtpsenonluhi_hf = 0x0064; + VFLGf_shbonfail_tm = 0x0; + VTIMtshaonok_hf = 0x0064; + VTIMtshboffok_hf = 0x0064; + VFLGf_phydcironok_tm = 0x0; + VFLGf_shbonok_tm = 0x0; + VTIMtshbonok_hf = 0x0064; + VFLGf_shboffok_tm = 0x0; + VFLGf_shaofffail_tm = 0x0; + VFLGf_psenoffok_tm = 0x0; + VFLGf_shconok_tm = 0x0; + VFLGf_hydonfail_tm = 0x0; + VFLGf_shaoffok_tm = 0x0; + VFLGf_shcofffail_tm = 0x0; + VFLGf_psenofffail_tm = 0x0; + VFLGf_shaonfail_tm = 0x0; + VFLGf_hydoffok_hf = 0x0; + VFLGf_shconfail_tm = 0x0; + VFLGf_shaonok_tm = 0x0; + VTIMtshaoffok_hf = 0x0064; + VTIMtpsenonlulo_hf = 0x0064; + VTIMtpsenoffokhydon_hf = CU15tpsenoffokhydon; + VTIMtpsenoffokhydoff_hf = CU15tpsenoffokhydoff; +} + + + + + +/*-----------------------------------------------------------------------------------------------*/ +/*-----------------------------------------------------------------------------------------------*/ +/*--- REVISION MANAGEMENT ---*/ +/*-----------------------------------------------------------------------------------------------*/ +/*-----------------------------------------------------------------------------------------------*/ + +/*-----------------------------------------------------------------------------------------------*/ +/*--- ---*/ +/*--- $Rev: date: name: company: minute: content: ---*/ +/*--- $Rev:40 2026/03/06 ManhVo FS EAGLE4.1p3 TL4.3p5 ---*/ +/*--- ---*/ +/*-----------------------------------------------------------------------------------------------*/ +/*--- + $Log$ + ---*/ +/*-----------------------------------------------------------------------------------------------*/ + + diff --git a/tests/integration/test_fastapi_app.py b/tests/integration/test_fastapi_app.py index bb573dd..185df7f 100644 --- a/tests/integration/test_fastapi_app.py +++ b/tests/integration/test_fastapi_app.py @@ -1,5 +1,6 @@ import base64 from io import BytesIO +from pathlib import Path from zipfile import ZipFile from fastapi.testclient import TestClient @@ -23,6 +24,8 @@ def test_web_app_exposes_table_and_excel_controls() -> None: assert response.status_code == 200 assert "Testcase_table" in response.text + assert 'name="support_template"' in response.text + assert "Support template (Excel)" in response.text assert "Export Excel" in response.text assert "Export CSV" in response.text assert "Ccode_interface" in response.text @@ -119,6 +122,57 @@ def test_web_app_generates_mcdc_artifacts() -> None: assert "mcdc_testcases.xlsx" in payload["downloads"] +def test_web_app_generate_follows_support_template() -> None: + client = TestClient(app) + + source_bytes = Path("tests/evaluation/ft-hfs_failinform-fh4-4.c").read_bytes() + template_bytes = Path("tests/evaluation/SIL_SV_ATG_1_sample.xlsx").read_bytes() + + response = client.post( + "/api/generate", + data={"target_function": "J_hfs_failinform", "mcdc_mode": "masking"}, + files={ + "source": ("ft.c", source_bytes, "text/x-c"), + "support_template": ( + "template.xlsx", + template_bytes, + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ), + }, + ) + + payload = response.json() + assert response.status_code == 200 + table = payload["report"]["testcase_table"] + # Columns follow the template exactly. + assert table["input_columns"][0] == "VS15tmpatfact_hf" + assert len(table["input_columns"]) == 26 + assert table["parameter_columns"] == ["CU15tpsenoffokhydoff", "CU15tpsenoffokhydon"] + assert len(table["output_columns"]) == 34 + assert not any( + name.startswith(("XS15", "XU15", "S_TBL")) for name in table["parameter_columns"] + ) + # Template metadata supplies the default Excel name. + assert payload["excel_filename"] == "SIL_SV_ATG_1.xlsx" + assert "SIL_SV_ATG_1.xlsx" in payload["downloads"] + + +def test_web_app_rejects_non_xlsx_support_template() -> None: + client = TestClient(app) + + response = client.post( + "/api/generate", + data={"target_function": "logic"}, + files={ + "source": ("logic.c", b"int logic(int a){ if (a > 0) return 1; return 0; }", "text/x-c"), + "support_template": ("template.txt", b"not an excel file", "text/plain"), + }, + ) + + assert response.status_code == 400 + assert "xlsx" in response.json()["detail"].lower() + + def test_web_app_exports_excel_with_user_metadata() -> None: client = TestClient(app) diff --git a/tests/unit/test_mcdc_generator.py b/tests/unit/test_mcdc_generator.py index f585066..7c69845 100644 --- a/tests/unit/test_mcdc_generator.py +++ b/tests/unit/test_mcdc_generator.py @@ -11,7 +11,10 @@ extract_function_parameters, find_tool_path, generate_mcdc_report, + parse_support_template, + read_xlsx_grid, summarize_coverage_readiness, + testcase_table as build_testcase_table, testcase_table_rows as build_testcase_table_rows, testcase_table_rows_from_dict as build_testcase_table_rows_from_dict, testcase_table_rows_to_csv as build_testcase_table_rows_csv, @@ -733,3 +736,243 @@ def test_finds_llvm_tool_from_configured_bin_directory(tmp_path: Path, monkeypat monkeypatch.setenv("PATH", "") assert find_tool_path("clang") == str(tool) + + +def _rows_for_decision(table: dict, decision_id: str) -> dict[bool, dict]: + """Return {decision_result: row} for the rows of a single decision.""" + return { + bool(row["decision_result"]): row + for row in table["rows"] + if row["decision_id"] == decision_id + } + + +def test_solver_expands_intermediate_and_to_root_inputs(tmp_path: Path) -> None: + # A decision that tests an intermediate variable must be traced back to the real + # external inputs that drive it, with the literal value needed to satisfy it. + source = tmp_path / "intermediate_and.c" + source.write_text( + """ +/*+++ $RAM_EXTERN$ +++*/ +extern UInt8 mode; +extern UInt8 enable; + +/*+++ $RAM_PUBLIC$ +++*/ +UInt8 output_flag; + +void f(void) +{ + UInt8 tmp; + tmp = (mode == 20) && (enable != 0); + if (tmp != 0) { + output_flag = 1; + } +} +""", + encoding="utf-8", + ) + + report = generate_mcdc_report(source, target_function="f", mcdc_mode="masking") + table = report.to_dict()["testcase_table"] + + assert "mode" in table["input_columns"] + assert "enable" in table["input_columns"] + assert "tmp" not in table["input_columns"] + + rows = _rows_for_decision(table, "D1") + # True case satisfies the AND: mode must equal 20 and enable must be truthy. + assert rows[True]["inputs"]["mode"] == 20 + assert rows[True]["inputs"]["enable"] == 1 + # False case breaks the AND via the first operand (mode != 20). + assert rows[False]["inputs"]["mode"] == 21 + + +def test_solver_expands_intermediate_or_of_equalities(tmp_path: Path) -> None: + source = tmp_path / "intermediate_or.c" + source.write_text( + """ +/*+++ $RAM_EXTERN$ +++*/ +extern UInt8 a; +extern UInt8 b; + +/*+++ $RAM_PUBLIC$ +++*/ +UInt8 output_flag; + +void f(void) +{ + UInt8 tmp; + tmp = (a == 10) || (b == 10); + if (tmp != 0) { + output_flag = 1; + } +} +""", + encoding="utf-8", + ) + + report = generate_mcdc_report(source, target_function="f", mcdc_mode="masking") + table = report.to_dict()["testcase_table"] + + rows = _rows_for_decision(table, "D1") + # True case satisfies the OR by setting one operand to its matching value. + assert rows[True]["inputs"]["a"] == 10 + # False case requires every operand to miss its target value. + assert rows[False]["inputs"]["a"] == 11 + assert rows[False]["inputs"]["b"] == 11 + + +def test_solver_sets_feedback_state_variable_directly(tmp_path: Path) -> None: + # A state/feedback variable that is read in a decision and written later is set + # directly rather than expanded (its value depends on the previous cycle). + source = tmp_path / "feedback.c" + source.write_text( + """ +/*+++ $RAM_EXTERN$ +++*/ +extern UInt8 trigger; + +/*+++ $RAM_PUBLIC$ +++*/ +UInt8 state_flag; +UInt8 output_flag; + +void f(void) +{ + if (state_flag != 0) { + output_flag = 1; + } + state_flag = trigger; +} +""", + encoding="utf-8", + ) + + report = generate_mcdc_report(source, target_function="f", mcdc_mode="masking") + table = report.to_dict()["testcase_table"] + + assert "state_flag" in table["input_columns"] + rows = _rows_for_decision(table, "D1") + assert rows[True]["inputs"]["state_flag"] == 1 + assert rows[False]["inputs"]["state_flag"] == 0 + + +def test_solver_keeps_shared_input_consistent_across_conditions(tmp_path: Path) -> None: + # Two derived conditions in one decision share VU08xmode; a single value (30) makes + # the first true and the second false. The solver must not clobber that value with the + # second condition's off-value (21), which would break the first. + source = tmp_path / "shared_input.c" + source.write_text( + """ +/*+++ $RAM_EXTERN$ +++*/ +extern UInt8 VU08xmode; +extern UInt8 inhibit; + +/*+++ $RAM_PUBLIC$ +++*/ +UInt8 output_flag; + +void f(void) +{ + UInt8 hi; + UInt8 lo; + hi = (VU08xmode == 30) && (!(inhibit != 0)); + lo = (VU08xmode == 20) && (!(inhibit != 0)); + if ((hi != 0) || (lo != 0)) { + output_flag = 1; + } +} +""", + encoding="utf-8", + ) + + report = generate_mcdc_report(source, target_function="f", mcdc_mode="masking") + table = report.to_dict()["testcase_table"] + decision = next(d for d in report.to_dict()["decisions"] if d["id"] == "D1") + hi_index = decision["conditions"].index("hi != 0") + lo_index = decision["conditions"].index("lo != 0") + + rows = [row for row in table["rows"] if row["decision_id"] == "D1"] + assert rows + for row in rows: + wants = row["mcdc_condition_values"] + mode = row["inputs"]["VU08xmode"] + # Whenever a condition is intended true, the shared input must carry the value + # that actually makes it true (and not be overwritten by the other condition). + if wants[hi_index]: + assert mode == 30 + if wants[lo_index]: + assert mode == 20 + + +SAMPLE_TEMPLATE = Path("tests/evaluation/SIL_SV_ATG_1_sample.xlsx") +SAMPLE_SOURCE = Path("tests/evaluation/ft-hfs_failinform-fh4-4.c") + + +def test_parse_support_template_reads_columns_and_metadata() -> None: + override, metadata = parse_support_template(SAMPLE_TEMPLATE) + + assert override.input_names[0] == "VS15tmpatfact_hf" + assert len(override.input_names) == 26 + assert "VU08xshbhissta_hf" in override.input_names + assert override.parameter_names == ("CU15tpsenoffokhydoff", "CU15tpsenoffokhydon") + assert override.output_names[0] == "VTIMthydoffok_hf" + assert len(override.output_names) == 34 + assert override.array_sizes == {} + assert metadata.name == "SIL_SV_ATG_1" + assert metadata.scope == "HFS_FAILINFORM_FH4.c:1:HFS_FAILINFORM_FH4" + assert metadata.format_version == "1.3" + + +def test_parse_support_template_collapses_repeated_array_columns(tmp_path: Path) -> None: + template = tmp_path / "template.xlsx" + group_row = ["Mode", "Inputs", "Inputs", "Inputs", "Parameters", "Outputs"] + name_row = ["Step", "scalar_in", "arr", "arr", "gain", "result"] + write_testcase_workbook_rows( + [group_row, name_row], + template, + ExcelExportMetadata(name="CraftedTemplate", architecture="ARCH", scope="SCOPE"), + ) + + override, metadata = parse_support_template(template) + + assert override.input_names == ("scalar_in", "arr") + assert override.array_sizes == {"arr": 2} + assert override.parameter_names == ("gain",) + assert override.output_names == ("result",) + assert metadata.name == "CraftedTemplate" + assert metadata.architecture == "ARCH" + + +def test_read_xlsx_grid_round_trips_written_workbook(tmp_path: Path) -> None: + workbook = tmp_path / "grid.xlsx" + write_testcase_workbook_rows( + [["Mode", "Inputs"], ["Step", "x"], [0, 7]], + workbook, + ExcelExportMetadata(name="Grid"), + ) + + grid = read_xlsx_grid(workbook) + + assert grid[0][0] == "Format Version" + assert any(row and row[0] == "Step" for row in grid) + + +def test_interface_override_drives_generated_columns_and_values() -> None: + override, _ = parse_support_template(SAMPLE_TEMPLATE) + + report = generate_mcdc_report( + SAMPLE_SOURCE, + target_function="J_hfs_failinform", + interface_override=override, + ) + table = build_testcase_table(report) + + # The template is authoritative: columns match its layout exactly. + assert list(table["input_columns"]) == list(override.input_names) + assert list(table["parameter_columns"]) == list(override.parameter_names) + assert list(table["output_columns"]) == list(override.output_names) + # No auto-detected array parameters leak in. + assert not any( + name.startswith(("XS15", "XU15", "S_TBL")) for name in table["parameter_columns"] + ) + # The solver still drives real inputs, and parameters carry their C initial value. + d1 = _rows_for_decision(table, "D1") + assert d1[True]["inputs"]["VU08xshbhissta_hf"] == 10 + assert d1[True]["parameters"]["CU15tpsenoffokhydoff"] == 1