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 CHANGED
@@ -17,4 +17,4 @@
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = "4.14.1"
20
+ __version__ = "4.14.2"
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
- help="Path to the binary to test.",
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
- help="Test plan in YAML.",
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.debug(f"Read test plan from {plan}")
306
+ logging.info(f"Read test plan from {plan}")
303
307
  test_plan = parse_test_plan(plan)
304
308
  else:
305
- logging.warning(f"No test plan provided. Default to: {DEFAULT_TEST_PLAN}")
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(default_factory=tuple)
44
- stdout_regex_matches: tuple[str, ...] | str = field(default_factory=tuple)
45
- stderr_regex_matches: tuple[str, ...] | str = field(default_factory=tuple)
46
- output_regex_fullmatch: str | None = None
47
- stdout_regex_fullmatch: str | None = None
48
- stderr_regex_fullmatch: str | None = None
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
- # Wraps single string into a tuple.
87
- if isinstance(field_data, str):
88
- field_data = (field_data,)
89
- if not isinstance(field_data, Sequence):
90
- raise ValueError(
91
- f"{field_id} is not a tuple or a list: {field_data}"
92
- )
93
- if not all(isinstance(i, str) for i in field_data):
94
- raise ValueError(
95
- f"{field_id} contains non-string elements: {field_data}"
96
- )
97
- # Ignore blank value.
98
- field_data = tuple(i.strip() for i in field_data if i.strip())
99
-
100
- # Validates regexps.
101
- if field_data and "_regex_" in field_id:
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
- re.compile(regex)
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 as ex:
174
+ except TimeoutExpired:
160
175
  raise TimeoutError(
161
- f"CLI timed out after {self.timeout} seconds: {clean_args}"
162
- ) from ex
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 == "cli_parameters" or (not field_data and field_data != 0):
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
- if field_id == "exit_code":
171
- if result.returncode != field_data:
172
- raise AssertionError(
173
- f"CLI exited with code {result.returncode}, "
174
- f"expected {field_data}"
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 not re.search(regex, output):
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!r}"
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 re.fullmatch(regex, output):
235
+ if not regex.fullmatch(output):
210
236
  raise AssertionError(
211
- f"CLI's {name} does not fully match regex {regex!r}"
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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: gha-utils
3
- Version: 4.14.1
3
+ Version: 4.14.2
4
4
  Summary: ⚙️ CLI helpers for GitHub Actions + reuseable workflows
5
5
  Author-email: Kevin Deldycke <kevin@deldycke.com>
6
6
  Project-URL: Homepage, https://github.com/kdeldycke/workflows
@@ -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,,