From a9859fc9eb9fa49604ad606574841a292ac15ee1 Mon Sep 17 00:00:00 2001 From: zimri-leisher Date: Tue, 8 Sep 2026 10:25:08 -0400 Subject: [PATCH] Validate Fw.LogSeverity against the dictionary like the other canonical types (#225) Co-Authored-By: Claude Opus 5 (1M context) --- src/fpy/state.py | 6 +++ test/fpy/test_compiler_config.py | 67 ++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/fpy/state.py b/src/fpy/state.py index 4ecca9d..2a8b265 100644 --- a/src/fpy/state.py +++ b/src/fpy/state.py @@ -588,6 +588,12 @@ def _build_global_scopes( dict_type_name_dict, "Fw.TimeComparison", TIME_COMPARISON ) _validate_and_replace_type(dict_type_name_dict, "Svc.BlockState", BLOCK_STATE) + # Fw.LogSeverity backs log()'s severity argument. A dictionary without + # events never defines it, so it is optional -- but when a dictionary does + # define it, its representation must match the one log() pushes. + _validate_and_replace_type( + dict_type_name_dict, "Fw.LogSeverity", LOG_SEVERITY, required=False + ) _update_seq_args_from_dict(dict_type_name_dict) # Build the full type dict: start from (now-validated) dictionary types, diff --git a/test/fpy/test_compiler_config.py b/test/fpy/test_compiler_config.py index 9bdd65b..340daa6 100644 --- a/test/fpy/test_compiler_config.py +++ b/test/fpy/test_compiler_config.py @@ -857,3 +857,70 @@ def test_param_valid_absent_is_tolerated(): finally: Path(dict_path).unlink() _clear_caches() + + +# ============================================================================ +# Fw.LogSeverity validation +# ============================================================================ + + +def test_log_severity_mismatch_raises_error(): + """A dictionary whose Fw.LogSeverity disagrees with the canonical enum is + rejected: log() pushes a severity of the canonical representation type, + and the runtime pops it as Fw::LogSeverity::SerialType.""" + _clear_caches() + + def widen_rep(type_def): + type_def["representationType"] = { + "name": "U16", + "kind": "integer", + "size": 16, + "signed": False, + } + + dict_path = _create_test_dict_with_modified_type("Fw.LogSeverity", widen_rep) + + try: + from fpy.error import DictionaryError + + with pytest.raises(DictionaryError, match="Fw.LogSeverity"): + get_base_compile_state(dict_path) + finally: + Path(dict_path).unlink() + _clear_caches() + + +def test_log_severity_constant_mismatch_raises_error(): + """Renumbering one of its constants is rejected too.""" + _clear_caches() + + def renumber(type_def): + for const in type_def["enumeratedConstants"]: + if const["name"] == "FATAL": + const["value"] = 9 + + dict_path = _create_test_dict_with_modified_type("Fw.LogSeverity", renumber) + + try: + from fpy.error import DictionaryError + + with pytest.raises(DictionaryError, match="Fw.LogSeverity"): + get_base_compile_state(dict_path) + finally: + Path(dict_path).unlink() + _clear_caches() + + +def test_log_severity_absent_is_tolerated(): + """A dictionary that never mentions it leaves the canonical definition + standing.""" + _clear_caches() + + dict_path = _create_test_dict_with_modified_type("Fw.LogSeverity", None) + + try: + state = get_base_compile_state(dict_path) + assert state is not None + finally: + Path(dict_path).unlink() + _clear_caches()