antsibull-nox 0.2.0__py3-none-any.whl → 0.3.0__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.
@@ -0,0 +1,568 @@
1
+ # Author: Felix Fontein <felix@fontein.de>
2
+ # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or
3
+ # https://www.gnu.org/licenses/gpl-3.0.txt)
4
+ # SPDX-License-Identifier: GPL-3.0-or-later
5
+ # SPDX-FileCopyrightText: 2025, Ansible Project
6
+
7
+ """
8
+ Create nox ansible-test sessions.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import typing as t
15
+ from collections.abc import Callable
16
+ from pathlib import Path
17
+
18
+ import nox
19
+
20
+ from ..ansible import (
21
+ AnsibleCoreVersion,
22
+ get_ansible_core_info,
23
+ get_ansible_core_package_name,
24
+ get_supported_core_versions,
25
+ )
26
+ from ..paths import copy_directory_tree_into
27
+ from ..python import get_installed_python_versions
28
+ from ..utils import Version
29
+ from .collections import prepare_collections
30
+ from .utils import (
31
+ install,
32
+ register,
33
+ )
34
+
35
+
36
+ def _parse_ansible_core_version(
37
+ version: str | AnsibleCoreVersion,
38
+ ) -> AnsibleCoreVersion:
39
+ if version in ("devel", "milestone"):
40
+ # For some reason mypy doesn't notice that
41
+ return t.cast(AnsibleCoreVersion, version)
42
+ if isinstance(version, Version):
43
+ return version
44
+ return Version.parse(version)
45
+
46
+
47
+ def add_ansible_test_session(
48
+ *,
49
+ name: str,
50
+ description: str | None,
51
+ extra_deps_files: list[str | os.PathLike] | None = None,
52
+ ansible_test_params: list[str],
53
+ add_posargs: bool = True,
54
+ default: bool,
55
+ ansible_core_version: str | AnsibleCoreVersion,
56
+ ansible_core_source: t.Literal["git", "pypi"] = "git",
57
+ ansible_core_repo_name: str | None = None,
58
+ ansible_core_branch_name: str | None = None,
59
+ handle_coverage: t.Literal["never", "always", "auto"] = "auto",
60
+ register_name: str | None = None,
61
+ register_extra_data: dict[str, t.Any] | None = None,
62
+ callback_before: Callable[[], None] | None = None,
63
+ callback_after: Callable[[], None] | None = None,
64
+ ) -> None:
65
+ """
66
+ Add generic ansible-test session.
67
+
68
+ Returns a list of Python versions set for this session.
69
+ """
70
+ parsed_ansible_core_version = _parse_ansible_core_version(ansible_core_version)
71
+
72
+ def compose_dependencies() -> list[str]:
73
+ deps = [
74
+ get_ansible_core_package_name(
75
+ parsed_ansible_core_version,
76
+ source=ansible_core_source,
77
+ ansible_repo=ansible_core_repo_name,
78
+ branch_name=ansible_core_branch_name,
79
+ )
80
+ ]
81
+ return deps
82
+
83
+ def run_ansible_test(session: nox.Session) -> None:
84
+ install(session, *compose_dependencies())
85
+ prepared_collections = prepare_collections(
86
+ session,
87
+ install_in_site_packages=False,
88
+ extra_deps_files=extra_deps_files,
89
+ install_out_of_tree=True,
90
+ )
91
+ if not prepared_collections:
92
+ session.warn("Skipping ansible-test...")
93
+ return
94
+ cwd = Path.cwd()
95
+ with session.chdir(prepared_collections.current_path):
96
+ if callback_before:
97
+ callback_before()
98
+
99
+ command = ["ansible-test"] + ansible_test_params
100
+ if add_posargs and session.posargs:
101
+ command.extend(session.posargs)
102
+ session.run(*command)
103
+
104
+ coverage = (handle_coverage == "auto" and "--coverage" in command) or (
105
+ handle_coverage == "always"
106
+ )
107
+ if coverage:
108
+ session.run(
109
+ "ansible-test",
110
+ "coverage",
111
+ "xml",
112
+ "--color",
113
+ "-v",
114
+ "--requirements",
115
+ "--group-by",
116
+ "command",
117
+ "--group-by",
118
+ "version",
119
+ )
120
+
121
+ if callback_after:
122
+ callback_after()
123
+
124
+ copy_directory_tree_into(
125
+ prepared_collections.current_path / "tests" / "output",
126
+ cwd / "tests" / "output",
127
+ )
128
+
129
+ # Determine Python version(s)
130
+ core_info = get_ansible_core_info(parsed_ansible_core_version)
131
+ all_versions = get_installed_python_versions()
132
+
133
+ installed_versions = [
134
+ version
135
+ for version in core_info.controller_python_versions
136
+ if version in all_versions
137
+ ]
138
+ python = max(installed_versions or core_info.controller_python_versions)
139
+ python_versions = [python]
140
+
141
+ run_ansible_test.__doc__ = description
142
+ nox.session(
143
+ name=name,
144
+ default=default,
145
+ python=[str(python_version) for python_version in python_versions],
146
+ )(run_ansible_test)
147
+
148
+ if register_name:
149
+ data = {
150
+ "name": name,
151
+ "ansible-core": (
152
+ str(ansible_core_branch_name)
153
+ if ansible_core_branch_name is not None
154
+ else str(parsed_ansible_core_version)
155
+ ),
156
+ "python": " ".join(str(python) for python in python_versions),
157
+ }
158
+ if register_extra_data:
159
+ data.update(register_extra_data)
160
+ register(register_name, data)
161
+
162
+
163
+ def add_ansible_test_sanity_test_session(
164
+ *,
165
+ name: str,
166
+ description: str | None,
167
+ default: bool,
168
+ ansible_core_version: str | AnsibleCoreVersion,
169
+ ansible_core_source: t.Literal["git", "pypi"] = "git",
170
+ ansible_core_repo_name: str | None = None,
171
+ ansible_core_branch_name: str | None = None,
172
+ skip_tests: list[str] | None = None,
173
+ allow_disabled: bool = False,
174
+ enable_optional_errors: bool = False,
175
+ register_extra_data: dict[str, t.Any] | None = None,
176
+ ) -> None:
177
+ """
178
+ Add generic ansible-test sanity test session.
179
+ """
180
+ command = ["sanity", "--docker", "-v", "--color"]
181
+ if skip_tests:
182
+ for test in skip_tests:
183
+ command.extend(["--skip", test])
184
+ if allow_disabled:
185
+ command.append("--allow-disabled")
186
+ if enable_optional_errors:
187
+ command.append("--enable-optional-errors")
188
+ add_ansible_test_session(
189
+ name=name,
190
+ description=description,
191
+ ansible_test_params=command,
192
+ default=default,
193
+ ansible_core_version=ansible_core_version,
194
+ ansible_core_source=ansible_core_source,
195
+ ansible_core_repo_name=ansible_core_repo_name,
196
+ ansible_core_branch_name=ansible_core_branch_name,
197
+ register_name="sanity",
198
+ register_extra_data=register_extra_data,
199
+ )
200
+
201
+
202
+ def _parse_min_max_except(
203
+ min_version: Version | str | None,
204
+ max_version: Version | str | None,
205
+ except_versions: list[AnsibleCoreVersion | str] | None,
206
+ ) -> tuple[Version | None, Version | None, tuple[AnsibleCoreVersion, ...] | None]:
207
+ if isinstance(min_version, str):
208
+ min_version = Version.parse(min_version)
209
+ if isinstance(max_version, str):
210
+ max_version = Version.parse(max_version)
211
+ if except_versions is None:
212
+ return min_version, max_version, None
213
+ evs = tuple(_parse_ansible_core_version(version) for version in except_versions)
214
+ return min_version, max_version, evs
215
+
216
+
217
+ def add_all_ansible_test_sanity_test_sessions(
218
+ *,
219
+ default: bool = False,
220
+ include_devel: bool = False,
221
+ include_milestone: bool = False,
222
+ add_devel_like_branches: list[tuple[str | None, str]] | None = None,
223
+ min_version: Version | str | None = None,
224
+ max_version: Version | str | None = None,
225
+ except_versions: list[AnsibleCoreVersion | str] | None = None,
226
+ skip_tests: list[str] | None = None,
227
+ allow_disabled: bool = False,
228
+ enable_optional_errors: bool = False,
229
+ ) -> None:
230
+ """
231
+ Add ansible-test sanity test meta session that runs ansible-test sanity
232
+ for all supported ansible-core versions.
233
+ """
234
+ parsed_min_version, parsed_max_version, parsed_except_versions = (
235
+ _parse_min_max_except(min_version, max_version, except_versions)
236
+ )
237
+
238
+ sanity_sessions = []
239
+ for ansible_core_version in get_supported_core_versions(
240
+ include_devel=include_devel,
241
+ include_milestone=include_milestone,
242
+ min_version=parsed_min_version,
243
+ max_version=parsed_max_version,
244
+ except_versions=parsed_except_versions,
245
+ ):
246
+ name = f"ansible-test-sanity-{ansible_core_version}"
247
+ add_ansible_test_sanity_test_session(
248
+ name=name,
249
+ description=f"Run sanity tests from ansible-core {ansible_core_version}'s ansible-test",
250
+ ansible_core_version=ansible_core_version,
251
+ skip_tests=skip_tests,
252
+ allow_disabled=allow_disabled,
253
+ enable_optional_errors=enable_optional_errors,
254
+ register_extra_data={"display-name": f"Ⓐ{ansible_core_version}"},
255
+ default=False,
256
+ )
257
+ sanity_sessions.append(name)
258
+ if add_devel_like_branches:
259
+ for repo_name, branch_name in add_devel_like_branches:
260
+ repo_prefix = (
261
+ f"{repo_name.replace('/', '-')}-" if repo_name is not None else ""
262
+ )
263
+ repo_postfix = f", {repo_name} repository" if repo_name is not None else ""
264
+ name = f"ansible-test-sanity-{repo_prefix}{branch_name.replace('/', '-')}"
265
+ add_ansible_test_sanity_test_session(
266
+ name=name,
267
+ description=(
268
+ "Run sanity tests from ansible-test in ansible-core's"
269
+ f" {branch_name} branch{repo_postfix}"
270
+ ),
271
+ ansible_core_version="devel",
272
+ ansible_core_repo_name=repo_name,
273
+ ansible_core_branch_name=branch_name,
274
+ skip_tests=skip_tests,
275
+ allow_disabled=allow_disabled,
276
+ enable_optional_errors=enable_optional_errors,
277
+ register_extra_data={"display-name": f"Ⓐ{branch_name}"},
278
+ default=False,
279
+ )
280
+ sanity_sessions.append(name)
281
+
282
+ def run_all_sanity_tests(
283
+ session: nox.Session, # pylint: disable=unused-argument
284
+ ) -> None:
285
+ pass
286
+
287
+ run_all_sanity_tests.__doc__ = (
288
+ "Meta session for running all ansible-test-sanity-* sessions."
289
+ )
290
+ nox.session(
291
+ name="ansible-test-sanity",
292
+ default=default,
293
+ requires=sanity_sessions,
294
+ )(run_all_sanity_tests)
295
+
296
+
297
+ def add_ansible_test_unit_test_session(
298
+ *,
299
+ name: str,
300
+ description: str | None,
301
+ default: bool,
302
+ ansible_core_version: str | AnsibleCoreVersion,
303
+ ansible_core_source: t.Literal["git", "pypi"] = "git",
304
+ ansible_core_repo_name: str | None = None,
305
+ ansible_core_branch_name: str | None = None,
306
+ register_extra_data: dict[str, t.Any] | None = None,
307
+ ) -> None:
308
+ """
309
+ Add generic ansible-test unit test session.
310
+ """
311
+ add_ansible_test_session(
312
+ name=name,
313
+ description=description,
314
+ ansible_test_params=["units", "--docker", "-v", "--color"],
315
+ extra_deps_files=["tests/unit/requirements.yml"],
316
+ default=default,
317
+ ansible_core_version=ansible_core_version,
318
+ ansible_core_source=ansible_core_source,
319
+ ansible_core_repo_name=ansible_core_repo_name,
320
+ ansible_core_branch_name=ansible_core_branch_name,
321
+ register_name="units",
322
+ register_extra_data=register_extra_data,
323
+ )
324
+
325
+
326
+ def add_all_ansible_test_unit_test_sessions(
327
+ *,
328
+ default: bool = False,
329
+ include_devel: bool = False,
330
+ include_milestone: bool = False,
331
+ add_devel_like_branches: list[tuple[str | None, str]] | None = None,
332
+ min_version: Version | str | None = None,
333
+ max_version: Version | str | None = None,
334
+ except_versions: list[AnsibleCoreVersion | str] | None = None,
335
+ ) -> None:
336
+ """
337
+ Add ansible-test unit test meta session that runs ansible-test units
338
+ for all supported ansible-core versions.
339
+ """
340
+ parsed_min_version, parsed_max_version, parsed_except_versions = (
341
+ _parse_min_max_except(min_version, max_version, except_versions)
342
+ )
343
+
344
+ units_sessions = []
345
+ for ansible_core_version in get_supported_core_versions(
346
+ include_devel=include_devel,
347
+ include_milestone=include_milestone,
348
+ min_version=parsed_min_version,
349
+ max_version=parsed_max_version,
350
+ except_versions=parsed_except_versions,
351
+ ):
352
+ name = f"ansible-test-units-{ansible_core_version}"
353
+ add_ansible_test_unit_test_session(
354
+ name=name,
355
+ description=f"Run unit tests with ansible-core {ansible_core_version}'s ansible-test",
356
+ ansible_core_version=ansible_core_version,
357
+ register_extra_data={"display-name": f"Ⓐ{ansible_core_version}"},
358
+ default=False,
359
+ )
360
+ units_sessions.append(name)
361
+ if add_devel_like_branches:
362
+ for repo_name, branch_name in add_devel_like_branches:
363
+ repo_prefix = (
364
+ f"{repo_name.replace('/', '-')}-" if repo_name is not None else ""
365
+ )
366
+ repo_postfix = f", {repo_name} repository" if repo_name is not None else ""
367
+ name = f"ansible-test-units-{repo_prefix}{branch_name.replace('/', '-')}"
368
+ add_ansible_test_unit_test_session(
369
+ name=name,
370
+ description=(
371
+ "Run unit tests from ansible-test in ansible-core's"
372
+ f" {branch_name} branch{repo_postfix}"
373
+ ),
374
+ ansible_core_version="devel",
375
+ ansible_core_repo_name=repo_name,
376
+ ansible_core_branch_name=branch_name,
377
+ register_extra_data={"display-name": f"Ⓐ{branch_name}"},
378
+ default=False,
379
+ )
380
+ units_sessions.append(name)
381
+
382
+ def run_all_unit_tests(
383
+ session: nox.Session, # pylint: disable=unused-argument
384
+ ) -> None:
385
+ pass
386
+
387
+ run_all_unit_tests.__doc__ = (
388
+ "Meta session for running all ansible-test-units-* sessions."
389
+ )
390
+ nox.session(
391
+ name="ansible-test-units",
392
+ default=default,
393
+ requires=units_sessions,
394
+ )(run_all_unit_tests)
395
+
396
+
397
+ def add_ansible_test_integration_sessions_default_container(
398
+ *,
399
+ include_devel: bool = False,
400
+ include_milestone: bool = False,
401
+ add_devel_like_branches: list[tuple[str | None, str]] | None = None,
402
+ min_version: Version | str | None = None,
403
+ max_version: Version | str | None = None,
404
+ except_versions: list[AnsibleCoreVersion | str] | None = None,
405
+ core_python_versions: (
406
+ dict[str | AnsibleCoreVersion, list[str | Version]] | None
407
+ ) = None,
408
+ controller_python_versions_only: bool = False,
409
+ default: bool = False,
410
+ ) -> None:
411
+ """
412
+ Add ansible-test integration tests using the default Docker container.
413
+
414
+ ``core_python_versions`` can be used to restrict the Python versions
415
+ to be used for a specific ansible-core version.
416
+
417
+ ``controller_python_versions_only`` can be used to only run against
418
+ controller Python versions.
419
+ """
420
+
421
+ def add_integration_tests(
422
+ ansible_core_version: AnsibleCoreVersion,
423
+ repo_name: str | None = None,
424
+ branch_name: str | None = None,
425
+ ) -> list[str]:
426
+ # Determine Python versions to run tests for
427
+ py_versions = (
428
+ (core_python_versions.get(branch_name) if branch_name is not None else None)
429
+ or core_python_versions.get(ansible_core_version)
430
+ or core_python_versions.get(str(ansible_core_version))
431
+ if core_python_versions
432
+ else None
433
+ )
434
+ if py_versions is None:
435
+ core_info = get_ansible_core_info(ansible_core_version)
436
+ py_versions = list(
437
+ core_info.controller_python_versions
438
+ if controller_python_versions_only
439
+ else core_info.remote_python_versions
440
+ )
441
+
442
+ # Add sessions
443
+ integration_sessions_core: list[str] = []
444
+ if branch_name is None:
445
+ base_name = f"ansible-test-integration-{ansible_core_version}-"
446
+ else:
447
+ repo_prefix = (
448
+ f"{repo_name.replace('/', '-')}-" if repo_name is not None else ""
449
+ )
450
+ base_name = f"ansible-test-integration-{repo_prefix}{branch_name.replace('/', '-')}-"
451
+ for py_version in py_versions:
452
+ name = f"{base_name}{py_version}"
453
+ if branch_name is None:
454
+ description = (
455
+ f"Run integration tests from ansible-core {ansible_core_version}'s"
456
+ f" ansible-test with Python {py_version}"
457
+ )
458
+ else:
459
+ repo_postfix = (
460
+ f", {repo_name} repository" if repo_name is not None else ""
461
+ )
462
+ description = (
463
+ f"Run integration tests from ansible-test in ansible-core's {branch_name}"
464
+ f" branch{repo_postfix} with Python {py_version}"
465
+ )
466
+ add_ansible_test_session(
467
+ name=name,
468
+ description=description,
469
+ ansible_test_params=[
470
+ "integration",
471
+ "--docker",
472
+ "default",
473
+ "-v",
474
+ "--color",
475
+ "--python",
476
+ str(py_version),
477
+ ],
478
+ extra_deps_files=["tests/integration/requirements.yml"],
479
+ ansible_core_version=ansible_core_version,
480
+ ansible_core_repo_name=repo_name,
481
+ ansible_core_branch_name=branch_name,
482
+ default=False,
483
+ register_name="integration",
484
+ register_extra_data={
485
+ "display-name": f"Ⓐ{ansible_core_version}+py{py_version}+default"
486
+ },
487
+ )
488
+ integration_sessions_core.append(name)
489
+ return integration_sessions_core
490
+
491
+ parsed_min_version, parsed_max_version, parsed_except_versions = (
492
+ _parse_min_max_except(min_version, max_version, except_versions)
493
+ )
494
+ integration_sessions: list[str] = []
495
+ for ansible_core_version in get_supported_core_versions(
496
+ include_devel=include_devel,
497
+ include_milestone=include_milestone,
498
+ min_version=parsed_min_version,
499
+ max_version=parsed_max_version,
500
+ except_versions=parsed_except_versions,
501
+ ):
502
+ integration_sessions_core = add_integration_tests(ansible_core_version)
503
+ if integration_sessions_core:
504
+ name = f"ansible-test-integration-{ansible_core_version}"
505
+ integration_sessions.append(name)
506
+
507
+ def run_integration_tests(
508
+ session: nox.Session, # pylint: disable=unused-argument
509
+ ) -> None:
510
+ pass
511
+
512
+ run_integration_tests.__doc__ = (
513
+ f"Meta session for running all {name}-* sessions."
514
+ )
515
+ nox.session(
516
+ name=name,
517
+ requires=integration_sessions_core,
518
+ default=False,
519
+ )(run_integration_tests)
520
+ if add_devel_like_branches:
521
+ for repo_name, branch_name in add_devel_like_branches:
522
+ integration_sessions_core = add_integration_tests(
523
+ "devel", repo_name=repo_name, branch_name=branch_name
524
+ )
525
+ if integration_sessions_core:
526
+ repo_prefix = (
527
+ f"{repo_name.replace('/', '-')}-" if repo_name is not None else ""
528
+ )
529
+ name = f"ansible-test-integration-{repo_prefix}{branch_name.replace('/', '-')}"
530
+ integration_sessions.append(name)
531
+
532
+ def run_integration_tests_for_branch(
533
+ session: nox.Session, # pylint: disable=unused-argument
534
+ ) -> None:
535
+ pass
536
+
537
+ run_integration_tests_for_branch.__doc__ = (
538
+ f"Meta session for running all {name}-* sessions."
539
+ )
540
+ nox.session(
541
+ name=name,
542
+ requires=integration_sessions_core,
543
+ default=False,
544
+ )(run_integration_tests_for_branch)
545
+
546
+ def ansible_test_integration(
547
+ session: nox.Session, # pylint: disable=unused-argument
548
+ ) -> None:
549
+ pass
550
+
551
+ ansible_test_integration.__doc__ = (
552
+ "Meta session for running all ansible-test-integration-* sessions."
553
+ )
554
+ nox.session(
555
+ name="ansible-test-integration",
556
+ requires=integration_sessions,
557
+ default=default,
558
+ )(ansible_test_integration)
559
+
560
+
561
+ __all__ = [
562
+ "add_ansible_test_session",
563
+ "add_ansible_test_sanity_test_session",
564
+ "add_all_ansible_test_sanity_test_sessions",
565
+ "add_ansible_test_unit_test_session",
566
+ "add_all_ansible_test_unit_test_sessions",
567
+ "add_ansible_test_integration_sessions_default_container",
568
+ ]