]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
make compile_templates deterministic 1454/head
authorTakuto Ikuta <tikuta@google.com>
Wed, 26 May 2021 08:09:33 +0000 (17:09 +0900)
committerDavid Lord <davidism@gmail.com>
Mon, 9 Aug 2021 17:58:24 +0000 (10:58 -0700)
Python3 doesn't keep insertion order for set(), so this sorts some
places for deterministic output for compiled template.

CHANGES.rst
src/jinja2/compiler.py
src/jinja2/idtracking.py
tests/test_compile.py [new file with mode: 0644]

index 7bfebffcc3aad1d428ab39ba52c8415d6790cdec..116461af291ccabe4969df082189ed0408b51a4b 100644 (file)
@@ -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
index 5fe93494929c2bbc2eb447fe86b58ac2545387ff..b6297202ad78fd7e0739fc7ef2edd8fb5940c5d1 100644 (file)
@@ -556,7 +556,7 @@ class CodeGenerator(NodeVisitor):
             visitor.tests,
             "tests",
         ):
-            for name in names:
+            for name in sorted(names):
                 if name not in id_map:
                     id_map[name] = self.temporary_identifier()
 
index a2e7a055e0febe5b05f2f3833fb243792199ba93..38c525ee31278c95b93bafd341a0882638746ffd 100644 (file)
@@ -149,7 +149,7 @@ class Symbols:
         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 (file)
index 0000000..42a773f
--- /dev/null
@@ -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