diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 56f231d9578..ce3c7889f4b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -197,15 +197,15 @@ jobs: - name: Test all files (sage -t --all --long) if: (success() || failure()) && steps.build.outcome == 'success' run: | - ../sage -python -m pip install coverage - ../sage -python -m coverage run ./bin/sage-runtests --all --long -p2 --random-seed=286735480429121101562228604801325644303 - working-directory: ./worktree-image/src + ./sage -python -m pip install coverage + ./sage -python -m coverage run --rcfile=src/tox.ini src/bin/sage-runtests --all --long -p2 --format github --random-seed=286735480429121101562228604801325644303 + working-directory: ./worktree-image - name: Prepare coverage results if: (success() || failure()) && steps.build.outcome == 'success' run: | - ./venv/bin/python3 -m coverage combine src/.coverage/ - ./venv/bin/python3 -m coverage xml + ./sage -python -m coverage combine --rcfile=src/tox.ini + ./sage -python -m coverage xml --rcfile=src/tox.ini mkdir -p coverage-report mv coverage.xml coverage-report/ working-directory: ./worktree-image diff --git a/src/bin/sage-runtests b/src/bin/sage-runtests index 4a8b05cfab6..81efd01a4d8 100755 --- a/src/bin/sage-runtests +++ b/src/bin/sage-runtests @@ -34,6 +34,8 @@ if __name__ == "__main__": what.add_argument("--installed", action="store_true", default=False, help="test all installed modules of the Sage library") parser.add_argument("--logfile", type=argparse.FileType('a'), metavar="FILE", help="log all output to FILE") + parser.add_argument("--format", choices=["sage", "github"], default="sage", + help="set format of error messages and warnings") parser.add_argument("-l", "--long", action="store_true", default=False, help="include lines with the phrase 'long time'") parser.add_argument("-s", "--short", dest="target_walltime", nargs='?', type=int, default=-1, const=300, metavar="SECONDS", diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 1a128983187..396f5e28263 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -139,6 +139,7 @@ def __init__(self, **kwds): self.show_skipped = False self.target_walltime = -1 self.baseline_stats_path = None + self.format = "sage" # sage-runtests contains more optional tags. Technically, adding # auto_optional_tags here is redundant, since that is added diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index fce553543bb..9590c8a2faa 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -1235,16 +1235,57 @@ def _failure_header(self, test, example, message='Failed example:'): """ out = [self.DIVIDER] with OriginalSource(example): - if test.filename: - if test.lineno is not None and example.lineno is not None: - lineno = test.lineno + example.lineno + 1 + if self.options.format == 'sage': + if test.filename: + if test.lineno is not None and example.lineno is not None: + lineno = test.lineno + example.lineno + 1 + else: + lineno = '?' + out.append('File "%s", line %s, in %s' % + (test.filename, lineno, test.name)) + else: + out.append('Line %s, in %s' % (example.lineno + 1, test.name)) + out.append(message) + elif self.options.format == 'github': + # https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#using-workflow-commands-to-access-toolkit-functions + if message.startswith('Warning: '): + command = f'::warning title={message}' + message = message[len('Warning: '):] + elif self.baseline.get('failed', False): + command = f'::notice title={message}' + message += ' [failed in baseline]' else: - lineno = '?' - out.append('File "%s", line %s, in %s' % - (test.filename, lineno, test.name)) + command = f'::error title={message}' + if extra := getattr(example, 'extra', None): + message += f': {extra}' + if test.filename: + command += f',file={test.filename}' + if test.lineno is not None and example.lineno is not None: + lineno = test.lineno + example.lineno + 1 + command += f',line={lineno}' + lineno = None + else: + command += f',line={example.lineno + 1}' + # + # Urlencoding trick for multi-line annotations + # https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448 + # + # This only affects the display in the workflow Summary, after clicking "Show more"; + # the message needs to be long enough so that "Show more" becomes available. + # https://github.com/actions/toolkit/issues/193#issuecomment-1867084340 + # + # Unfortunately, this trick does not make the annotations in the diff view multi-line. + # + if '\n' in message: + message = message.replace('\n', '%0A') + # The actual threshold for "Show more" to appear depends on the window size. + show_more_threshold = 500 + if (pad := show_more_threshold - len(message)) > 0: + message += ' ' * pad + command += f'::{message}' + out.append(command) else: - out.append('Line %s, in %s' % (example.lineno + 1, test.name)) - out.append(message) + raise ValueError(f'unknown format option: {self.options.format}') source = example.source out.append(doctest._indent(source)) return '\n'.join(out) @@ -1417,6 +1458,7 @@ def report_failure(self, out, test, example, got, globs): """ if not self.options.initial or self.no_failure_yet: self.no_failure_yet = False + example.extra = f'Got: {got}' returnval = doctest.DocTestRunner.report_failure(self, out, test, example, got) if self.options.debug: self._fakeout.stop_spoofing() @@ -1567,6 +1609,8 @@ def report_unexpected_exception(self, out, test, example, exc_info): """ if not self.options.initial or self.no_failure_yet: self.no_failure_yet = False + + example.extra = "Exception raised:\n" + "".join(traceback.format_exception(*exc_info)) returnval = doctest.DocTestRunner.report_unexpected_exception(self, out, test, example, exc_info) if self.options.debug: self._fakeout.stop_spoofing() diff --git a/src/tox.ini b/src/tox.ini index 00f7a153f35..f2f1fc158fd 100644 --- a/src/tox.ini +++ b/src/tox.ini @@ -299,3 +299,7 @@ source = sage concurrency = multiprocessing data_file = .coverage/.coverage disable_warnings = no-data-collected + +[coverage:report] +ignore_errors = True +skip_empty = True