bugfix: preserve dict order for Spec.dag_hash()
in Python 2 (#31092)
Fix a bug introduced in #21720. `spack_json.dump()` calls `_strify()` on dictionaries to convert `unicode` to `str`, but it constructs `dict` objects instead of `collections.OrderedDict` objects, so in Python 2 (or earlier versions of 3) it can scramble dictionary order. This can cause hashes to differ between Python 2 and Python 3, or between Python 3.7 and earlier Python 3's. - [x] use `OrderedDict` in `_strify` - [x] add a regression test
This commit is contained in:
parent
f42680b785
commit
bf2b30a5f5
@ -8,7 +8,10 @@
|
|||||||
The YAML and JSON formats preserve DAG information in the spec.
|
The YAML and JSON formats preserve DAG information in the spec.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
|
import collections
|
||||||
import inspect
|
import inspect
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@ -433,3 +436,75 @@ def test_legacy_yaml(tmpdir, install_mockery, mock_packages):
|
|||||||
spec = Spec.from_yaml(yaml)
|
spec = Spec.from_yaml(yaml)
|
||||||
concrete_spec = spec.concretized()
|
concrete_spec = spec.concretized()
|
||||||
assert concrete_spec.eq_dag(spec)
|
assert concrete_spec.eq_dag(spec)
|
||||||
|
|
||||||
|
|
||||||
|
#: A well ordered Spec dictionary, using ``OrderdDict``.
|
||||||
|
#: Any operation that transforms Spec dictionaries should
|
||||||
|
#: preserve this order.
|
||||||
|
ordered_spec = collections.OrderedDict([
|
||||||
|
("arch", collections.OrderedDict([
|
||||||
|
("platform", "darwin"),
|
||||||
|
("platform_os", "bigsur"),
|
||||||
|
("target", collections.OrderedDict([
|
||||||
|
("features", [
|
||||||
|
"adx",
|
||||||
|
"aes",
|
||||||
|
"avx",
|
||||||
|
"avx2",
|
||||||
|
"bmi1",
|
||||||
|
"bmi2",
|
||||||
|
"clflushopt",
|
||||||
|
"f16c",
|
||||||
|
"fma",
|
||||||
|
"mmx",
|
||||||
|
"movbe",
|
||||||
|
"pclmulqdq",
|
||||||
|
"popcnt",
|
||||||
|
"rdrand",
|
||||||
|
"rdseed",
|
||||||
|
"sse",
|
||||||
|
"sse2",
|
||||||
|
"sse4_1",
|
||||||
|
"sse4_2",
|
||||||
|
"ssse3",
|
||||||
|
"xsavec",
|
||||||
|
"xsaveopt"
|
||||||
|
]),
|
||||||
|
("generation", 0),
|
||||||
|
("name", "skylake"),
|
||||||
|
("parents", ["broadwell"]),
|
||||||
|
("vendor", "GenuineIntel"),
|
||||||
|
])),
|
||||||
|
])),
|
||||||
|
("compiler", collections.OrderedDict([
|
||||||
|
("name", "apple-clang"),
|
||||||
|
("version", "13.0.0"),
|
||||||
|
])),
|
||||||
|
("name", "zlib"),
|
||||||
|
("namespace", "builtin"),
|
||||||
|
("parameters", collections.OrderedDict([
|
||||||
|
("cflags", []),
|
||||||
|
("cppflags", []),
|
||||||
|
("cxxflags", []),
|
||||||
|
("fflags", []),
|
||||||
|
("ldflags", []),
|
||||||
|
("ldlibs", []),
|
||||||
|
("optimize", True),
|
||||||
|
("pic", True),
|
||||||
|
("shared", True),
|
||||||
|
])),
|
||||||
|
("version", "1.2.11"),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.regression("31092")
|
||||||
|
def test_strify_preserves_order():
|
||||||
|
"""Ensure that ``spack_json._strify()`` dumps dictionaries in the right order.
|
||||||
|
|
||||||
|
``_strify()`` is used in ``spack_json.dump()``, which is used in
|
||||||
|
``Spec.dag_hash()``, so if this goes wrong, ``Spec`` hashes can vary between python
|
||||||
|
versions.
|
||||||
|
|
||||||
|
"""
|
||||||
|
strified = sjson._strify(ordered_spec)
|
||||||
|
assert list(ordered_spec.items()) == list(strified.items())
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
"""Simple wrapper around JSON to guarantee consistent use of load/dump. """
|
"""Simple wrapper around JSON to guarantee consistent use of load/dump. """
|
||||||
|
import collections
|
||||||
import json
|
import json
|
||||||
from typing import Any, Dict, Optional # novm
|
from typing import Any, Dict, Optional # novm
|
||||||
|
|
||||||
@ -72,9 +73,10 @@ def _strify(data, ignore_dicts=False):
|
|||||||
# if this is a dictionary, return dictionary of byteified keys and values
|
# if this is a dictionary, return dictionary of byteified keys and values
|
||||||
# but only if we haven't already byteified it
|
# but only if we haven't already byteified it
|
||||||
if isinstance(data, dict) and not ignore_dicts:
|
if isinstance(data, dict) and not ignore_dicts:
|
||||||
return dict((_strify(key, ignore_dicts=True),
|
return collections.OrderedDict(
|
||||||
_strify(value, ignore_dicts=True)) for key, value in
|
(_strify(key, ignore_dicts=True), _strify(value, ignore_dicts=True))
|
||||||
iteritems(data))
|
for key, value in iteritems(data)
|
||||||
|
)
|
||||||
|
|
||||||
# if it's anything else, return it in its original form
|
# if it's anything else, return it in its original form
|
||||||
return data
|
return data
|
||||||
|
Loading…
Reference in New Issue
Block a user