diff --git a/CHANGES.rst b/CHANGES.rst index 7bfebffcc..116461af2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,8 @@ Unreleased - Fix a loop scoping bug that caused assignments in nested loops to still be referenced outside of it. :issue:`1427` +- Make ``compile_templates`` deterministic for filter and import + names. :issue:`1452, 1453` Version 3.0.1 diff --git a/src/jinja2/compiler.py b/src/jinja2/compiler.py index 5fe934949..b6297202a 100644 --- a/src/jinja2/compiler.py +++ b/src/jinja2/compiler.py @@ -556,7 +556,7 @@ def pull_dependencies(self, nodes: t.Iterable[nodes.Node]) -> None: visitor.tests, "tests", ): - for name in names: + for name in sorted(names): if name not in id_map: id_map[name] = self.temporary_identifier() diff --git a/src/jinja2/idtracking.py b/src/jinja2/idtracking.py index a2e7a055e..38c525ee3 100644 --- a/src/jinja2/idtracking.py +++ b/src/jinja2/idtracking.py @@ -149,7 +149,7 @@ def dump_stores(self) -> t.Dict[str, str]: node: t.Optional["Symbols"] = self while node is not None: - for name in node.stores: + for name in sorted(node.stores): if name not in rv: rv[name] = self.find_ref(name) # type: ignore diff --git a/tests/test_compile.py b/tests/test_compile.py new file mode 100644 index 000000000..42a773f21 --- /dev/null +++ b/tests/test_compile.py @@ -0,0 +1,28 @@ +import os +import re + +from jinja2.environment import Environment +from jinja2.loaders import DictLoader + + +def test_filters_deterministic(tmp_path): + src = "".join(f"{{{{ {i}|filter{i} }}}}" for i in range(10)) + env = Environment(loader=DictLoader({"foo": src})) + env.filters.update(dict.fromkeys((f"filter{i}" for i in range(10)), lambda: None)) + env.compile_templates(tmp_path, zip=None) + name = os.listdir(tmp_path)[0] + content = (tmp_path / name).read_text("utf8") + expect = [f"filters['filter{i}']" for i in range(10)] + found = re.findall(r"filters\['filter\d']", content) + assert found == expect + + +def test_import_as_with_context_deterministic(tmp_path): + src = "\n".join(f'{{% import "bar" as bar{i} with context %}}' for i in range(10)) + env = Environment(loader=DictLoader({"foo": src})) + env.compile_templates(tmp_path, zip=None) + name = os.listdir(tmp_path)[0] + content = (tmp_path / name).read_text("utf8") + expect = [f"'bar{i}': " for i in range(10)] + found = re.findall(r"'bar\d': ", content)[:10] + assert found == expect