|
1 | 1 | # -*- coding: utf-8 -*- |
2 | | -"""#1967: :file:`util/pip_install.py`, the install step of most ``unit-tests.yml`` jobs, run as the steps run it. |
| 2 | +"""#1967: :file:`util/pip_install.py`, the install step of most test jobs, run as the steps run it. |
3 | 3 |
|
4 | 4 | * :class:`TestPipInstall` runs the script against a stand-in ``pip``: a clean |
5 | 5 | install, a failed one with and without an ``ERROR:`` line, a failed upgrade, |
|
11 | 11 | * :class:`TestTheStepsRunTheScript` pins each covered step to the script, with |
12 | 12 | its guards and environment, and every other ``pip install`` in |
13 | 13 | ``unit-tests.yml`` to a listed step that keeps its shell. |
| 14 | +* :class:`TestTheOtherWorkflowsRunTheScript` does the same for the install of |
| 15 | + ``experimental.yml``'s ``experimental`` and ``python-compatibility.yml``'s |
| 16 | + ``compatibility``. |
14 | 17 |
|
15 | 18 | """ |
16 | 19 |
|
|
29 | 32 | import unittest |
30 | 33 |
|
31 | 34 | from tests._dependency_gates import EDITABLE_INSTALL |
32 | | -from tests._workflows import job, load, steps |
| 35 | +from tests._workflows import WORKFLOWS, job, load, steps |
33 | 36 |
|
34 | 37 | ROOT = pathlib.Path(__file__).resolve().parents[2] |
35 | 38 | SCRIPT = ROOT / 'util' / 'pip_install.py' |
36 | | -WORKFLOW = ROOT / '.github' / 'workflows' / 'unit-tests.yml' |
| 39 | +WORKFLOW = WORKFLOWS / 'unit-tests.yml' |
37 | 40 | LOG = 'pip-install.log' |
38 | 41 | SPEC = '.[test,DPKT]' |
39 | 42 |
|
|
72 | 75 | TWINS = {'test-ktlo': 'test', 'integration-ktlo': 'integration', 'pypcap-parity-ktlo': 'pypcap-parity'} |
73 | 76 | #: Text only the former inline install held. |
74 | 77 | INLINE = ('pip-install.log', 'did not install on', "s/^ERROR: //p") |
| 78 | +#: The covered steps outside ``unit-tests.yml``, as ``(workflow, job)``, each step whole, |
| 79 | +#: and its job's ``ANNOTATION_LEVEL``. The experimental legs are never blocking, so |
| 80 | +#: always ``warning``; Compat's KTLO legs take ``warning`` as ``test``'s do. Compat |
| 81 | +#: installs no extra, and its step keeps the default shell. |
| 82 | +ELSEWHERE = { |
| 83 | + ('experimental.yml', 'experimental'): ({ |
| 84 | + 'name': 'Install package and test dependencies', |
| 85 | + 'id': 'install', |
| 86 | + 'if': "${{ steps.python.outcome == 'success' }}", |
| 87 | + 'continue-on-error': True, |
| 88 | + 'timeout-minutes': 10, |
| 89 | + 'shell': 'bash', |
| 90 | + 'env': {'LEG': 'Python ${{ matrix.python-version }} (experimental, non-blocking)'}, |
| 91 | + 'run': "python util/pip_install.py -e '.[test,DPKT,crypto,NGAP]'", |
| 92 | + }, 'warning'), |
| 93 | + ('python-compatibility.yml', 'compatibility'): ({ |
| 94 | + 'name': 'Install package', |
| 95 | + 'id': 'install', |
| 96 | + 'if': "${{ steps.python.outcome == 'success' }}", |
| 97 | + 'continue-on-error': '${{ matrix.ktlo == true }}', |
| 98 | + 'env': {'LEG': MATRIX_LEG}, |
| 99 | + 'run': 'python util/pip_install.py -e .', |
| 100 | + }, MATRIX_LEVEL), |
| 101 | +} |
| 102 | +#: The other ``pip install`` steps of those workflows, which install ``requests`` for |
| 103 | +#: util/superseded.py on an interpreter of their own, never the package. |
| 104 | +ELSEWHERE_INLINE = frozenset({ |
| 105 | + ('experimental.yml', 'current', 'Install requests'), |
| 106 | + ('experimental.yml', 'experimental', "Install the watcher's requests"), |
| 107 | +}) |
75 | 108 |
|
76 | 109 | #: A stand-in ``pip``: it appends its arguments to ``calls``, then does what |
77 | 110 | #: ``PIP_<STEP>`` says, ``STEP`` ``UPGRADE`` or ``INSTALL``: ``OUTPUT`` lines, each |
@@ -368,5 +401,41 @@ def test_the_listed_steps_still_install(self) -> None: |
368 | 401 | self.assertIn('pip install', str(self.step(job_id, name)['run'])) |
369 | 402 |
|
370 | 403 |
|
| 404 | +class TestTheOtherWorkflowsRunTheScript(unittest.TestCase): |
| 405 | + """The experimental and Compat installs run the script, guards unchanged; nothing else there keeps the shell.""" |
| 406 | + |
| 407 | + def test_each_site_is_the_script_with_its_guards(self) -> None: |
| 408 | + for (name, job_id), (expected, _) in ELSEWHERE.items(): |
| 409 | + with self.subTest(workflow=name, job=job_id): |
| 410 | + found = [entry for entry in steps(WORKFLOWS / name, job_id) if entry.get('name') == expected['name']] |
| 411 | + self.assertEqual(found, [expected]) |
| 412 | + |
| 413 | + def test_each_job_sets_the_level(self) -> None: |
| 414 | + for (name, job_id), (_, level) in ELSEWHERE.items(): |
| 415 | + with self.subTest(workflow=name, job=job_id): |
| 416 | + self.assertEqual(job(WORKFLOWS / name, job_id)['env']['ANNOTATION_LEVEL'], level) |
| 417 | + |
| 418 | + def test_every_other_pip_install_is_a_listed_one(self) -> None: |
| 419 | + sites = {(name, job_id, expected['name']) for (name, job_id), (expected, _) in ELSEWHERE.items()} |
| 420 | + found = [] |
| 421 | + for name in sorted({name for name, _ in ELSEWHERE}): |
| 422 | + for job_id, body in load(WORKFLOWS / name)['jobs'].items(): |
| 423 | + for step in body.get('steps', []): |
| 424 | + where = (name, job_id, step.get('name')) |
| 425 | + run = str(step.get('run', '')) |
| 426 | + if re.search(r'pip install|pip_install\.py', run) and where not in sites | ELSEWHERE_INLINE: |
| 427 | + found.append(f'{name} {job_id} {step.get("name")!r}') |
| 428 | + found.extend(f'{name} {job_id} {step.get("name")!r}: {piece}' for piece in INLINE if piece in run) |
| 429 | + self.assertEqual(found, []) |
| 430 | + |
| 431 | + def test_the_listed_steps_still_install(self) -> None: |
| 432 | + """So :data:`ELSEWHERE_INLINE` names nothing stale.""" |
| 433 | + for name, job_id, step_name in ELSEWHERE_INLINE: |
| 434 | + with self.subTest(workflow=name, job=job_id, step=step_name): |
| 435 | + found = [entry for entry in steps(WORKFLOWS / name, job_id) if entry.get('name') == step_name] |
| 436 | + self.assertEqual(len(found), 1) |
| 437 | + self.assertIn('pip install', str(found[0]['run'])) |
| 438 | + |
| 439 | + |
371 | 440 | if __name__ == '__main__': |
372 | 441 | unittest.main() |
0 commit comments