diff --git a/CHANGES.rst b/CHANGES.rst index fc540af5a..a85318085 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,8 @@ Version 3.0.2 Unreleased +- Make ``compile_templates`` deterministic. :issue:`1452, 1453` + Version 3.0.1 ------------- diff --git a/src/jinja2/compiler.py b/src/jinja2/compiler.py index ef4c0a1f1..98d69ae9a 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..2a810f6ec --- /dev/null +++ b/tests/test_compile.py @@ -0,0 +1,76 @@ +import os +import shutil +import tempfile + +from jinja2 import Environment +from jinja2 import loaders + + +class TestCompileTemplate: + archive = None + + def setup(self): + self.archive = tempfile.mkdtemp() + + def teardown(self): + shutil.rmtree(self.archive) + + def test_filters(self): + num_filter = 10 + + env = Environment( + loader=loaders.DictLoader( + { + "foo": "".join( + "{{ %d|filter%d }}" % (i, i) for i in range(num_filter) + ) + } + ) + ) + + for i in range(num_filter): + env.filters["filter%d" % i] = lambda value: value + + env.compile_templates(self.archive, zip=None, ignore_errors=False) + + files = os.listdir(self.archive) + assert len(files) == 1 + + with open(os.path.join(self.archive, files[0])) as f: + content = f.read() + + for i in range(num_filter): + idx = content.find("environment.filters['filter%d']" % i) + assert idx >= 0 + content = content[idx:] + + def test_import_as_with_context(self): + num_import = 10 + + env = Environment( + loader=loaders.DictLoader( + { + "foo": "\n".join( + '{%% import "bar" as bar%d with context %%}' % i + for i in range(num_import) + ) + } + ) + ) + env.compile_templates(self.archive, zip=None, ignore_errors=False) + + files = os.listdir(self.archive) + assert len(files) == 1 + + with open(os.path.join(self.archive, files[0])) as f: + content = next( + filter(lambda line: "make_module" in line, f.read().splitlines()) + ) + + for i in range(num_import): + print(i) + print(content) + + idx = content.find("'bar%d': " % i) + assert idx >= 0 + content = content[idx:]