From ed19633dd7dd3e14c34150014c59a81c43fe4ec0 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Fri, 19 Dec 2025 10:49:43 -0800 Subject: [PATCH] Fix for #312 , support generators no longer have access to code generator filters This change ensures that unsupported filters are not made available to the support generators --- .gitignore | 1 + src/nunavut/jinja/__init__.py | 44 ++++++++++++++-- test/gentest_filters/test_filters.py | 76 +++++++++++++++++++++++++++- 3 files changed, 116 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index bb03af27..183190e5 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ out venv* *.bkp *.bak +*.profraw # Eclipse .metadata diff --git a/src/nunavut/jinja/__init__.py b/src/nunavut/jinja/__init__.py index 54c0dd21..b34e77c0 100644 --- a/src/nunavut/jinja/__init__.py +++ b/src/nunavut/jinja/__init__.py @@ -204,7 +204,23 @@ def __init__( env_builder.add_globals(**additional_globals) env_builder.set_embed_auditing_info(embed_auditing_info) - self._env = env_builder.create(language_context) + self._env = self._create_environment(env_builder, language_context) + + def _create_environment( + self, env_builder: CodeGenEnvironmentBuilder, language_context: nunavut.lang.LanguageContext + ) -> CodeGenEnvironment: + """ + Create the code generation environment for this generator. + + This is a template method that subclasses can override to customize the environment + with generator-specific filters, tests, and globals. The base implementation creates + an environment with language-specific filters only. + + :param env_builder: The environment builder with basic configuration already set. + :param language_context: The language context for this generator. + :return: A configured CodeGenEnvironment instance. + """ + return env_builder.create(language_context) @property def dsdl_loader(self) -> DSDLTemplateLoader: @@ -772,9 +788,31 @@ def is_deprecated(instance: pydsdl.Any) -> bool: def __init__(self, namespace: nunavut.Namespace, resource_types: int = ResourceType.ANY.value, **kwargs: Any): super().__init__(namespace, resource_types=resource_types, **kwargs) + + def _create_environment( + self, env_builder: CodeGenEnvironmentBuilder, language_context: nunavut.lang.LanguageContext + ) -> CodeGenEnvironment: + """ + Create the environment with DSDL-specific filters and tests. + + This override adds DSDL-specific functionality like type_to_template, type_to_include_path, + and DSDL tests (is_service_request, is_service_response, etc.) that should only be + available in DSDL type templates, not in support templates. + + :param env_builder: The environment builder with basic configuration already set. + :param language_context: The language context for this generator. + :return: A configured CodeGenEnvironment with DSDL-specific additions. + """ + env = super()._create_environment(env_builder, language_context) + + # Add DSDL-specific tests (is_service_request, is_service_response, etc.) for test_name, test in self._create_all_dsdl_tests().items(): - self._env.add_test(test_name, test) - self._env.add_conventional_methods_to_environment(self) + env.add_test(test_name, test) + + # Add DSDL-specific filters (type_to_template, type_to_include_path, etc.) + env.add_conventional_methods_to_environment(self) + + return env # +-----------------------------------------------------------------------+ # | AbstractGenerator diff --git a/test/gentest_filters/test_filters.py b/test/gentest_filters/test_filters.py index ee8313bd..91622bd1 100644 --- a/test/gentest_filters/test_filters.py +++ b/test/gentest_filters/test_filters.py @@ -14,8 +14,8 @@ from nunavut import Namespace from nunavut._namespace import build_namespace_tree # deprecated -from nunavut.jinja import DSDLCodeGenerator -from nunavut.jinja.jinja2.exceptions import TemplateAssertionError +from nunavut.jinja import DSDLCodeGenerator, SupportGenerator +from nunavut.jinja.jinja2.exceptions import TemplateAssertionError, UndefinedError from nunavut.lang import Language, LanguageClassLoader, LanguageContextBuilder @@ -448,3 +448,75 @@ def test_filter_to_template_unique(gen_paths): actual = foo_file.read() assert expected == actual + + +def test_support_generator_filter_isolation(gen_paths): # type: ignore + """ + Test that DSDL-specific filters are not available in SupportGenerator templates. + This verifies that suggestion #2 (creating different environment objects per generator) + is properly implemented, preventing filter leaking between generators. + """ + root_path = str(gen_paths.dsdl_dir / Path("uavcan")) + output_path = gen_paths.out_dir / "filter_isolation" + compound_types = read_namespace(root_path, []) + language_context = LanguageContextBuilder().set_target_language("c").create() + namespace = build_namespace_tree(compound_types, root_path, output_path, language_context) + + # Create a template that tries to use a DSDL-specific filter + template_dir = gen_paths.out_dir / "filter_isolation_templates" + template_dir.mkdir(parents=True, exist_ok=True) + template_file = template_dir / "serialization.j2" + + # This template tries to use type_to_template, which should only be available in DSDLCodeGenerator + with open(template_file, "w", encoding="utf-8") as f: + f.write("{{ 'test' | type_to_template }}") + + # Create a SupportGenerator with this template + from nunavut._utilities import ResourceType + support_generator = SupportGenerator( + namespace, + resource_types=ResourceType.SERIALIZATION_SUPPORT.value, + templates_dir=template_dir + ) + + # Attempting to generate should fail because type_to_template filter is not available + # in SupportGenerator's environment + with pytest.raises((TemplateAssertionError, UndefinedError, AttributeError)) as exc_info: + list(support_generator.generate_all()) + + # Verify the error message mentions the missing filter + error_message = str(exc_info.value) + assert "type_to_template" in error_message.lower() or "no filter" in error_message.lower() + + +def test_dsdl_generator_has_dsdl_filters(gen_paths): # type: ignore + """ + Test that DSDL-specific filters ARE available in DSDLCodeGenerator templates. + This is the positive test case for filter isolation. + """ + root_path = str(gen_paths.dsdl_dir / Path("uavcan")) + output_path = gen_paths.out_dir / "dsdl_filter_test" + compound_types = read_namespace(root_path, []) + language_context = LanguageContextBuilder().set_target_language("c").create() + namespace = build_namespace_tree(compound_types, root_path, output_path, language_context) + + # Create a template that uses DSDL-specific filters + template_dir = gen_paths.out_dir / "dsdl_filter_test_templates" + template_dir.mkdir(parents=True, exist_ok=True) + template_file = template_dir / "Any.j2" + + # This template uses type_to_template, which should be available in DSDLCodeGenerator + with open(template_file, "w", encoding="utf-8") as f: + f.write("{{ T | type_to_template }}") + + # Create a DSDLCodeGenerator with this template + dsdl_generator = DSDLCodeGenerator( + namespace, + templates_dir=template_dir + ) + + # This should work without errors + generated_files = list(dsdl_generator.generate_all()) + + # Verify that files were generated successfully + assert len(generated_files) > 0