@@ -84,19 +84,6 @@ def check_file_exists(path):
8484 assert os .path .isfile (path ), f"Expected { path } to exist, but it didn't!"
8585
8686
87- def check_json (path : Union [str , os .PathLike ], content : str ) -> None :
88- """Check a JSON file."""
89-
90- with open (path , "r" , encoding = "UTF-8" ) as file_to_check :
91- actual_json = json .load (file_to_check )
92- expected_json = json .loads (content )
93-
94- check_content (
95- json .dumps (expected_json , indent = 4 , sort_keys = True ).splitlines (),
96- json .dumps (actual_json , indent = 4 , sort_keys = True ).splitlines (),
97- )
98-
99-
10087def apply_archive_substitutions (text : str , context ) -> str :
10188 """Replace archive-related dynamic placeholders with values stored on *context*."""
10289 if hasattr (context , "archive_sha256" ):
@@ -110,67 +97,6 @@ def apply_archive_substitutions(text: str, context) -> str:
11097 return text
11198
11299
113- def _json_subset_matches (expected , actual ) -> bool :
114- """Return *True* when *expected* is a subset of *actual* (recursive).
115-
116- **List matching is greedy and order-sensitive.** Each item in *expected*
117- is matched against *actual* in order, claiming the first unused actual
118- item that satisfies the subset check. This means an earlier expected
119- item can consume the only actual item that a later, more specific
120- expected item would need. For example, with::
121-
122- expected = [{"a": 1}, {"a": 1, "b": 2}]
123- actual = [{"a": 1, "b": 2}]
124-
125- the first expected item matches ``{"a": 1, "b": 2}`` (leaving nothing
126- for the second), so the overall match returns *False* even though
127- ``{"a": 1, "b": 2}`` satisfies the second item. Consumers should
128- **not** rely on non-deterministic matching; instead, pre-order *expected*
129- lists from most-specific to least-specific to avoid this behaviour.
130- """
131- if isinstance (expected , dict ):
132- if not isinstance (actual , dict ):
133- return False
134- return all (
135- k in actual and _json_subset_matches (v , actual [k ])
136- for k , v in expected .items ()
137- )
138- if isinstance (expected , list ):
139- if not isinstance (actual , list ):
140- return False
141- matched = [False ] * len (actual )
142- for exp_item in expected :
143- found = False
144- for i , act_item in enumerate (actual ):
145- if not matched [i ] and _json_subset_matches (exp_item , act_item ):
146- matched [i ] = True
147- found = True
148- break
149- if not found :
150- return False
151- return True
152- return expected == actual
153-
154-
155- def check_json_subset (path : Union [str , os .PathLike ], content : str , context ) -> None :
156- """Assert that a JSON file *contains* the given key-values (subset match).
157-
158- Dynamic placeholders (``<archive-sha256>``, ``<archive-url>``) in
159- *content* are substituted with values from *context* before parsing.
160- """
161- content = apply_archive_substitutions (content , context )
162-
163- with open (path , "r" , encoding = "UTF-8" ) as file_to_check :
164- actual_json = json .load (file_to_check )
165- expected_json = json .loads (content )
166-
167- assert _json_subset_matches (expected_json , actual_json ), (
168- f"JSON subset mismatch.\n "
169- f"Expected subset:\n { json .dumps (expected_json , indent = 4 , sort_keys = True )} \n "
170- f"Actual:\n { json .dumps (actual_json , indent = 4 , sort_keys = True )} "
171- )
172-
173-
174100def check_content (
175101 expected_content : Iterable [str ], actual_content : Iterable [str ], strict = False
176102) -> None :
@@ -405,6 +331,18 @@ def step_impl(context, name):
405331 check_file_exists (name )
406332
407333
334+ def check_json (path : Union [str , os .PathLike ], content : str ) -> None :
335+ """Check a JSON file for exact equality (after normalising formatting)."""
336+ with open (path , "r" , encoding = "UTF-8" ) as file_to_check :
337+ actual_json = json .load (file_to_check )
338+ expected_json = json .loads (content )
339+
340+ check_content (
341+ json .dumps (expected_json , indent = 4 , sort_keys = True ).splitlines (),
342+ json .dumps (actual_json , indent = 4 , sort_keys = True ).splitlines (),
343+ )
344+
345+
408346@then ("the '{name}' file contains" )
409347def step_impl (context , name ):
410348 if name .endswith (".json" ):
@@ -418,12 +356,6 @@ def step_impl(_, name):
418356 assert os .path .exists (name ), f"Expected { name } to exist, but it didn't!"
419357
420358
421- @then ("the '{name}' json file includes" )
422- def step_impl (context , name ):
423- """Partial JSON match - the expected JSON must be a *subset* of the actual file."""
424- check_json_subset (name , context .text , context )
425-
426-
427359def multisub (patterns : List [Tuple [Pattern [str ], str ]], text : str ) -> str :
428360 """Apply a list of tuples that each contain a regex + replace string."""
429361 for pattern , replace in patterns :
0 commit comments