From 695dab004d958f77a2f57060cc5312a2e11962a7 Mon Sep 17 00:00:00 2001 From: Yogev Neumann Date: Mon, 2 Feb 2026 17:54:27 -0500 Subject: [PATCH 1/2] Consolidate Data Element templates Signed-off-by: Yogev Neumann --- tools/j2735_c_generator_data_element.py | 79 ++++++++++++---- ...ssemble_de.j2 => assemble_de_bitstring.j2} | 74 ++++++++++++++- tools/templates/file_header_de.j2 | 92 ------------------- .../test_bitstring_internal_ext_size.py | 65 +++++++++++++ .../test_bitstring_internal_max_wire_bits.py | 65 +++++++++++++ .../test_bitstring_internal_root_size.py | 65 +++++++++++++ 6 files changed, 328 insertions(+), 112 deletions(-) rename tools/templates/{assemble_de.j2 => assemble_de_bitstring.j2} (54%) delete mode 100644 tools/templates/file_header_de.j2 create mode 100644 tools/tests/c_generator/test_bitstring_internal_ext_size.py create mode 100644 tools/tests/c_generator/test_bitstring_internal_max_wire_bits.py create mode 100644 tools/tests/c_generator/test_bitstring_internal_root_size.py diff --git a/tools/j2735_c_generator_data_element.py b/tools/j2735_c_generator_data_element.py index 1287b4f..02c8afd 100644 --- a/tools/j2735_c_generator_data_element.py +++ b/tools/j2735_c_generator_data_element.py @@ -15,43 +15,90 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: 2026 Yogev Neumann """ -TODO +J2735 Data Element C Code Generator. + +Generates zero-copy C header files for J2735 Data Element types (BIT STRING, +INTEGER, ENUMERATED, etc.). Data Elements use the DE_ prefix in J2735 as they +represent atomic/primitive types. + +Example usage: + from tools.j2735_c_generator_data_element import generate_data_element + from tools.j2735_spec_parser import parse_spec_file + + spec = parse_spec_file("J2735_202409_pdf_content.txt") + + # BIT STRING types + code = generate_data_element("VehicleEventFlags", spec) """ from .j2735_c_generator_jinja import create_jinja_env, get_template from .j2735_spec_constraints import BitStringConstraint -from .j2735_spec_parser import J2735Specification +from .j2735_spec_parser import ASN1TypeClass, ASN1TypeDefinition, J2735Specification -_TEMPLATE_NAME = "assemble_de.j2" +_BITSTRING_TEMPLATE_NAME = "assemble_de_bitstring.j2" def generate_data_element(type_name: str, spec: J2735Specification) -> str: - """Generate a complete C header file for a J2735 Data Element. + """Generate complete C header file for a Data Element. + + This is the main entry point for Data Element code generation. Dispatches + to the appropriate internal generator based on the ASN.1 type class. + + Supported types: + - BIT STRING: Fixed-size bit fields with named bits Args: - type_name: The ASN.1 type name (e.g., "VehicleEventFlags"). + type_name: Name of the type (e.g., "VehicleEventFlags"). spec: The parsed J2735 specification. Returns: Complete C header file content as a string. Raises: - TypeError: If the type is not a supported constraint type. + ValueError: If type_name is not found. + ValueError: If type is not a supported Data Element type. """ typedef = spec.lookup_type(type_name) if typedef is None: raise ValueError(f"Type '{type_name}' not found in specification") - if not isinstance(typedef.constraint, BitStringConstraint): + + if typedef.type_class == ASN1TypeClass.BIT_STRING: + return _generate_bitstring(typedef) + raise ValueError( + f"Type '{type_name}' is {typedef.type_class.name}, which is not a supported " + f"Data Element type. Supported types: BIT_STRING" + ) + + +def _generate_bitstring(typedef: ASN1TypeDefinition) -> str: + """Generate C code for a BIT STRING type. + + Internal helper for generate_data_element(). + + Args: + typedef: The ASN.1 type definition for the BIT STRING. + + Returns: + Complete C header file content as a string. + + Raises: + TypeError: If constraint is not BitStringConstraint. + + Examples: + >>> from tools.tests.conftest import SPEC_FILE_PATH + >>> from tools.j2735_spec_parser import parse_spec_file + >>> spec = parse_spec_file(SPEC_FILE_PATH) + >>> code = generate_data_element("VehicleEventFlags", spec) + >>> "J2735_VEHICLE_EVENT_FLAGS_GET_EVENT_HAZARD_LIGHTS" in code + True + """ + # Type narrowing for mypy - validate constraint type + bitstring = typedef.constraint + if not isinstance(bitstring, BitStringConstraint): raise TypeError( - f"generate_data_element currently only supports BitStringConstraint, " - f"got {type(typedef.constraint).__name__}" + f"Expected BitStringConstraint for BIT STRING type, got {type(bitstring).__name__}" ) - # Create Jinja environment and add pow() global for mask calculations - env = create_jinja_env() - template = get_template(env, _TEMPLATE_NAME) + template = get_template(create_jinja_env(), _BITSTRING_TEMPLATE_NAME) - return template.render( - data_type=typedef.type_class.name.lower(), - typedef=typedef, - ) + return template.render(typedef=typedef) diff --git a/tools/templates/assemble_de.j2 b/tools/templates/assemble_de_bitstring.j2 similarity index 54% rename from tools/templates/assemble_de.j2 rename to tools/templates/assemble_de_bitstring.j2 index 002c436..8a3eaac 100644 --- a/tools/templates/assemble_de.j2 +++ b/tools/templates/assemble_de_bitstring.j2 @@ -17,20 +17,86 @@ SPDX-FileCopyrightText: 2026 Yogev Neumann -#} {#- -Template: assemble_de.j2 -Purpose: Generate C header file for a data element (DE). +Template: assemble_de_bitstring.j2 +Purpose: Generate C header file for a BIT STRING data element (DE). Template Context (from generator): - typedef: The full typedef object containing the metadata -#} +{%- set data_type = typedef.type_class.name | lower -%} {%- set is_extensible = typedef.constraint.is_extensible -%} {%- set named_bits = typedef.constraint.named_bits -%} {%- set typedef_name = typedef.name -%} {%- set typedef_name_upper = typedef_name | screaming_snake -%} {#- Banner line width: 100 chars total, content area is 92 chars -#} {%- set W = 92 -%} -{% include 'license.j2' %} -{% include 'file_header_de.j2' %} +{#- Compute derived values for the header -#} +{%- set ext_bits = typedef.constraint.ext_bits -%} +{%- set read_bits = typedef.constraint.read_bits -%} +{%- set root_size = typedef.constraint.root_size -%} +{%- set root_wire_size = 1 + root_size -%} +{%- set garbage_bits = read_bits - 1 - root_size -%} +{%- set ext_bit_pos = read_bits - 1 -%} +{#- Box column widths -#} +{%- set COL1 = 5 -%} +{%- set COL2_NONEXT = 52 -%} +{%- set COL2_EXT = 18 -%} +{%- set COL3_EXT = 31 -%} +{% include 'license.j2' %} +/** + * @file + * @author Yogev Neumann + * @brief J2735 {{ typedef_name }} Definition and Access Macros. + * + * {{ typedef_name }} ::= BIT STRING { +{% for name, index in typedef.constraint.named_bits.items() | sort(attribute='1') %} + * {{ name }} ({{ index }}){{ "," if not loop.last else "" }} +{% endfor %} + * } (SIZE ({{ root_size }}{{ ", ..." if is_extensible else "" }}{{ ", " ~ ext_bits if is_extensible and ext_bits != root_size else "" }})) + * +{% if is_extensible %} + * Extensible BIT STRING with root size {{ root_size }} and known extension size {{ ext_bits }}. + * + * Wire Format (non-extended, {{ root_wire_size }} bits total): + * ┌{{ "─" * (COL1 + 2) }}┬{{ "─" * (COL2_NONEXT + 2) }}┐ + * │ {{ "%-*s"|format(COL1, "Bit 0") }} │ {{ "%-*s"|format(COL2_NONEXT, "Bits 1-" ~ root_size) }} │ + * ├{{ "─" * (COL1 + 2) }}┼{{ "─" * (COL2_NONEXT + 2) }}┤ + * │ {{ "%-*s"|format(COL1, "Ext=0") }} │ {{ "%-*s"|format(COL2_NONEXT, "flags[0.." ~ (root_size - 1) ~ "] (" ~ root_size ~ " bits)") }} │ + * └{{ "─" * (COL1 + 2) }}┴{{ "─" * (COL2_NONEXT + 2) }}┘ + * + * Wire Format (extended, {{ read_bits }} bits total): + * ┌{{ "─" * (COL1 + 2) }}┬{{ "─" * (COL2_EXT + 2) }}┬{{ "─" * (COL3_EXT + 2) }}┐ + * │ {{ "%-*s"|format(COL1, "Bit 0") }} │ {{ "%-*s"|format(COL2_EXT, "Bits 1-7") }} │ {{ "%-*s"|format(COL3_EXT, "Bits 8-" ~ (read_bits - 1)) }} │ + * ├{{ "─" * (COL1 + 2) }}┼{{ "─" * (COL2_EXT + 2) }}┼{{ "─" * (COL3_EXT + 2) }}┤ + * │ {{ "%-*s"|format(COL1, "Ext=1") }} │ {{ "%-*s"|format(COL2_EXT, "nsnnwn=" ~ ext_bits ~ " (7 bits)") }} │ {{ "%-*s"|format(COL3_EXT, "flags[0.." ~ (ext_bits - 1) ~ "] (" ~ ext_bits ~ " bits)") }} │ + * └{{ "─" * (COL1 + 2) }}┴{{ "─" * (COL2_EXT + 2) }}┴{{ "─" * (COL3_EXT + 2) }}┘ +{% else %} +{%- set COL_FIXED = 60 %} + * Fixed BIT STRING with size {{ root_size }}. + * + * Wire Format ({{ root_wire_size }} bits total): + * ┌{{ "─" * (COL_FIXED + 2) }}┐ + * │ {{ "%-*s"|format(COL_FIXED, "Bits 0-" ~ (root_size - 1)) }} │ + * ├{{ "─" * (COL_FIXED + 2) }}┤ + * │ {{ "%-*s"|format(COL_FIXED, "flags[0.." ~ (root_size - 1) ~ "] (" ~ root_size ~ " bits)") }} │ + * └{{ "─" * (COL_FIXED + 2) }}┘ +{% endif %} + * + * Optimization: Single-Read Strategy + * ────────────────────────────────────────────────────────────────────────────────────────── + * Max wire size = {{ read_bits }} bits ≤ 56-bit READ_BITS limit. + * We read all {{ read_bits }} bits in ONE call, then use bit arithmetic to extract: + * - Extension bit at position {{ ext_bit_pos }} (MSB of {{ read_bits }}-bit value) + * - Flags at positions 0-{{ ext_bits - 1 }} (extended) or shifted for non-extended + * + * {{ read_bits }}-bit read layout (left-justified from bit 0): + * Non-extended: [Ext=0][F0..F{{ root_size - 1 }}][{{ garbage_bits }} garbage bits] + * bit{{ ext_bit_pos }} {{ ext_bit_pos - 1 }}..{{ garbage_bits }} {{ garbage_bits - 1 }}..0 + * Extended: [Ext=1][nsnnwn:7][F0..F{{ ext_bits - 1 }}] + * bit{{ ext_bit_pos }} {{ ext_bit_pos - 1 }}..{{ ext_bits }} {{ ext_bits - 1 }}..0 + * + * @todo Update the Doxygen to indicate [in] and [out] parameters + */ #ifndef J2735_INTERNAL_DE_{{ typedef_name | upper }}_H #define J2735_INTERNAL_DE_{{ typedef_name | upper }}_H diff --git a/tools/templates/file_header_de.j2 b/tools/templates/file_header_de.j2 deleted file mode 100644 index a8118da..0000000 --- a/tools/templates/file_header_de.j2 +++ /dev/null @@ -1,92 +0,0 @@ -{#- - Copyright 2026 Yogev Neumann - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - SPDX-License-Identifier: Apache-2.0 - SPDX-FileCopyrightText: 2026 Yogev Neumann --#} -{#- -Template: file_header_de.j2 -Purpose: Generate the file header comment for a data element (DE) C header file. - -Template Context (from generator): - - typedef: The full typedef object containing the metadata --#} -{#- Compute derived values for the header -#} -{%- set ext_bits = typedef.constraint.ext_bits -%} -{%- set read_bits = typedef.constraint.read_bits -%} -{%- set root_size = typedef.constraint.root_size -%} -{%- set type_name = typedef.name -%} -{%- set root_wire_size = 1 + root_size -%} -{%- set garbage_bits = read_bits - 1 - root_size -%} -{%- set ext_bit_pos = read_bits - 1 -%} -{#- Box column widths -#} -{%- set COL1 = 5 -%} -{%- set COL2_NONEXT = 52 -%} -{%- set COL2_EXT = 18 -%} -{%- set COL3_EXT = 31 -%} -/** - * @file - * @author Yogev Neumann - * @brief J2735 {{ type_name }} Definition and Access Macros. - * - * {{ type_name }} ::= BIT STRING { -{% for name, index in typedef.constraint.named_bits.items() | sort(attribute='1') %} - * {{ name }} ({{ index }}){{ "," if not loop.last else "" }} -{% endfor %} - * } (SIZE ({{ root_size }}{{ ", ..." if is_extensible else "" }}{{ ", " ~ ext_bits if is_extensible and ext_bits != root_size else "" }})) - * -{% if is_extensible %} - * Extensible BIT STRING with root size {{ root_size }} and known extension size {{ ext_bits }}. - * - * Wire Format (non-extended, {{ root_wire_size }} bits total): - * ┌{{ "─" * (COL1 + 2) }}┬{{ "─" * (COL2_NONEXT + 2) }}┐ - * │ {{ "%-*s"|format(COL1, "Bit 0") }} │ {{ "%-*s"|format(COL2_NONEXT, "Bits 1-" ~ root_size) }} │ - * ├{{ "─" * (COL1 + 2) }}┼{{ "─" * (COL2_NONEXT + 2) }}┤ - * │ {{ "%-*s"|format(COL1, "Ext=0") }} │ {{ "%-*s"|format(COL2_NONEXT, "flags[0.." ~ (root_size - 1) ~ "] (" ~ root_size ~ " bits)") }} │ - * └{{ "─" * (COL1 + 2) }}┴{{ "─" * (COL2_NONEXT + 2) }}┘ - * - * Wire Format (extended, {{ read_bits }} bits total): - * ┌{{ "─" * (COL1 + 2) }}┬{{ "─" * (COL2_EXT + 2) }}┬{{ "─" * (COL3_EXT + 2) }}┐ - * │ {{ "%-*s"|format(COL1, "Bit 0") }} │ {{ "%-*s"|format(COL2_EXT, "Bits 1-7") }} │ {{ "%-*s"|format(COL3_EXT, "Bits 8-" ~ (read_bits - 1)) }} │ - * ├{{ "─" * (COL1 + 2) }}┼{{ "─" * (COL2_EXT + 2) }}┼{{ "─" * (COL3_EXT + 2) }}┤ - * │ {{ "%-*s"|format(COL1, "Ext=1") }} │ {{ "%-*s"|format(COL2_EXT, "nsnnwn=" ~ ext_bits ~ " (7 bits)") }} │ {{ "%-*s"|format(COL3_EXT, "flags[0.." ~ (ext_bits - 1) ~ "] (" ~ ext_bits ~ " bits)") }} │ - * └{{ "─" * (COL1 + 2) }}┴{{ "─" * (COL2_EXT + 2) }}┴{{ "─" * (COL3_EXT + 2) }}┘ -{% else %} -{%- set COL_FIXED = 60 %} - * Fixed BIT STRING with size {{ root_size }}. - * - * Wire Format ({{ root_wire_size }} bits total): - * ┌{{ "─" * (COL_FIXED + 2) }}┐ - * │ {{ "%-*s"|format(COL_FIXED, "Bits 0-" ~ (root_size - 1)) }} │ - * ├{{ "─" * (COL_FIXED + 2) }}┤ - * │ {{ "%-*s"|format(COL_FIXED, "flags[0.." ~ (root_size - 1) ~ "] (" ~ root_size ~ " bits)") }} │ - * └{{ "─" * (COL_FIXED + 2) }}┘ -{% endif %} - * - * Optimization: Single-Read Strategy - * ────────────────────────────────────────────────────────────────────────────────────────── - * Max wire size = {{ read_bits }} bits ≤ 56-bit READ_BITS limit. - * We read all {{ read_bits }} bits in ONE call, then use bit arithmetic to extract: - * - Extension bit at position {{ ext_bit_pos }} (MSB of {{ read_bits }}-bit value) - * - Flags at positions 0-{{ ext_bits - 1 }} (extended) or shifted for non-extended - * - * {{ read_bits }}-bit read layout (left-justified from bit 0): - * Non-extended: [Ext=0][F0..F{{ root_size - 1 }}][{{ garbage_bits }} garbage bits] - * bit{{ ext_bit_pos }} {{ ext_bit_pos - 1 }}..{{ garbage_bits }} {{ garbage_bits - 1 }}..0 - * Extended: [Ext=1][nsnnwn:7][F0..F{{ ext_bits - 1 }}] - * bit{{ ext_bit_pos }} {{ ext_bit_pos - 1 }}..{{ ext_bits }} {{ ext_bits - 1 }}..0 - * - * @todo Update the Doxygen to indicate [in] and [out] parameters - */ diff --git a/tools/tests/c_generator/test_bitstring_internal_ext_size.py b/tools/tests/c_generator/test_bitstring_internal_ext_size.py new file mode 100644 index 0000000..2231f11 --- /dev/null +++ b/tools/tests/c_generator/test_bitstring_internal_ext_size.py @@ -0,0 +1,65 @@ +# Copyright 2026 Yogev Neumann +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: 2026 Yogev Neumann +"""Tests for BIT STRING internal extended size constants generator. + +Tests cover bitstring_internal_ext_size.j2 which generates +J2735_INTERNAL_EXT_SIZE_{TYPE} constants for extended bit count. +""" + +from tools.tests.conftest import SpecLoadingTestBase, generate_bitstring_code + +_TEMPLATE_NAME = "bitstring/bitstring_internal_ext_size.j2" + + +class TestExtSizeGenerator(SpecLoadingTestBase): + """Tests for bitstring_internal_ext_size template.""" + + def test_extensible_type_has_ext_size_14(self) -> None: + """VehicleEventFlags should have ext_bits=14.""" + code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "VehicleEventFlags") + self.assertIn("J2735_INTERNAL_EXT_SIZE_VEHICLE_EVENT_FLAGS", code) + self.assertIn("14U", code) + + def test_non_extensible_8bit_type(self) -> None: + """GNSSstatus (8-bit) should have ext_bits=8 (same as root).""" + code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "GNSSstatus") + self.assertIn("J2735_INTERNAL_EXT_SIZE_GNSS_STATUS", code) + self.assertIn("8U", code) + + def test_non_extensible_12bit_type(self) -> None: + """AllowedManeuvers (12-bit) should have ext_bits=12.""" + code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "AllowedManeuvers") + self.assertIn("J2735_INTERNAL_EXT_SIZE_ALLOWED_MANEUVERS", code) + self.assertIn("12U", code) + + def test_small_2bit_type(self) -> None: + """LaneDirection (2-bit) should have ext_bits=2.""" + code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "LaneDirection") + self.assertIn("J2735_INTERNAL_EXT_SIZE_LANE_DIRECTION", code) + self.assertIn("2U", code) + + def test_invalid_type_raises_value_error(self) -> None: + """Non-existent type should raise ValueError.""" + with self.assertRaises(ValueError) as ctx: + _ = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "NonExistentType") + self.assertIn("not found", str(ctx.exception)) + + def test_non_bitstring_type_raises_value_error(self) -> None: + """INTEGER type should raise ValueError.""" + with self.assertRaises(ValueError) as ctx: + _ = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "MsgCount") + self.assertIn("not a BIT STRING", str(ctx.exception)) diff --git a/tools/tests/c_generator/test_bitstring_internal_max_wire_bits.py b/tools/tests/c_generator/test_bitstring_internal_max_wire_bits.py new file mode 100644 index 0000000..629e7fb --- /dev/null +++ b/tools/tests/c_generator/test_bitstring_internal_max_wire_bits.py @@ -0,0 +1,65 @@ +# Copyright 2026 Yogev Neumann +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: 2026 Yogev Neumann +"""Tests for BIT STRING internal max wire bits constants generator. + +Tests cover bitstring_internal_max_wire_bits.j2 which generates +J2735_INTERNAL_MAX_WIRE_BITS_{TYPE} constants for maximum wire encoding size. +""" + +from tools.tests.conftest import SpecLoadingTestBase, generate_bitstring_code + +_TEMPLATE_NAME = "bitstring/bitstring_internal_max_wire_bits.j2" + + +class TestMaxWireBitsGenerator(SpecLoadingTestBase): + """Tests for bitstring_internal_max_wire_bits template.""" + + def test_extensible_type_has_max_wire_bits_22(self) -> None: + """VehicleEventFlags should have read_bits=22 (1+7+14).""" + code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "VehicleEventFlags") + self.assertIn("J2735_INTERNAL_MAX_WIRE_BITS_VEHICLE_EVENT_FLAGS", code) + self.assertIn("22U", code) + + def test_non_extensible_8bit_type(self) -> None: + """GNSSstatus (8-bit) should have read_bits=8.""" + code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "GNSSstatus") + self.assertIn("J2735_INTERNAL_MAX_WIRE_BITS_GNSS_STATUS", code) + self.assertIn("8U", code) + + def test_non_extensible_12bit_type(self) -> None: + """AllowedManeuvers (12-bit) should have read_bits=12.""" + code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "AllowedManeuvers") + self.assertIn("J2735_INTERNAL_MAX_WIRE_BITS_ALLOWED_MANEUVERS", code) + self.assertIn("12U", code) + + def test_small_2bit_type(self) -> None: + """LaneDirection (2-bit) should have read_bits=2.""" + code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "LaneDirection") + self.assertIn("J2735_INTERNAL_MAX_WIRE_BITS_LANE_DIRECTION", code) + self.assertIn("2U", code) + + def test_invalid_type_raises_value_error(self) -> None: + """Non-existent type should raise ValueError.""" + with self.assertRaises(ValueError) as ctx: + _ = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "NonExistentType") + self.assertIn("not found", str(ctx.exception)) + + def test_non_bitstring_type_raises_value_error(self) -> None: + """INTEGER type should raise ValueError.""" + with self.assertRaises(ValueError) as ctx: + _ = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "MsgCount") + self.assertIn("not a BIT STRING", str(ctx.exception)) diff --git a/tools/tests/c_generator/test_bitstring_internal_root_size.py b/tools/tests/c_generator/test_bitstring_internal_root_size.py new file mode 100644 index 0000000..e908f82 --- /dev/null +++ b/tools/tests/c_generator/test_bitstring_internal_root_size.py @@ -0,0 +1,65 @@ +# Copyright 2026 Yogev Neumann +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: 2026 Yogev Neumann +"""Tests for BIT STRING internal root size constants generator. + +Tests cover bitstring_internal_root_size.j2 which generates +J2735_INTERNAL_ROOT_SIZE_{TYPE} constants for root bit count. +""" + +from tools.tests.conftest import SpecLoadingTestBase, generate_bitstring_code + +_TEMPLATE_NAME = "bitstring/bitstring_internal_root_size.j2" + + +class TestRootSizeGenerator(SpecLoadingTestBase): + """Tests for bitstring_internal_root_size template.""" + + def test_extensible_type_has_root_size_13(self) -> None: + """VehicleEventFlags should have root_size=13.""" + code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "VehicleEventFlags") + self.assertIn("J2735_INTERNAL_ROOT_SIZE_VEHICLE_EVENT_FLAGS", code) + self.assertIn("13U", code) + + def test_non_extensible_8bit_type(self) -> None: + """GNSSstatus (8-bit) should have root_size=8.""" + code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "GNSSstatus") + self.assertIn("J2735_INTERNAL_ROOT_SIZE_GNSS_STATUS", code) + self.assertIn("8U", code) + + def test_non_extensible_12bit_type(self) -> None: + """AllowedManeuvers (12-bit) should have root_size=12.""" + code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "AllowedManeuvers") + self.assertIn("J2735_INTERNAL_ROOT_SIZE_ALLOWED_MANEUVERS", code) + self.assertIn("12U", code) + + def test_small_2bit_type(self) -> None: + """LaneDirection (2-bit) should have root_size=2.""" + code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "LaneDirection") + self.assertIn("J2735_INTERNAL_ROOT_SIZE_LANE_DIRECTION", code) + self.assertIn("2U", code) + + def test_invalid_type_raises_value_error(self) -> None: + """Non-existent type should raise ValueError.""" + with self.assertRaises(ValueError) as ctx: + _ = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "NonExistentType") + self.assertIn("not found", str(ctx.exception)) + + def test_non_bitstring_type_raises_value_error(self) -> None: + """INTEGER type should raise ValueError.""" + with self.assertRaises(ValueError) as ctx: + _ = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "MsgCount") + self.assertIn("not a BIT STRING", str(ctx.exception)) From 2498718ac565123e39a25d586b6501a0d6c8d50e Mon Sep 17 00:00:00 2001 From: Yogev Neumann Date: Mon, 2 Feb 2026 18:14:06 -0500 Subject: [PATCH 2/2] Remove duplicated code and address _generate_bitstring() comment Signed-off-by: Yogev Neumann --- tools/j2735_c_generator_data_element.py | 3 ++- .../c_generator/test_bitstring_internal_bit_pos.py | 12 ------------ .../c_generator/test_bitstring_internal_ext_size.py | 12 ------------ .../c_generator/test_bitstring_internal_root_size.py | 12 ------------ 4 files changed, 2 insertions(+), 37 deletions(-) diff --git a/tools/j2735_c_generator_data_element.py b/tools/j2735_c_generator_data_element.py index 02c8afd..dd11610 100644 --- a/tools/j2735_c_generator_data_element.py +++ b/tools/j2735_c_generator_data_element.py @@ -88,7 +88,8 @@ def _generate_bitstring(typedef: ASN1TypeDefinition) -> str: >>> from tools.tests.conftest import SPEC_FILE_PATH >>> from tools.j2735_spec_parser import parse_spec_file >>> spec = parse_spec_file(SPEC_FILE_PATH) - >>> code = generate_data_element("VehicleEventFlags", spec) + >>> typedef = spec.lookup_type("VehicleEventFlags") + >>> code = _generate_bitstring(typedef) >>> "J2735_VEHICLE_EVENT_FLAGS_GET_EVENT_HAZARD_LIGHTS" in code True """ diff --git a/tools/tests/c_generator/test_bitstring_internal_bit_pos.py b/tools/tests/c_generator/test_bitstring_internal_bit_pos.py index 597ae98..c01ba58 100644 --- a/tools/tests/c_generator/test_bitstring_internal_bit_pos.py +++ b/tools/tests/c_generator/test_bitstring_internal_bit_pos.py @@ -52,15 +52,3 @@ def test_small_type_lane_direction(self) -> None: # Should have bits 0 and 1 self.assertIn("0U", code) self.assertIn("1U", code) - - def test_invalid_type_raises_value_error(self) -> None: - """Non-existent type should raise ValueError.""" - with self.assertRaises(ValueError) as ctx: - _ = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "NonExistentType") - self.assertIn("not found", str(ctx.exception)) - - def test_non_bitstring_type_raises_value_error(self) -> None: - """INTEGER type should raise ValueError.""" - with self.assertRaises(ValueError) as ctx: - _ = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "MsgCount") - self.assertIn("not a BIT STRING", str(ctx.exception)) diff --git a/tools/tests/c_generator/test_bitstring_internal_ext_size.py b/tools/tests/c_generator/test_bitstring_internal_ext_size.py index 2231f11..4cc3af8 100644 --- a/tools/tests/c_generator/test_bitstring_internal_ext_size.py +++ b/tools/tests/c_generator/test_bitstring_internal_ext_size.py @@ -51,15 +51,3 @@ def test_small_2bit_type(self) -> None: code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "LaneDirection") self.assertIn("J2735_INTERNAL_EXT_SIZE_LANE_DIRECTION", code) self.assertIn("2U", code) - - def test_invalid_type_raises_value_error(self) -> None: - """Non-existent type should raise ValueError.""" - with self.assertRaises(ValueError) as ctx: - _ = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "NonExistentType") - self.assertIn("not found", str(ctx.exception)) - - def test_non_bitstring_type_raises_value_error(self) -> None: - """INTEGER type should raise ValueError.""" - with self.assertRaises(ValueError) as ctx: - _ = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "MsgCount") - self.assertIn("not a BIT STRING", str(ctx.exception)) diff --git a/tools/tests/c_generator/test_bitstring_internal_root_size.py b/tools/tests/c_generator/test_bitstring_internal_root_size.py index e908f82..0bb2de6 100644 --- a/tools/tests/c_generator/test_bitstring_internal_root_size.py +++ b/tools/tests/c_generator/test_bitstring_internal_root_size.py @@ -51,15 +51,3 @@ def test_small_2bit_type(self) -> None: code = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "LaneDirection") self.assertIn("J2735_INTERNAL_ROOT_SIZE_LANE_DIRECTION", code) self.assertIn("2U", code) - - def test_invalid_type_raises_value_error(self) -> None: - """Non-existent type should raise ValueError.""" - with self.assertRaises(ValueError) as ctx: - _ = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "NonExistentType") - self.assertIn("not found", str(ctx.exception)) - - def test_non_bitstring_type_raises_value_error(self) -> None: - """INTEGER type should raise ValueError.""" - with self.assertRaises(ValueError) as ctx: - _ = generate_bitstring_code(_TEMPLATE_NAME, self.spec, "MsgCount") - self.assertIn("not a BIT STRING", str(ctx.exception))