diff --git a/tests/project/test_unit_tests_push_reports_2030.py b/tests/project/test_unit_tests_push_reports_2030.py index c51a847d6e..989786219d 100644 --- a/tests/project/test_unit_tests_push_reports_2030.py +++ b/tests/project/test_unit_tests_push_reports_2030.py @@ -155,7 +155,8 @@ def nodes(*runs: 'tuple[str, str, list[tuple[str, ...]]]') -> 'list[dict[str, An ('NOTICE', '', 'The ubuntu-latest label will migrate to Ubuntu 26')]) #: A stand-in ``gh``: ``api graphql -f ...``, the check-suite query, answers -#: ``$FAKE_SUITE_ANSWER``; ``run download`` copies ``$FAKE_ARTIFACTS``' folders; +#: ``$FAKE_SUITE_ANSWER``; ``run download`` copies ``$FAKE_ARTIFACTS``' folders, +#: or, given ``say:TEXT``, fails printing ``TEXT``; #: both log their arguments to ``$FAKE_SUITE_LOG``. ``api graphql --input -``, the #: posting script's own calls, goes to the stand-in GitHub beside it. FAKE_GH = textwrap.dedent('''\ @@ -170,6 +171,9 @@ def nodes(*runs: 'tuple[str, str, list[tuple[str, ...]]]') -> 'list[dict[str, An if source == 'fail': print('HTTP 403: Resource not accessible by integration', file=sys.stderr) sys.exit(1) + if source.startswith('say:'): + print(source[4:], file=sys.stderr) + sys.exit(1) if not source or not os.listdir(source): print('no valid artifacts found to download', file=sys.stderr) sys.exit(1) @@ -729,6 +733,32 @@ def test_unreadable_summaries_warn(self) -> None: report) self.assertIn('::warning title=CI report not written::The job summaries could not be read', stdout) + def test_each_nothing_to_download_wording_is_no_summary(self) -> None: + # #2074: each way gh's pkg/cmd/run/download/download.go says it found nothing. + for text in ('no valid artifacts found to download', + 'no artifact matches any of the names or patterns provided', + 'no artifact matches any of the names provided'): + with self.subTest(text=text): + outputs, report, stdout = self.report('success', check_runs(GREEN), FAKE_ARTIFACTS=f'say:{text}') + self.assertEqual(outputs['post'], 'true') + self.assertNotIn('Job summaries', report) + self.assertNotIn('could not be read', report) + self.assertNotIn('::warning', stdout) + + def test_a_genuine_download_error_still_warns(self) -> None: + # The last holds a wording inside a longer line, which is not gh saying it. + for text in ('error fetching artifacts: HTTP 401: Bad credentials (https://api.github.com/repos/' + 'JarryShaw/PyPCAPKit/actions/runs/1/artifacts?per_page=100)', + 'error fetching artifacts: HTTP 404: Not Found (https://api.github.com/repos/' + 'JarryShaw/PyPCAPKit/actions/runs/1/artifacts?per_page=100)', + 'error fetching artifacts: Get "https://api.github.com/repos/JarryShaw/PyPCAPKit/actions/' + 'runs/1/artifacts?per_page=100": dial tcp: lookup api.github.com: no such host', + 'error downloading job-summary-test-0: unexpected EOF (not: no valid artifacts found to download)'): + with self.subTest(text=text): + _, report, stdout = self.report('success', check_runs(GREEN), FAKE_ARTIFACTS=f'say:{text}') + self.assertIn(f'The job summaries could not be read: `{text}`.', report) + self.assertIn('::warning title=CI report not written::The job summaries could not be read', stdout) + class TestTheDownload(unittest.TestCase): """:func:`check_suite_report.download` reads each artifact's folder, and one unpacked in place.""" diff --git a/util/check_suite_report.py b/util/check_suite_report.py index 80564d87b5..e009df28f7 100644 --- a/util/check_suite_report.py +++ b/util/check_suite_report.py @@ -128,6 +128,15 @@ class Workflow(NamedTuple): ARTIFACT = 'job-summary-*' SHA = re.compile(r'[0-9a-f]{40}') RUN_PAGE = re.compile(r'https://[\w.-]+/[\w.-]+/[\w.-]+/actions/runs/\d+') +#: What ``gh run download`` says, failing, where it found nothing to download, as +#: :file:`pkg/cmd/run/download/download.go` in cli/cli writes each (#2074): a run +#: with no artifact that has not expired, and one with none :data:`ARTIFACT` +#: matches, worded the latter way since gh 2.11.0 and the former before it. +NOTHING = frozenset({ + 'no valid artifacts found to download', + 'no artifact matches any of the names or patterns provided', + 'no artifact matches any of the names provided', +}) #: A mention, which a summary's text must not make. MENTION = re.compile(r'@(?=[A-Za-z0-9])') #: What the report joins its job summaries' blocks by. @@ -237,8 +246,11 @@ def download(repo: 'str', run_id: 'str', directory: 'pathlib.Path') -> 'tuple[li return [], f'gh could not run: {error}' if proc.returncode: text = proc.stderr.strip() or proc.stdout.strip() - # A run whose jobs wrote no summary uploaded no artifact, which gh reports so. - return [], '' if 'no valid artifacts' in text else (text.splitlines() or ['gh failed'])[-1] + # A run whose jobs wrote no summary uploaded no artifact it matches, which + # gh reports as one of NOTHING's lines; anything else is an error. + if any(line.strip() in NOTHING for line in text.splitlines()): + return [], '' + return [], (text.splitlines() or ['gh failed'])[-1] found = [] # gh puts each artifact a pattern matches in a folder of its name; a lone # one unpacked into the directory itself is read there too.