gha-utils 4.14.1__py3-none-any.whl → 4.14.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of gha-utils might be problematic. Click here for more details.
- gha_utils/__init__.py +1 -1
- gha_utils/cli.py +11 -5
- gha_utils/test_plan.py +74 -46
- {gha_utils-4.14.1.dist-info → gha_utils-4.14.2.dist-info}/METADATA +1 -1
- gha_utils-4.14.2.dist-info/RECORD +14 -0
- gha_utils-4.14.1.dist-info/RECORD +0 -14
- {gha_utils-4.14.1.dist-info → gha_utils-4.14.2.dist-info}/WHEEL +0 -0
- {gha_utils-4.14.1.dist-info → gha_utils-4.14.2.dist-info}/entry_points.txt +0 -0
- {gha_utils-4.14.1.dist-info → gha_utils-4.14.2.dist-info}/top_level.txt +0 -0
gha_utils/__init__.py
CHANGED
gha_utils/cli.py
CHANGED
|
@@ -279,12 +279,15 @@ def mailmap_sync(ctx, source, create_if_missing, destination_mailmap):
|
|
|
279
279
|
# `file_path` type.
|
|
280
280
|
type=click.Path(exists=True, executable=True, resolve_path=True),
|
|
281
281
|
required=True,
|
|
282
|
-
|
|
282
|
+
metavar="FILE_PATH",
|
|
283
|
+
help="Path to the binary file to test.",
|
|
283
284
|
)
|
|
284
285
|
@option(
|
|
285
286
|
"--plan",
|
|
286
287
|
type=file_path(exists=True, readable=True, resolve_path=True),
|
|
287
|
-
|
|
288
|
+
metavar="FILE_PATH",
|
|
289
|
+
help="Path to the test plan file in YAML. If not provided, a default test "
|
|
290
|
+
"plan will be executed.",
|
|
288
291
|
)
|
|
289
292
|
@option(
|
|
290
293
|
"-t",
|
|
@@ -293,18 +296,21 @@ def mailmap_sync(ctx, source, create_if_missing, destination_mailmap):
|
|
|
293
296
|
# 0.0 is negative values are provided, so we mimic this behavior here:
|
|
294
297
|
# https://github.com/python/cpython/blob/5740b95076b57feb6293cda4f5504f706a7d622d/Lib/subprocess.py#L1596-L1597
|
|
295
298
|
type=FloatRange(min=0, clamp=True),
|
|
299
|
+
metavar="SECONDS",
|
|
296
300
|
help="Set the default timeout for each CLI call, if not specified in the "
|
|
297
301
|
"test plan.",
|
|
298
302
|
)
|
|
299
303
|
def test_plan(binary: Path, plan: Path | None, timeout: float | None) -> None:
|
|
300
304
|
# Load test plan from workflow input, or use a default one.
|
|
301
305
|
if plan:
|
|
302
|
-
logging.
|
|
306
|
+
logging.info(f"Read test plan from {plan}")
|
|
303
307
|
test_plan = parse_test_plan(plan)
|
|
304
308
|
else:
|
|
305
|
-
logging.warning(
|
|
309
|
+
logging.warning("No test plan provided: use default test plan.")
|
|
306
310
|
test_plan = DEFAULT_TEST_PLAN # type: ignore[assignment]
|
|
311
|
+
logging.debug(f"Test plan: {test_plan}")
|
|
307
312
|
|
|
308
313
|
for index, test_case in enumerate(test_plan):
|
|
309
|
-
logging.info(f"Run test #{index}")
|
|
314
|
+
logging.info(f"Run test #{index + 1}")
|
|
315
|
+
logging.debug(f"Test case parameters: {test_case}")
|
|
310
316
|
test_case.check_cli_test(binary, default_timeout=timeout)
|
gha_utils/test_plan.py
CHANGED
|
@@ -18,6 +18,8 @@ from __future__ import annotations
|
|
|
18
18
|
|
|
19
19
|
import logging
|
|
20
20
|
import re
|
|
21
|
+
import shlex
|
|
22
|
+
import sys
|
|
21
23
|
from dataclasses import asdict, dataclass, field
|
|
22
24
|
from pathlib import Path
|
|
23
25
|
from subprocess import TimeoutExpired, run
|
|
@@ -40,12 +42,18 @@ class TestCase:
|
|
|
40
42
|
output_contains: tuple[str, ...] | str = field(default_factory=tuple)
|
|
41
43
|
stdout_contains: tuple[str, ...] | str = field(default_factory=tuple)
|
|
42
44
|
stderr_contains: tuple[str, ...] | str = field(default_factory=tuple)
|
|
43
|
-
output_regex_matches: tuple[str, ...] | str = field(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
output_regex_matches: tuple[re.Pattern | str, ...] | str = field(
|
|
46
|
+
default_factory=tuple
|
|
47
|
+
)
|
|
48
|
+
stdout_regex_matches: tuple[re.Pattern | str, ...] | str = field(
|
|
49
|
+
default_factory=tuple
|
|
50
|
+
)
|
|
51
|
+
stderr_regex_matches: tuple[re.Pattern | str, ...] | str = field(
|
|
52
|
+
default_factory=tuple
|
|
53
|
+
)
|
|
54
|
+
output_regex_fullmatch: re.Pattern | str | None = None
|
|
55
|
+
stdout_regex_fullmatch: re.Pattern | str | None = None
|
|
56
|
+
stderr_regex_fullmatch: re.Pattern | str | None = None
|
|
49
57
|
|
|
50
58
|
def __post_init__(self) -> None:
|
|
51
59
|
"""Normalize all fields."""
|
|
@@ -72,40 +80,47 @@ class TestCase:
|
|
|
72
80
|
if not isinstance(field_data, bool):
|
|
73
81
|
raise ValueError(f"strip_ansi is not a boolean: {field_data}")
|
|
74
82
|
|
|
75
|
-
# Validates and normalize regex fullmatch fields.
|
|
76
|
-
elif field_id.endswith("_fullmatch"):
|
|
77
|
-
if field_data:
|
|
78
|
-
if not isinstance(field_data, str):
|
|
79
|
-
raise ValueError(f"{field_id} is not a string: {field_data}")
|
|
80
|
-
# Normalize empty strings to None.
|
|
81
|
-
else:
|
|
82
|
-
field_data = None
|
|
83
|
-
|
|
84
83
|
# Validates and normalize tuple of strings.
|
|
85
84
|
else:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
85
|
+
if field_data:
|
|
86
|
+
# Wraps single string and other types into a tuple.
|
|
87
|
+
if isinstance(field_data, str) or not isinstance(
|
|
88
|
+
field_data, Sequence
|
|
89
|
+
):
|
|
90
|
+
# CLI parameters needs to be split on Unix-like systems.
|
|
91
|
+
# XXX If we need the same for Windows, have a look at:
|
|
92
|
+
# https://github.com/maxpat78/w32lex
|
|
93
|
+
if field_id == "cli_parameters" and sys.platform != "win32":
|
|
94
|
+
field_data = tuple(shlex.split(field_data))
|
|
95
|
+
else:
|
|
96
|
+
field_data = (field_data,)
|
|
97
|
+
|
|
98
|
+
for item in field_data:
|
|
99
|
+
if not isinstance(item, str):
|
|
100
|
+
raise ValueError(f"Invalid string in {field_id}: {item}")
|
|
101
|
+
# Ignore blank value.
|
|
102
|
+
field_data = tuple(i for i in field_data if i.strip())
|
|
103
|
+
|
|
104
|
+
# Validates fields containing one or more regexes.
|
|
105
|
+
if "_regex_" in field_id and field_data:
|
|
106
|
+
# Compile all regexes.
|
|
107
|
+
valid_regexes = []
|
|
102
108
|
for regex in flatten((field_data,)):
|
|
103
109
|
try:
|
|
104
|
-
|
|
110
|
+
# Let dots in regex match newlines.
|
|
111
|
+
valid_regexes.append(re.compile(regex, re.DOTALL))
|
|
105
112
|
except re.error as ex:
|
|
106
113
|
raise ValueError(
|
|
107
114
|
f"Invalid regex in {field_id}: {regex}"
|
|
108
115
|
) from ex
|
|
116
|
+
# Normalize single regex to a single element.
|
|
117
|
+
if field_id.endswith("_fullmatch"):
|
|
118
|
+
if valid_regexes:
|
|
119
|
+
field_data = valid_regexes.pop()
|
|
120
|
+
else:
|
|
121
|
+
field_data = None
|
|
122
|
+
else:
|
|
123
|
+
field_data = tuple(valid_regexes)
|
|
109
124
|
|
|
110
125
|
setattr(self, field_id, field_data)
|
|
111
126
|
|
|
@@ -156,24 +171,32 @@ class TestCase:
|
|
|
156
171
|
# encoding="utf-8",
|
|
157
172
|
text=True,
|
|
158
173
|
)
|
|
159
|
-
except TimeoutExpired
|
|
174
|
+
except TimeoutExpired:
|
|
160
175
|
raise TimeoutError(
|
|
161
|
-
f"CLI timed out after {self.timeout} seconds: {clean_args}"
|
|
162
|
-
)
|
|
176
|
+
f"CLI timed out after {self.timeout} seconds: {' '.join(clean_args)}"
|
|
177
|
+
)
|
|
163
178
|
|
|
164
179
|
print_cli_run(clean_args, result)
|
|
165
180
|
|
|
166
181
|
for field_id, field_data in asdict(self).items():
|
|
167
|
-
if field_id == "
|
|
182
|
+
if field_id == "exit_code":
|
|
183
|
+
if field_data is not None:
|
|
184
|
+
logging.info(f"Test exit code, expecting: {field_data}")
|
|
185
|
+
if result.returncode != field_data:
|
|
186
|
+
raise AssertionError(
|
|
187
|
+
f"CLI exited with code {result.returncode}, "
|
|
188
|
+
f"expected {field_data}"
|
|
189
|
+
)
|
|
190
|
+
# The specific exit code matches, let's proceed to the next test.
|
|
168
191
|
continue
|
|
169
192
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
)
|
|
193
|
+
# Ignore non-output fields, and empty test cases.
|
|
194
|
+
elif not (
|
|
195
|
+
field_id.startswith(("output_", "stdout_", "stderr_")) and field_data
|
|
196
|
+
):
|
|
197
|
+
continue
|
|
176
198
|
|
|
199
|
+
# Prepare output and name for comparison.
|
|
177
200
|
output = ""
|
|
178
201
|
name = ""
|
|
179
202
|
if field_id.startswith("output_"):
|
|
@@ -188,10 +211,12 @@ class TestCase:
|
|
|
188
211
|
name = "<stderr>"
|
|
189
212
|
|
|
190
213
|
if self.strip_ansi:
|
|
214
|
+
logging.info(f"Strip ANSI escape sequences from CLI's {name}")
|
|
191
215
|
output = strip_ansi(output)
|
|
192
216
|
|
|
193
217
|
if field_id.endswith("_contains"):
|
|
194
218
|
for sub_string in field_data:
|
|
219
|
+
logging.info(f"Check if CLI's {name} contains: {sub_string!r}")
|
|
195
220
|
if sub_string not in output:
|
|
196
221
|
raise AssertionError(
|
|
197
222
|
f"CLI's {name} does not contain {sub_string!r}"
|
|
@@ -199,18 +224,21 @@ class TestCase:
|
|
|
199
224
|
|
|
200
225
|
elif field_id.endswith("_regex_matches"):
|
|
201
226
|
for regex in field_data:
|
|
202
|
-
if
|
|
227
|
+
logging.info(f"Check if CLI's {name} matches: {sub_string!r}")
|
|
228
|
+
if not regex.search(output):
|
|
203
229
|
raise AssertionError(
|
|
204
|
-
f"CLI's {name} does not match regex {regex
|
|
230
|
+
f"CLI's {name} does not match regex {regex}"
|
|
205
231
|
)
|
|
206
232
|
|
|
207
233
|
elif field_id.endswith("_regex_fullmatch"):
|
|
208
234
|
regex = field_data
|
|
209
|
-
if not
|
|
235
|
+
if not regex.fullmatch(output):
|
|
210
236
|
raise AssertionError(
|
|
211
|
-
f"CLI's {name} does not fully match regex {regex
|
|
237
|
+
f"CLI's {name} does not fully match regex {regex}"
|
|
212
238
|
)
|
|
213
239
|
|
|
240
|
+
logging.info("All tests passed for CLI.")
|
|
241
|
+
|
|
214
242
|
|
|
215
243
|
DEFAULT_TEST_PLAN = (
|
|
216
244
|
# Output the version of the CLI.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
gha_utils/__init__.py,sha256=dR8lNlVY_u847eZ0CTxmll40WHAkfXzWXN26hheu824,866
|
|
2
|
+
gha_utils/__main__.py,sha256=Dck9BjpLXmIRS83k0mghAMcYVYiMiFLltQdfRuMSP_Q,1703
|
|
3
|
+
gha_utils/changelog.py,sha256=oahY88A9FRV14f1JSFKIiYrN_TS7Jo3QlljXqJbeuaE,5892
|
|
4
|
+
gha_utils/cli.py,sha256=Gq2pGpIOmvXtsexmg6nwN2ZuZ1lpHrJ5A43CeuDjK68,10985
|
|
5
|
+
gha_utils/mailmap.py,sha256=naUqJYJnE3fLTjju1nd6WMm7ODiSaI2SHuJxRtmaFWs,6269
|
|
6
|
+
gha_utils/matrix.py,sha256=_afJD0K-xZLNxwykVnUhD0Gj9cdO0Z43g3VHa-q_tkI,11941
|
|
7
|
+
gha_utils/metadata.py,sha256=Oumkmo4-O549hxlw8zaTWIPkITpUxUjpulj_MXn6fjA,48649
|
|
8
|
+
gha_utils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
gha_utils/test_plan.py,sha256=B6EiiGWVTrNS7jSIO9EE0kebccx10Fn0ZPapJTOOyGA,11928
|
|
10
|
+
gha_utils-4.14.2.dist-info/METADATA,sha256=uPoJ9lj1TvWEt1Izsipp-4R6bsQ-gwHjnqwILdpL0fQ,20514
|
|
11
|
+
gha_utils-4.14.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
12
|
+
gha_utils-4.14.2.dist-info/entry_points.txt,sha256=8bJOwQYf9ZqsLhBR6gUCzvwLNI9f8tiiBrJ3AR0EK4o,54
|
|
13
|
+
gha_utils-4.14.2.dist-info/top_level.txt,sha256=C94Blb61YkkyPBwCdM3J_JPDjWH0lnKa5nGZeZ5M6yE,10
|
|
14
|
+
gha_utils-4.14.2.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
gha_utils/__init__.py,sha256=tyy7yfWx6CvbUpDZspBu8GcoheikuKeyNdTJc9kXb3k,866
|
|
2
|
-
gha_utils/__main__.py,sha256=Dck9BjpLXmIRS83k0mghAMcYVYiMiFLltQdfRuMSP_Q,1703
|
|
3
|
-
gha_utils/changelog.py,sha256=oahY88A9FRV14f1JSFKIiYrN_TS7Jo3QlljXqJbeuaE,5892
|
|
4
|
-
gha_utils/cli.py,sha256=k0y799fNn8W10A_LT4QuO-ZgSWUloA7owMAePJa70sI,10730
|
|
5
|
-
gha_utils/mailmap.py,sha256=naUqJYJnE3fLTjju1nd6WMm7ODiSaI2SHuJxRtmaFWs,6269
|
|
6
|
-
gha_utils/matrix.py,sha256=_afJD0K-xZLNxwykVnUhD0Gj9cdO0Z43g3VHa-q_tkI,11941
|
|
7
|
-
gha_utils/metadata.py,sha256=Oumkmo4-O549hxlw8zaTWIPkITpUxUjpulj_MXn6fjA,48649
|
|
8
|
-
gha_utils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
gha_utils/test_plan.py,sha256=IeBm5VRQnaSUMohpV0SnBvPSLG-Ke6O4i8WJsHdyqOM,10609
|
|
10
|
-
gha_utils-4.14.1.dist-info/METADATA,sha256=l3L-pznLOZFr_MYlQpQfuhA8HHRi4ltqS57oa0jUtXI,20514
|
|
11
|
-
gha_utils-4.14.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
12
|
-
gha_utils-4.14.1.dist-info/entry_points.txt,sha256=8bJOwQYf9ZqsLhBR6gUCzvwLNI9f8tiiBrJ3AR0EK4o,54
|
|
13
|
-
gha_utils-4.14.1.dist-info/top_level.txt,sha256=C94Blb61YkkyPBwCdM3J_JPDjWH0lnKa5nGZeZ5M6yE,10
|
|
14
|
-
gha_utils-4.14.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|