S03-H3 CLOSED baseline
This commit is contained in:
+267
@@ -0,0 +1,267 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fail-closed, profile-aware verifier for S03-H1 runtime configurations."""
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
EXPECTED_PROFILE_HASHES = {
|
||||
"TEST": "d037561b32760fbd0e2aec9f1eefc5efda27b1918eca4742c89092a8a2f7e92e",
|
||||
"DIAGNOSTIC": "cd999ff249452298d650a22c8fe5b0b03bf48c0735f4630befdec8209cbb13a3",
|
||||
}
|
||||
EXPECTED_DISPLAY_CLASSES = {
|
||||
"TEST": "GAMDataSource",
|
||||
"DIAGNOSTIC": "LoggerDataSource",
|
||||
}
|
||||
EXPECTED_GAMDISPLAY_BLOCK_SHA256 = (
|
||||
"13b14b504dac43df5febf230cd069c5093d951726382b1d5dee8edf43d6814d0"
|
||||
)
|
||||
EXPECTED_IOGAM_WRITER_BLOCK_SHA256 = (
|
||||
"b3d54ad2fae08e6640bfa0be39ead9841906b4573a3b407e5ecf21340b5c5717"
|
||||
)
|
||||
EXPECTED_FILEWRITER_BLOCK_SHA256 = (
|
||||
"0bb949b9d72fa758bdfd8b2039336bc1fda071ecf038304402032de63f4fc455"
|
||||
)
|
||||
PLACEHOLDER_LINE = ' Filename = "__MARTE_RUN_DIR__/s03_output.bin"'
|
||||
|
||||
|
||||
def digest(text):
|
||||
return hashlib.sha256(text.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def extract_block(text, start_marker, end_marker):
|
||||
if text.count(start_marker) != 1:
|
||||
raise ValueError(f"START_MARKER_COUNT:{start_marker!r}:{text.count(start_marker)}")
|
||||
start = text.index(start_marker)
|
||||
try:
|
||||
end = text.index(end_marker, start)
|
||||
except ValueError as exc:
|
||||
raise ValueError(f"END_MARKER_MISSING:{end_marker!r}") from exc
|
||||
return text[start:end]
|
||||
|
||||
|
||||
class Verification:
|
||||
def __init__(self):
|
||||
self.checks = {}
|
||||
self.first_error = "NONE"
|
||||
|
||||
def check(self, name, condition, error_code):
|
||||
self.checks[name] = "PASS" if condition else "FAIL"
|
||||
if not condition and self.first_error == "NONE":
|
||||
self.first_error = error_code
|
||||
|
||||
def emit(self):
|
||||
for name in sorted(self.checks):
|
||||
print(f"{name}={self.checks[name]}")
|
||||
print(f"PROFILE_ERROR_CODE={self.first_error}")
|
||||
|
||||
|
||||
def verify(mode, config_path, expected_filename):
|
||||
report = Verification()
|
||||
try:
|
||||
text = config_path.read_text(encoding="utf-8")
|
||||
except OSError as exc:
|
||||
print("CONFIG_READ_STATUS=FAIL")
|
||||
print(f"CONFIG_READ_ERROR={exc}")
|
||||
print("PROFILE_ERROR_CODE=CONFIG_READ_FAILED")
|
||||
print("CONFIG_PROFILE_VERIFICATION_STATUS=FAIL")
|
||||
return 1
|
||||
|
||||
expected_line = f' Filename = "{expected_filename}"'
|
||||
filename_count = sum(1 for line in text.splitlines() if line == expected_line)
|
||||
placeholder_count = text.count("__MARTE_RUN_DIR__")
|
||||
|
||||
canonical = text
|
||||
if filename_count == 1:
|
||||
canonical = text.replace(expected_line, PLACEHOLDER_LINE, 1)
|
||||
|
||||
canonical_hash = digest(canonical)
|
||||
expected_profile_hash = EXPECTED_PROFILE_HASHES[mode]
|
||||
expected_display_class = EXPECTED_DISPLAY_CLASSES[mode]
|
||||
|
||||
print(f"VERIFIED_MODE={mode}")
|
||||
print(f"EXPECTED_FILENAME={expected_filename}")
|
||||
print(f"EXPECTED_FILENAME_COUNT={filename_count}")
|
||||
print(f"PLACEHOLDER_COUNT={placeholder_count}")
|
||||
print(f"CANONICAL_CONFIG_SHA256={canonical_hash}")
|
||||
print(f"EXPECTED_PROFILE_CONFIG_SHA256={expected_profile_hash}")
|
||||
|
||||
report.check(
|
||||
"RUNTIME_FILENAME_STATUS",
|
||||
filename_count == 1 and placeholder_count == 0,
|
||||
"RUNTIME_FILENAME_MISMATCH",
|
||||
)
|
||||
report.check(
|
||||
"GAMDISPLAY_OBJECT_STATUS",
|
||||
text.count("+GAMDisplay =") == 1,
|
||||
"GAMDISPLAY_OBJECT_COUNT_MISMATCH",
|
||||
)
|
||||
report.check(
|
||||
"THREAD_ORDER_STATUS",
|
||||
text.count(
|
||||
"Functions = {GAMTimer SentinelProducer GAMDisplay IOGAM_Writer}"
|
||||
)
|
||||
== 1,
|
||||
"THREAD_ORDER_MISMATCH",
|
||||
)
|
||||
|
||||
try:
|
||||
gamdisplay = extract_block(
|
||||
canonical,
|
||||
" +GAMDisplay = {\n",
|
||||
" +IOGAM_Writer = {\n",
|
||||
)
|
||||
except ValueError as exc:
|
||||
print(f"GAMDISPLAY_EXTRACTION_ERROR={exc}")
|
||||
gamdisplay = ""
|
||||
|
||||
display_checks = {
|
||||
"GAMDISPLAY_CLASS_COUNT": gamdisplay.count("Class = IOGAM") == 1,
|
||||
"GAMDISPLAY_COUNTER_COUNT": gamdisplay.count(
|
||||
" Counter = {\n"
|
||||
)
|
||||
== 2,
|
||||
"GAMDISPLAY_TIME_COUNT": gamdisplay.count(" Time = {\n") == 2,
|
||||
"GAMDISPLAY_CYCLETIME_COUNT": gamdisplay.count(
|
||||
"State1_Thread1_CycleTime ="
|
||||
)
|
||||
== 2,
|
||||
"GAMDISPLAY_SCALAR_A_COUNT": gamdisplay.count("SentinelScalarA =") == 2,
|
||||
"GAMDISPLAY_SCALAR_B_COUNT": gamdisplay.count("SentinelScalarB =") == 2,
|
||||
"GAMDISPLAY_VECTOR_COUNT": gamdisplay.count("SentinelVector =") == 2,
|
||||
"GAMDISPLAY_VECTOR_DIMENSION_COUNT": gamdisplay.count(
|
||||
"NumberOfDimensions = 1"
|
||||
)
|
||||
== 2,
|
||||
"GAMDISPLAY_VECTOR_ELEMENTS_COUNT": gamdisplay.count(
|
||||
"NumberOfElements = 3"
|
||||
)
|
||||
== 2,
|
||||
"GAMDISPLAY_OUTPUT_DATASOURCE_COUNT": gamdisplay.count(
|
||||
"DataSource = Display"
|
||||
)
|
||||
== 6,
|
||||
}
|
||||
for name, passed in display_checks.items():
|
||||
report.check(name, passed, f"{name}_MISMATCH")
|
||||
|
||||
gamdisplay_hash = digest(gamdisplay)
|
||||
print(f"GAMDISPLAY_BLOCK_SHA256={gamdisplay_hash}")
|
||||
print(
|
||||
"EXPECTED_GAMDISPLAY_BLOCK_SHA256="
|
||||
f"{EXPECTED_GAMDISPLAY_BLOCK_SHA256}"
|
||||
)
|
||||
report.check(
|
||||
"GAMDISPLAY_BLOCK_STATUS",
|
||||
gamdisplay_hash == EXPECTED_GAMDISPLAY_BLOCK_SHA256,
|
||||
"GAMDISPLAY_BLOCK_HASH_MISMATCH",
|
||||
)
|
||||
|
||||
try:
|
||||
writer = extract_block(
|
||||
canonical,
|
||||
" +IOGAM_Writer = {\n",
|
||||
" }\n +Data = {\n",
|
||||
)
|
||||
except ValueError as exc:
|
||||
print(f"IOGAM_WRITER_EXTRACTION_ERROR={exc}")
|
||||
writer = ""
|
||||
writer_hash = digest(writer)
|
||||
print(f"IOGAM_WRITER_BLOCK_SHA256={writer_hash}")
|
||||
print(
|
||||
"EXPECTED_IOGAM_WRITER_BLOCK_SHA256="
|
||||
f"{EXPECTED_IOGAM_WRITER_BLOCK_SHA256}"
|
||||
)
|
||||
report.check(
|
||||
"IOGAM_WRITER_INVARIANT_STATUS",
|
||||
writer_hash == EXPECTED_IOGAM_WRITER_BLOCK_SHA256,
|
||||
"IOGAM_WRITER_BLOCK_HASH_MISMATCH",
|
||||
)
|
||||
|
||||
try:
|
||||
filewriter = extract_block(
|
||||
canonical,
|
||||
" +FileWriter = {\n",
|
||||
" }\n +States = {\n",
|
||||
)
|
||||
except ValueError as exc:
|
||||
print(f"FILEWRITER_EXTRACTION_ERROR={exc}")
|
||||
filewriter = ""
|
||||
filewriter_hash = digest(filewriter)
|
||||
print(f"FILEWRITER_BLOCK_SHA256={filewriter_hash}")
|
||||
print(
|
||||
"EXPECTED_FILEWRITER_BLOCK_SHA256="
|
||||
f"{EXPECTED_FILEWRITER_BLOCK_SHA256}"
|
||||
)
|
||||
report.check(
|
||||
"FILEWRITER_INVARIANT_STATUS",
|
||||
filewriter_hash == EXPECTED_FILEWRITER_BLOCK_SHA256,
|
||||
"FILEWRITER_BLOCK_HASH_MISMATCH",
|
||||
)
|
||||
|
||||
structural_expectations = {
|
||||
"CONSTANT_GAM_COUNT": (text.count("Class = ConstantGAM"), 1),
|
||||
"SENTINEL_SCALAR_A_DECLARATION_COUNT": (
|
||||
text.count("SentinelScalarA ="),
|
||||
6,
|
||||
),
|
||||
"SENTINEL_SCALAR_B_DECLARATION_COUNT": (
|
||||
text.count("SentinelScalarB ="),
|
||||
6,
|
||||
),
|
||||
"SENTINEL_VECTOR_DECLARATION_COUNT": (
|
||||
text.count("SentinelVector ="),
|
||||
6,
|
||||
),
|
||||
"VECTOR_DIMENSION_COUNT": (text.count("NumberOfDimensions = 1"), 6),
|
||||
"VECTOR_CARDINALITY_COUNT": (text.count("NumberOfElements = 3"), 6),
|
||||
"FILEWRITER_CLASS_COUNT": (
|
||||
text.count("Class = FileDataSource::FileWriter"),
|
||||
1,
|
||||
),
|
||||
"PLAIN_FILEWRITER_CLASS_COUNT": (text.count("Class = FileWriter"), 0),
|
||||
}
|
||||
for name, (observed, expected) in structural_expectations.items():
|
||||
print(f"{name}={observed}")
|
||||
print(f"EXPECTED_{name}={expected}")
|
||||
report.check(
|
||||
f"{name}_STATUS",
|
||||
observed == expected,
|
||||
f"{name}_MISMATCH",
|
||||
)
|
||||
|
||||
expected_display_block = (
|
||||
" +Display = {\n"
|
||||
f" Class = {expected_display_class}\n"
|
||||
" }\n"
|
||||
)
|
||||
report.check(
|
||||
"DISPLAY_DATASOURCE_CLASS_STATUS",
|
||||
text.count(expected_display_block) == 1,
|
||||
"DISPLAY_DATASOURCE_CLASS_MISMATCH",
|
||||
)
|
||||
report.check(
|
||||
"PROFILE_CANONICAL_HASH_STATUS",
|
||||
canonical_hash == expected_profile_hash,
|
||||
"PROFILE_CANONICAL_HASH_MISMATCH",
|
||||
)
|
||||
|
||||
report.emit()
|
||||
passed = all(value == "PASS" for value in report.checks.values())
|
||||
print(f"CONFIG_PROFILE_VERIFICATION_STATUS={'PASS' if passed else 'FAIL'}")
|
||||
return 0 if passed else 1
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("mode", choices=sorted(EXPECTED_PROFILE_HASHES))
|
||||
parser.add_argument("configuration", type=Path)
|
||||
parser.add_argument("expected_filename")
|
||||
args = parser.parse_args()
|
||||
return verify(args.mode, args.configuration, args.expected_filename)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user