From 7f982bb0591ea5f34e72ce8893f319fc0df0fead Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 20 Dec 2023 14:07:05 -0800 Subject: [PATCH 01/26] .github/workflows/ci-conda-known-test-failures.json: Add random failure https://github.com/sagemath/sage/issues/36832 --- .github/workflows/ci-conda-known-test-failures.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci-conda-known-test-failures.json b/.github/workflows/ci-conda-known-test-failures.json index 2d828ac98fb..4680c5e7c81 100644 --- a/.github/workflows/ci-conda-known-test-failures.json +++ b/.github/workflows/ci-conda-known-test-failures.json @@ -47,6 +47,9 @@ "sage.rings.polynomial.skew_polynomial_finite_field": { "failed": true }, + "sage.schemes.elliptic_curves.ell_field": { + "failed": true + }, "sage.tests.gap_packages": { "failed": true } From 48cde676c8b0a94df9d40f6fa56ebc43227694a7 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 20 Dec 2023 13:38:10 -0800 Subject: [PATCH 02/26] src/sage/doctest/forker.py: Show '# [failed in baseline]' earlier --- src/sage/doctest/forker.py | 41 +++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 4315cd1ba9f..12b4bdfaa80 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -513,7 +513,7 @@ def __init__(self, *args, **kwds): - ``stdout`` -- an open file to restore for debugging - - ``checker`` -- None, or an instance of + - ``checker`` -- ``None``, or an instance of :class:`doctest.OutputChecker` - ``verbose`` -- boolean, determines whether verbose printing @@ -522,6 +522,9 @@ def __init__(self, *args, **kwds): - ``optionflags`` -- Controls the comparison with the expected output. See :mod:`testmod` for more information. + - ``baseline`` -- ``None`` or a dictionary, the ``baseline_stats`` + value + EXAMPLES:: sage: from sage.doctest.parsing import SageOutputChecker @@ -535,6 +538,9 @@ def __init__(self, *args, **kwds): O = kwds.pop('outtmpfile', None) self.msgfile = kwds.pop('msgfile', None) self.options = kwds.pop('sage_options') + self.baseline = kwds.pop('baseline', None) + if self.baseline is None: + self.baseline = {} doctest.DocTestRunner.__init__(self, *args, **kwds) self._fakeout = SageSpoofInOut(O) if self.msgfile is None: @@ -1721,12 +1727,20 @@ def serial_dispatch(self): """ for source in self.controller.sources: heading = self.controller.reporter.report_head(source) + basename = source.basename + if self.controller.baseline_stats: + the_baseline_stats = self.controller.baseline_stats.get(basename, {}) + else: + the_baseline_stats = {} + if the_baseline_stats.get('failed', False): + heading += " # [failed in baseline]" if not self.controller.options.only_errors: self.controller.log(heading) with tempfile.TemporaryFile() as outtmpfile: result = DocTestTask(source)(self.controller.options, - outtmpfile, self.controller.logger) + outtmpfile, self.controller.logger, + baseline=the_baseline_stats) outtmpfile.seek(0) output = bytes_to_str(outtmpfile.read()) @@ -1984,10 +1998,17 @@ def sel_exit(): # Start a new worker. import copy worker_options = copy.copy(opt) + basename = source.basename + if self.controller.baseline_stats: + the_baseline_stats = self.controller.baseline_stats.get(basename, {}) + else: + the_baseline_stats = {} if target_endtime is not None: worker_options.target_walltime = (target_endtime - now) / (max(1, pending_tests / opt.nthreads)) - w = DocTestWorker(source, options=worker_options, funclist=[sel_exit]) + w = DocTestWorker(source, options=worker_options, baseline=the_baseline_stats, funclist=[sel_exit]) heading = self.controller.reporter.report_head(w.source) + if the_baseline_stats.get('failed', False): + heading += " # [failed in baseline]" if not self.controller.options.only_errors: w.messages = heading + "\n" # Store length of heading to detect if the @@ -2146,7 +2167,7 @@ class should be accessed by the child process. sage: reporter.report(FDS, False, W.exitcode, result, "") [... tests, ... s] """ - def __init__(self, source, options, funclist=[]): + def __init__(self, source, options, funclist=[], baseline=None): """ Initialization. @@ -2170,6 +2191,7 @@ def __init__(self, source, options, funclist=[]): self.source = source self.options = options self.funclist = funclist + self.baseline = baseline # Open pipe for messages. These are raw file descriptors, # not Python file objects! @@ -2241,7 +2263,7 @@ def run(self): os.close(self.rmessages) msgpipe = os.fdopen(self.wmessages, "w") try: - task(self.options, self.outtmpfile, msgpipe, self.result_queue) + task(self.options, self.outtmpfile, msgpipe, self.result_queue, baseline=self.baseline) finally: msgpipe.close() self.outtmpfile.close() @@ -2507,7 +2529,8 @@ def __init__(self, source): """ self.source = source - def __call__(self, options, outtmpfile=None, msgfile=None, result_queue=None): + def __call__(self, options, outtmpfile=None, msgfile=None, result_queue=None, *, + baseline=None): """ Calling the task does the actual work of running the doctests. @@ -2524,6 +2547,9 @@ def __call__(self, options, outtmpfile=None, msgfile=None, result_queue=None): - ``result_queue`` -- an instance of :class:`multiprocessing.Queue` to store the doctest result. For testing, this can also be None. + - ``baseline`` -- ``None`` or a dictionary, the ``baseline_stats`` + value. + OUTPUT: - ``(doctests, result_dict)`` where ``doctests`` is the number of @@ -2559,7 +2585,8 @@ def __call__(self, options, outtmpfile=None, msgfile=None, result_queue=None): outtmpfile=outtmpfile, msgfile=msgfile, sage_options=options, - optionflags=doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS) + optionflags=doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS, + baseline=baseline) runner.basename = self.source.basename runner.filename = self.source.path N = options.file_iterations From d52858cd55f394f8b9287ed74cc0d5935394c663 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 20 Dec 2023 15:30:09 -0800 Subject: [PATCH 03/26] Refactor through new function DocTestController.source_baseline --- src/sage/doctest/control.py | 28 ++++++++++++++++++++++++++++ src/sage/doctest/forker.py | 35 +++++++++++++---------------------- src/sage/doctest/reporting.py | 17 +++++++---------- 3 files changed, 48 insertions(+), 32 deletions(-) diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index fd92dc386bf..37562e1717c 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -1097,6 +1097,34 @@ def sort_key(source): return -self.stats.get(basename, default).get('walltime', 0), basename self.sources = sorted(self.sources, key=sort_key) + def source_baseline(self, source): + r""" + Return the ``baseline_stats`` value of ``source``. + + INPUT: + + - ``source`` -- a :class:`DocTestSource` instance + + OUTPUT: + + A dictionary. + + EXAMPLES:: + + sage: from sage.doctest.control import DocTestDefaults, DocTestController + sage: from sage.env import SAGE_SRC + sage: import os + sage: filename = os.path.join(SAGE_SRC,'sage','doctest','util.py') + sage: DD = DocTestDefaults() + sage: DC = DocTestController(DD, [filename]) + sage: DC.source_baseline(DC.sources[0]) + {} + """ + if self.baseline_stats: + basename = source.basename + return self.baseline_stats.get(basename, {}) + return {} + def run_doctests(self): """ Actually runs the doctests. diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 12b4bdfaa80..cb8348e2732 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -522,8 +522,7 @@ def __init__(self, *args, **kwds): - ``optionflags`` -- Controls the comparison with the expected output. See :mod:`testmod` for more information. - - ``baseline`` -- ``None`` or a dictionary, the ``baseline_stats`` - value + - ``baseline`` -- dictionary, the ``baseline_stats`` value EXAMPLES:: @@ -538,9 +537,7 @@ def __init__(self, *args, **kwds): O = kwds.pop('outtmpfile', None) self.msgfile = kwds.pop('msgfile', None) self.options = kwds.pop('sage_options') - self.baseline = kwds.pop('baseline', None) - if self.baseline is None: - self.baseline = {} + self.baseline = kwds.pop('baseline', {}) doctest.DocTestRunner.__init__(self, *args, **kwds) self._fakeout = SageSpoofInOut(O) if self.msgfile is None: @@ -1727,12 +1724,8 @@ def serial_dispatch(self): """ for source in self.controller.sources: heading = self.controller.reporter.report_head(source) - basename = source.basename - if self.controller.baseline_stats: - the_baseline_stats = self.controller.baseline_stats.get(basename, {}) - else: - the_baseline_stats = {} - if the_baseline_stats.get('failed', False): + baseline = self.controller.source_baseline(source) + if baseline.get('failed', False): heading += " # [failed in baseline]" if not self.controller.options.only_errors: self.controller.log(heading) @@ -1740,7 +1733,7 @@ def serial_dispatch(self): with tempfile.TemporaryFile() as outtmpfile: result = DocTestTask(source)(self.controller.options, outtmpfile, self.controller.logger, - baseline=the_baseline_stats) + baseline=baseline) outtmpfile.seek(0) output = bytes_to_str(outtmpfile.read()) @@ -1998,16 +1991,12 @@ def sel_exit(): # Start a new worker. import copy worker_options = copy.copy(opt) - basename = source.basename - if self.controller.baseline_stats: - the_baseline_stats = self.controller.baseline_stats.get(basename, {}) - else: - the_baseline_stats = {} + baseline = self.controller.source_baseline(source) if target_endtime is not None: worker_options.target_walltime = (target_endtime - now) / (max(1, pending_tests / opt.nthreads)) - w = DocTestWorker(source, options=worker_options, baseline=the_baseline_stats, funclist=[sel_exit]) + w = DocTestWorker(source, options=worker_options, funclist=[sel_exit], baseline=baseline) heading = self.controller.reporter.report_head(w.source) - if the_baseline_stats.get('failed', False): + if baseline.get('failed', False): heading += " # [failed in baseline]" if not self.controller.options.only_errors: w.messages = heading + "\n" @@ -2148,6 +2137,8 @@ class should be accessed by the child process. - ``funclist`` -- a list of callables to be called at the start of the child process. + - ``baseline`` -- dictionary, the ``baseline_stats`` value + EXAMPLES:: sage: from sage.doctest.forker import DocTestWorker, DocTestTask @@ -2263,7 +2254,8 @@ def run(self): os.close(self.rmessages) msgpipe = os.fdopen(self.wmessages, "w") try: - task(self.options, self.outtmpfile, msgpipe, self.result_queue, baseline=self.baseline) + task(self.options, self.outtmpfile, msgpipe, self.result_queue, + baseline=self.baseline) finally: msgpipe.close() self.outtmpfile.close() @@ -2547,8 +2539,7 @@ def __call__(self, options, outtmpfile=None, msgfile=None, result_queue=None, *, - ``result_queue`` -- an instance of :class:`multiprocessing.Queue` to store the doctest result. For testing, this can also be None. - - ``baseline`` -- ``None`` or a dictionary, the ``baseline_stats`` - value. + - ``baseline`` -- a dictionary, the ``baseline_stats`` value. OUTPUT: diff --git a/src/sage/doctest/reporting.py b/src/sage/doctest/reporting.py index a86153ce326..dcc0dee792c 100644 --- a/src/sage/doctest/reporting.py +++ b/src/sage/doctest/reporting.py @@ -399,10 +399,7 @@ def report(self, source, timeout, return_code, results, output, pid=None): postscript = self.postscript stats = self.stats basename = source.basename - if self.controller.baseline_stats: - the_baseline_stats = self.controller.baseline_stats.get(basename, {}) - else: - the_baseline_stats = {} + baseline = self.controller.source_baseline(source) cmd = self.report_head(source) try: ntests, result_dict = results @@ -423,14 +420,14 @@ def report(self, source, timeout, return_code, results, output, pid=None): fail_msg += " (and interrupt failed)" else: fail_msg += " (with %s after interrupt)" % signal_name(sig) - if the_baseline_stats.get('failed', False): + if baseline.get('failed', False): fail_msg += " [failed in baseline]" log(" %s\n%s\nTests run before %s timed out:" % (fail_msg, "*"*70, process_name)) log(output) log("*"*70) postscript['lines'].append(cmd + " # %s" % fail_msg) stats[basename] = {"failed": True, "walltime": 1e6, "ntests": ntests} - if not the_baseline_stats.get('failed', False): + if not baseline.get('failed', False): self.error_status |= 4 elif return_code: if return_code > 0: @@ -439,14 +436,14 @@ def report(self, source, timeout, return_code, results, output, pid=None): fail_msg = "Killed due to %s" % signal_name(-return_code) if ntests > 0: fail_msg += " after testing finished" - if the_baseline_stats.get('failed', False): + if baseline.get('failed', False): fail_msg += " [failed in baseline]" log(" %s\n%s\nTests run before %s failed:" % (fail_msg,"*"*70, process_name)) log(output) log("*"*70) postscript['lines'].append(cmd + " # %s" % fail_msg) stats[basename] = {"failed": True, "walltime": 1e6, "ntests": ntests} - if not the_baseline_stats.get('failed', False): + if not baseline.get('failed', False): self.error_status |= (8 if return_code > 0 else 16) else: if hasattr(result_dict, 'walltime') and hasattr(result_dict.walltime, '__len__') and len(result_dict.walltime) > 0: @@ -509,10 +506,10 @@ def report(self, source, timeout, return_code, results, output, pid=None): f = result_dict.failures if f: fail_msg = "%s failed" % (count_noun(f, "doctest")) - if the_baseline_stats.get('failed', False): + if baseline.get('failed', False): fail_msg += " [failed in baseline]" postscript['lines'].append(cmd + " # %s" % fail_msg) - if not the_baseline_stats.get('failed', False): + if not baseline.get('failed', False): self.error_status |= 1 if f or result_dict.err == 'tab': stats[basename] = {"failed": True, "walltime": wall, "ntests": ntests} From 1849493abcd1231647a878ecd8be373ebd038f39 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 20 Dec 2023 12:31:24 -0800 Subject: [PATCH 04/26] WIP --- .github/workflows/build.yml | 80 +----------------------- .github/workflows/ci-conda.yml | 104 -------------------------------- src/bin/sage-runtests | 2 + src/sage/combinat/words/word.py | 4 +- src/sage/doctest/control.py | 1 + src/sage/doctest/forker.py | 47 ++++++++++++--- src/sage_setup/clean.py | 2 +- 7 files changed, 45 insertions(+), 195 deletions(-) delete mode 100644 .github/workflows/ci-conda.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c9282086422..82c2f19dc8f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -96,39 +96,6 @@ jobs: MAKE: make -j2 --output-sync=recurse SAGE_NUM_THREADS: 2 - - name: Build modularized distributions - if: (success() || failure()) && steps.worktree.outcome == 'success' - run: make V=0 tox && make SAGE_CHECK=no pypi-wheels - working-directory: ./worktree-image - env: - MAKE: make -j2 --output-sync=recurse - SAGE_NUM_THREADS: 2 - - - name: Static code check with pyright - if: (success() || failure()) && steps.worktree.outcome == 'success' - uses: jakebailey/pyright-action@v1 - with: - version: 1.1.332 - # Many warnings issued by pyright are not yet helpful because there is not yet enough type information. - no-comments: true - working-directory: ./worktree-image - env: - # To avoid out of memory errors - NODE_OPTIONS: --max-old-space-size=8192 - - - name: Static code check with pyright (annotated) - if: (success() || failure()) && steps.worktree.outcome == 'success' - uses: jakebailey/pyright-action@v1 - with: - version: 1.1.332 - # Issue errors - no-comments: false - level: error - working-directory: ./worktree-image - env: - # To avoid out of memory errors - NODE_OPTIONS: --max-old-space-size=8192 - - name: Clean (fallback to non-incremental) id: clean if: (success() || failure()) && steps.worktree.outcome == 'success' && steps.incremental.outcome != 'success' @@ -154,52 +121,9 @@ jobs: # Testing - - name: Test changed files (sage -t --new) - if: (success() || failure()) && steps.build.outcome == 'success' - run: | - # We run tests with "sage -t --new"; this only tests the uncommitted changes. - ./sage -t --new -p2 - working-directory: ./worktree-image - env: - MAKE: make -j2 --output-sync=recurse - SAGE_NUM_THREADS: 2 - - - name: Test modularized distributions - if: (success() || failure()) && steps.build.outcome == 'success' - run: make V=0 tox && make pypi-wheels-check - working-directory: ./worktree-image - env: - MAKE: make -j2 --output-sync=recurse - SAGE_NUM_THREADS: 2 - - - name: Pytest - if: contains(github.ref, 'pytest') - run: | - ../sage -python -m pip install coverage pytest-xdist - ../sage -python -m coverage run -m pytest -c tox.ini --doctest-modules || true - working-directory: ./worktree-image/src - env: - # Increase the length of the lines in the "short summary" - COLUMNS: 120 - - 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 - - - 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 - mkdir -p coverage-report - mv coverage.xml coverage-report/ + ./sage -python -m pip install coverage + ./sage -python -m coverage run ./src/bin/sage-runtests --format github --baseline-stats-path=.github/workflows/ci-conda-known-test-failures.json -p4 --random-seed=286735480429121101562228604801325644303 src/sage/combinat/words src/sage/plot/plot.py src/sage_setup/clean.py working-directory: ./worktree-image - - - name: Upload coverage to codecov - if: (success() || failure()) && steps.build.outcome == 'success' - uses: codecov/codecov-action@v3 - with: - directory: ./worktree-image/coverage-report diff --git a/.github/workflows/ci-conda.yml b/.github/workflows/ci-conda.yml deleted file mode 100644 index 6432b7a0a6a..00000000000 --- a/.github/workflows/ci-conda.yml +++ /dev/null @@ -1,104 +0,0 @@ -name: Build & Test using Conda - -on: - push: - tags: - - '*' - branches: - - 'public/build/**-runci' - pull_request: - workflow_dispatch: - # Allow to run manually - -concurrency: - # Cancel previous runs of this workflow for the same branch - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - test: - name: Conda - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: [ubuntu-latest, macos-latest] - python: ['3.9', '3.10', '3.11'] - # Optional environment is disabled for now as its not yet working - # environment: [environment, environment-optional] - conda-env: [environment] - - steps: - - uses: actions/checkout@v4 - - - name: Merge CI fixes from sagemath/sage - run: | - .ci/merge-fixes.sh - env: - GH_TOKEN: ${{ github.token }} - SAGE_CI_FIXES_FROM_REPOSITORIES: ${{ vars.SAGE_CI_FIXES_FROM_REPOSITORIES }} - - - name: Create conda environment files - run: ./bootstrap-conda - - - name: Cache conda packages - uses: actions/cache@v3 - with: - path: ~/conda_pkgs_dir - key: - ${{ runner.os }}-conda-${{ hashFiles('src/environment-3.11.yml') }} - - - name: Setup Conda environment - uses: conda-incubator/setup-miniconda@v2 - with: - python-version: ${{ matrix.python }} - miniforge-version: latest - use-mamba: true - channels: conda-forge - channel-priority: true - activate-environment: sage - environment-file: src/${{ matrix.conda-env }}-${{ matrix.python }}.yml - - - name: Print Conda environment - shell: bash -l {0} - run: | - conda info - conda list - - - name: Configure - shell: bash -l {0} - continue-on-error: true - run: | - ./bootstrap - echo "::add-matcher::.github/workflows/configure-systempackage-problem-matcher.json" - ./configure --enable-build-as-root --with-python=$CONDA_PREFIX/bin/python --prefix=$CONDA_PREFIX --enable-system-site-packages $(for pkg in $(./sage -package list :standard: --has-file spkg-configure.m4 --has-file distros/conda.txt --exclude rpy2); do echo --with-system-$pkg=force; done) - echo "::remove-matcher owner=configure-system-package-warning::" - echo "::remove-matcher owner=configure-system-package-error::" - - - name: Build - shell: bash -l {0} - run: | - # Use --no-deps and pip check below to verify that all necessary dependencies are installed via conda. - pip install --no-build-isolation --no-deps -v -v -e ./pkgs/sage-conf ./pkgs/sage-setup - pip install --no-build-isolation --no-deps --config-settings editable_mode=compat -v -v -e ./src - env: - SAGE_NUM_THREADS: 2 - - - name: Verify dependencies - if: success() || failure() - shell: bash -l {0} - run: pip check - - - name: Test - if: success() || failure() - shell: bash -l {0} - run: ./sage -t --all --baseline-stats-path=.github/workflows/ci-conda-known-test-failures.json -p0 - - - name: Print logs - if: always() - run: | - for file in $(find . -type f -name "*.log"); do - echo "::group::$file" - cat "$file" - echo "::endgroup::" - done diff --git a/src/bin/sage-runtests b/src/bin/sage-runtests index e25cebb48a8..25444933bf6 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/combinat/words/word.py b/src/sage/combinat/words/word.py index 132195589e9..240e66b6a24 100644 --- a/src/sage/combinat/words/word.py +++ b/src/sage/combinat/words/word.py @@ -148,7 +148,7 @@ def Word(data=None, alphabet=None, length=None, datatype=None, caching=True, RSK Word over a string with a parent:: - sage: w = Word("abbabaab", alphabet="abc"); w + sage: w = Word("abbabaab", daslkdlsakdlksaldkasldklsakdlsak, alphabet="abc"); w word: abbabaab sage: w.parent() Finite words over {'a', 'b', 'c'} @@ -253,7 +253,7 @@ class FiniteWord_char(WordDatatype_char, FiniteWord_class): True sage: w.is_square_free() - False + Falsish sage: w[:-1].is_square_free() True diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 37562e1717c..01a9e080384 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 cb8348e2732..a6b43b37aa2 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -1235,16 +1235,40 @@ 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 - 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) + match self.options.format: + case '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) + case '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: + 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}' + command += f'::{message}' + out.append(command) source = example.source out.append(doctest._indent(source)) return '\n'.join(out) @@ -1417,6 +1441,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!r}' returnval = doctest.DocTestRunner.report_failure(self, out, test, example, got) if self.options.debug: self._fakeout.stop_spoofing() @@ -1567,6 +1592,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: " + repr("".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/sage_setup/clean.py b/src/sage_setup/clean.py index e9c81c9ed1a..bac2ed26417 100644 --- a/src/sage_setup/clean.py +++ b/src/sage_setup/clean.py @@ -101,7 +101,7 @@ def _find_stale_files(site_packages, python_packages, python_modules, ext_module sage: stale_iter = _find_stale_files(SAGE_LIB, python_packages, python_modules, [], extra_files) sage: from importlib.machinery import EXTENSION_SUFFIXES sage: skip_extensions = tuple(EXTENSION_SUFFIXES) - sage: for f in stale_iter: + sage: for f in stale_iter:dxfcgvhbjkl;,mkjnbhgvcfgvhbjnkml, ....: if f.endswith(skip_extensions): continue ....: if '/ext_data/' in f: continue ....: print('Found stale file: ' + f) From 8aca1adf8f09988c47533963851c1606756fbff8 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 20 Dec 2023 17:46:37 -0800 Subject: [PATCH 05/26] sage -t: Add option '--format github' --- src/bin/sage-runtests | 2 ++ src/sage/doctest/control.py | 1 + src/sage/doctest/forker.py | 47 +++++++++++++++++++++++++++++-------- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/bin/sage-runtests b/src/bin/sage-runtests index e25cebb48a8..25444933bf6 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 37562e1717c..01a9e080384 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 cb8348e2732..a6b43b37aa2 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -1235,16 +1235,40 @@ 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 - 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) + match self.options.format: + case '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) + case '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: + 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}' + command += f'::{message}' + out.append(command) source = example.source out.append(doctest._indent(source)) return '\n'.join(out) @@ -1417,6 +1441,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!r}' returnval = doctest.DocTestRunner.report_failure(self, out, test, example, got) if self.options.debug: self._fakeout.stop_spoofing() @@ -1567,6 +1592,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: " + repr("".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() From 72ac0ece1e818ba637f28acbf3e8528d600d7f13 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 20 Dec 2023 18:19:24 -0800 Subject: [PATCH 06/26] .github/workflows/build.yml: Use sage -t --format github --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c9282086422..2b87d6edb60 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -158,7 +158,7 @@ jobs: if: (success() || failure()) && steps.build.outcome == 'success' run: | # We run tests with "sage -t --new"; this only tests the uncommitted changes. - ./sage -t --new -p2 + ./sage -t --new -p2 --format github working-directory: ./worktree-image env: MAKE: make -j2 --output-sync=recurse @@ -185,9 +185,9 @@ 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 ./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' From 7a121e65c5bc2bb6b870dffb6c18d27dca52d6ea Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 20 Dec 2023 20:45:27 -0800 Subject: [PATCH 07/26] src/sage/doctest/control.py: Fix doctest --- src/sage/doctest/control.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 37562e1717c..96a73f600b2 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -1117,6 +1117,7 @@ def source_baseline(self, source): sage: filename = os.path.join(SAGE_SRC,'sage','doctest','util.py') sage: DD = DocTestDefaults() sage: DC = DocTestController(DD, [filename]) + sage: DC.expand_files_into_sources() sage: DC.source_baseline(DC.sources[0]) {} """ From 0bd0077e8a2bfc2e286d0aca270c362490e5b3c2 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 20 Dec 2023 21:13:05 -0800 Subject: [PATCH 08/26] src/sage/doctest/control.py: Log 'Using --baseline-stats-path=...' --- src/sage/doctest/control.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index 96a73f600b2..1a128983187 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -1171,6 +1171,8 @@ def run_doctests(self): iterations = ", ".join(iterations) if iterations: iterations = " (%s)" % (iterations) + if self.baseline_stats: + self.log(f"Using --baseline-stats-path={self.options.baseline_stats_path}") self.log("Doctesting %s%s%s." % (filestr, threads, iterations)) self.reporter = DocTestReporter(self) self.dispatcher = DocTestDispatcher(self) From 24779a92ba8a417575c700cc0c6b7ff2e601cf48 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 20 Dec 2023 21:49:57 -0800 Subject: [PATCH 09/26] src/sage/doctest/forker.py: Rewrite without 'match' --- src/sage/doctest/forker.py | 67 +++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index a6b43b37aa2..88c006ca48f 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -1235,40 +1235,41 @@ def _failure_header(self, test, example, message='Failed example:'): """ out = [self.DIVIDER] with OriginalSource(example): - match self.options.format: - case '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) - case '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]' + 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: - 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}' - command += f'::{message}' - out.append(command) + 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: + 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}' + command += f'::{message}' + out.append(command) + else: + raise ValueError(f'unknown format option: {self.options.format}') source = example.source out.append(doctest._indent(source)) return '\n'.join(out) From ee0cb6b033f3d31be8c09cf8705a307c21e2b84c Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 20 Dec 2023 23:03:28 -0800 Subject: [PATCH 10/26] .github/workflows/ci-conda-known-test-failures.json: Add https://github.com/sagemath/sage/issues/36939 --- .github/workflows/ci-conda-known-test-failures.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci-conda-known-test-failures.json b/.github/workflows/ci-conda-known-test-failures.json index 4680c5e7c81..18b898c6685 100644 --- a/.github/workflows/ci-conda-known-test-failures.json +++ b/.github/workflows/ci-conda-known-test-failures.json @@ -35,6 +35,9 @@ "sage.modular.modform.l_series_gross_zagier": { "failed": true }, + "sage.parallel.map_reduce": { + "failed": true + }, "sage.rings.function_field.drinfeld_modules.morphism": { "failed": true }, From fed6570ef3dffebaa58689ddae78c514addcac16 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 20 Dec 2023 23:15:09 -0800 Subject: [PATCH 11/26] .github/workflows/ci-conda-known-test-failures.json: Add https://github.com/sagemath/sage/issues/35973 --- .github/workflows/ci-conda-known-test-failures.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci-conda-known-test-failures.json b/.github/workflows/ci-conda-known-test-failures.json index 18b898c6685..8010bfc3727 100644 --- a/.github/workflows/ci-conda-known-test-failures.json +++ b/.github/workflows/ci-conda-known-test-failures.json @@ -53,6 +53,9 @@ "sage.schemes.elliptic_curves.ell_field": { "failed": true }, + "sage.structure.coerce_actions": { + "failed": true + }, "sage.tests.gap_packages": { "failed": true } From 22ef48640af4094cfad0f999b94645fdc5617b64 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 20 Dec 2023 23:20:05 -0800 Subject: [PATCH 12/26] .github/workflows/ci-conda-known-test-failures.json: Add some failures from https://github.com/sagemath/sage/pull/36372 --- .github/workflows/ci-conda-known-test-failures.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci-conda-known-test-failures.json b/.github/workflows/ci-conda-known-test-failures.json index 8010bfc3727..12ae507bedc 100644 --- a/.github/workflows/ci-conda-known-test-failures.json +++ b/.github/workflows/ci-conda-known-test-failures.json @@ -2,6 +2,9 @@ "sage_setup.clean": { "failed": true }, + "sage.algebras.fusion_rings.fusion_ring": { + "failed": true + }, "sage.combinat.cluster_algebra_quiver.quiver": { "failed": true }, @@ -47,12 +50,18 @@ "sage.rings.polynomial.multi_polynomial_libsingular": { "failed": true }, + "sage.rings.polynomial.polynomial_element": { + "failed": true + }, "sage.rings.polynomial.skew_polynomial_finite_field": { "failed": true }, "sage.schemes.elliptic_curves.ell_field": { "failed": true }, + "sage.sets.recursively_enumerated_set": { + "failed": true + }, "sage.structure.coerce_actions": { "failed": true }, From 5fc07c7e3574c2a2e096862555e2da3fc5c9c816 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 21 Dec 2023 10:48:40 -0800 Subject: [PATCH 13/26] src/bin/sage-runtests: Generalize argparse hack --- src/bin/sage-runtests | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bin/sage-runtests b/src/bin/sage-runtests index e25cebb48a8..4a8b05cfab6 100755 --- a/src/bin/sage-runtests +++ b/src/bin/sage-runtests @@ -141,7 +141,8 @@ if __name__ == "__main__": in_filenames = True new_arguments.append('--') new_arguments.append(arg) - afterlog = bool(arg == '--logfile') + afterlog = arg in ['--logfile', '--stats_path', '--stats-path', + '--baseline_stats_path', '--baseline-stats-path'] args = parser.parse_args(new_arguments) From 93c4e7d83347c85205d455331eba469b9050e521 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 21 Dec 2023 11:23:44 -0800 Subject: [PATCH 14/26] DocTestReporter.report_head: Move all printing of 'failed in baseline' here --- src/sage/doctest/forker.py | 4 ---- src/sage/doctest/reporting.py | 37 ++++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index cb8348e2732..a7379ec97bd 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -1725,8 +1725,6 @@ def serial_dispatch(self): for source in self.controller.sources: heading = self.controller.reporter.report_head(source) baseline = self.controller.source_baseline(source) - if baseline.get('failed', False): - heading += " # [failed in baseline]" if not self.controller.options.only_errors: self.controller.log(heading) @@ -1996,8 +1994,6 @@ def sel_exit(): worker_options.target_walltime = (target_endtime - now) / (max(1, pending_tests / opt.nthreads)) w = DocTestWorker(source, options=worker_options, funclist=[sel_exit], baseline=baseline) heading = self.controller.reporter.report_head(w.source) - if baseline.get('failed', False): - heading += " # [failed in baseline]" if not self.controller.options.only_errors: w.messages = heading + "\n" # Store length of heading to detect if the diff --git a/src/sage/doctest/reporting.py b/src/sage/doctest/reporting.py index dcc0dee792c..7bb40ff4bd6 100644 --- a/src/sage/doctest/reporting.py +++ b/src/sage/doctest/reporting.py @@ -162,14 +162,16 @@ def were_doctests_with_optional_tag_run(self, tag): return True return False - def report_head(self, source): + def report_head(self, source, fail_msg=None): """ - Return the "sage -t [options] file.py" line as string. + Return the ``sage -t [options] file.py`` line as string. INPUT: - ``source`` -- a source from :mod:`sage.doctest.sources` + - ``fail_msg`` -- ``None`` or a string + EXAMPLES:: sage: from sage.doctest.reporting import DocTestReporter @@ -190,6 +192,8 @@ def report_head(self, source): sage: DD.long = True sage: print(DTR.report_head(FDS)) sage -t --long .../sage/doctest/reporting.py + sage: print(DTR.report_head(FDS), "Failed by self-sabotage") + sage -t --long .../sage/doctest/reporting.py # Failed by self-sabotage """ cmd = "sage -t" if self.controller.options.long: @@ -206,6 +210,13 @@ def report_head(self, source): if environment != "sage.repl.ipython_kernel.all_jupyter": cmd += f" --environment={environment}" cmd += " " + source.printpath + baseline = self.controller.source_baseline(source) + if fail_msg: + cmd += " # " + fail_msg + if baseline.get('failed', False): + if not fail_msg: + cmd += " #" + cmd += " [failed in baseline]" return cmd def report(self, source, timeout, return_code, results, output, pid=None): @@ -420,12 +431,10 @@ def report(self, source, timeout, return_code, results, output, pid=None): fail_msg += " (and interrupt failed)" else: fail_msg += " (with %s after interrupt)" % signal_name(sig) - if baseline.get('failed', False): - fail_msg += " [failed in baseline]" log(" %s\n%s\nTests run before %s timed out:" % (fail_msg, "*"*70, process_name)) log(output) log("*"*70) - postscript['lines'].append(cmd + " # %s" % fail_msg) + postscript['lines'].append(self.report_head(source, fail_msg)) stats[basename] = {"failed": True, "walltime": 1e6, "ntests": ntests} if not baseline.get('failed', False): self.error_status |= 4 @@ -436,12 +445,10 @@ def report(self, source, timeout, return_code, results, output, pid=None): fail_msg = "Killed due to %s" % signal_name(-return_code) if ntests > 0: fail_msg += " after testing finished" - if baseline.get('failed', False): - fail_msg += " [failed in baseline]" log(" %s\n%s\nTests run before %s failed:" % (fail_msg,"*"*70, process_name)) log(output) log("*"*70) - postscript['lines'].append(cmd + " # %s" % fail_msg) + postscript['lines'].append(self.report_head(source, fail_msg)) stats[basename] = {"failed": True, "walltime": 1e6, "ntests": ntests} if not baseline.get('failed', False): self.error_status |= (8 if return_code > 0 else 16) @@ -458,13 +465,13 @@ def report(self, source, timeout, return_code, results, output, pid=None): log(" Error in doctesting framework (bad result returned)\n%s\nTests run before error:" % ("*"*70)) log(output) log("*"*70) - postscript['lines'].append(cmd + " # Testing error: bad result") + postscript['lines'].append(self.report_head(source, "Testing error: bad result")) self.error_status |= 64 elif result_dict.err == 'noresult': log(" Error in doctesting framework (no result returned)\n%s\nTests run before error:" % ("*"*70)) log(output) log("*"*70) - postscript['lines'].append(cmd + " # Testing error: no result") + postscript['lines'].append(self.report_head(source, "Testing error: no result")) self.error_status |= 64 elif result_dict.err == 'tab': if len(result_dict.tab_linenos) > 5: @@ -473,11 +480,11 @@ def report(self, source, timeout, return_code, results, output, pid=None): if len(result_dict.tab_linenos) > 1: tabs = "s" + tabs log(" Error: TAB character found at line%s" % (tabs)) - postscript['lines'].append(cmd + " # Tab character found") + postscript['lines'].append(self.report_head(source, "Tab character found")) self.error_status |= 32 elif result_dict.err == 'line_number': log(" Error: Source line number found") - postscript['lines'].append(cmd + " # Source line number found") + postscript['lines'].append(self.report_head(source, "Source line number found")) self.error_status |= 256 elif result_dict.err is not None: # This case should not occur @@ -494,7 +501,7 @@ def report(self, source, timeout, return_code, results, output, pid=None): if output: log("Tests run before doctest exception:\n" + output) log("*"*70) - postscript['lines'].append(cmd + " # %s" % fail_msg) + postscript['lines'].append(self.report_head(source, fail_msg)) if hasattr(result_dict, 'tb'): log(result_dict.tb) if hasattr(result_dict, 'walltime'): @@ -506,9 +513,7 @@ def report(self, source, timeout, return_code, results, output, pid=None): f = result_dict.failures if f: fail_msg = "%s failed" % (count_noun(f, "doctest")) - if baseline.get('failed', False): - fail_msg += " [failed in baseline]" - postscript['lines'].append(cmd + " # %s" % fail_msg) + postscript['lines'].append(self.report_head(source, fail_msg)) if not baseline.get('failed', False): self.error_status |= 1 if f or result_dict.err == 'tab': From c05bdba849318ae645270685be9085429a0944ba Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 21 Dec 2023 11:23:44 -0800 Subject: [PATCH 15/26] DocTestReporter.report_head: Move all printing of 'failed in baseline' here --- src/sage/doctest/forker.py | 4 ---- src/sage/doctest/reporting.py | 37 ++++++++++++++++++++--------------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index cb8348e2732..a7379ec97bd 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -1725,8 +1725,6 @@ def serial_dispatch(self): for source in self.controller.sources: heading = self.controller.reporter.report_head(source) baseline = self.controller.source_baseline(source) - if baseline.get('failed', False): - heading += " # [failed in baseline]" if not self.controller.options.only_errors: self.controller.log(heading) @@ -1996,8 +1994,6 @@ def sel_exit(): worker_options.target_walltime = (target_endtime - now) / (max(1, pending_tests / opt.nthreads)) w = DocTestWorker(source, options=worker_options, funclist=[sel_exit], baseline=baseline) heading = self.controller.reporter.report_head(w.source) - if baseline.get('failed', False): - heading += " # [failed in baseline]" if not self.controller.options.only_errors: w.messages = heading + "\n" # Store length of heading to detect if the diff --git a/src/sage/doctest/reporting.py b/src/sage/doctest/reporting.py index dcc0dee792c..42cc3b6d85e 100644 --- a/src/sage/doctest/reporting.py +++ b/src/sage/doctest/reporting.py @@ -162,14 +162,16 @@ def were_doctests_with_optional_tag_run(self, tag): return True return False - def report_head(self, source): + def report_head(self, source, fail_msg=None): """ - Return the "sage -t [options] file.py" line as string. + Return the ``sage -t [options] file.py`` line as string. INPUT: - ``source`` -- a source from :mod:`sage.doctest.sources` + - ``fail_msg`` -- ``None`` or a string + EXAMPLES:: sage: from sage.doctest.reporting import DocTestReporter @@ -190,6 +192,8 @@ def report_head(self, source): sage: DD.long = True sage: print(DTR.report_head(FDS)) sage -t --long .../sage/doctest/reporting.py + sage: print(DTR.report_head(FDS, "Failed by self-sabotage")) + sage -t --long .../sage/doctest/reporting.py # Failed by self-sabotage """ cmd = "sage -t" if self.controller.options.long: @@ -206,6 +210,13 @@ def report_head(self, source): if environment != "sage.repl.ipython_kernel.all_jupyter": cmd += f" --environment={environment}" cmd += " " + source.printpath + baseline = self.controller.source_baseline(source) + if fail_msg: + cmd += " # " + fail_msg + if baseline.get('failed', False): + if not fail_msg: + cmd += " #" + cmd += " [failed in baseline]" return cmd def report(self, source, timeout, return_code, results, output, pid=None): @@ -420,12 +431,10 @@ def report(self, source, timeout, return_code, results, output, pid=None): fail_msg += " (and interrupt failed)" else: fail_msg += " (with %s after interrupt)" % signal_name(sig) - if baseline.get('failed', False): - fail_msg += " [failed in baseline]" log(" %s\n%s\nTests run before %s timed out:" % (fail_msg, "*"*70, process_name)) log(output) log("*"*70) - postscript['lines'].append(cmd + " # %s" % fail_msg) + postscript['lines'].append(self.report_head(source, fail_msg)) stats[basename] = {"failed": True, "walltime": 1e6, "ntests": ntests} if not baseline.get('failed', False): self.error_status |= 4 @@ -436,12 +445,10 @@ def report(self, source, timeout, return_code, results, output, pid=None): fail_msg = "Killed due to %s" % signal_name(-return_code) if ntests > 0: fail_msg += " after testing finished" - if baseline.get('failed', False): - fail_msg += " [failed in baseline]" log(" %s\n%s\nTests run before %s failed:" % (fail_msg,"*"*70, process_name)) log(output) log("*"*70) - postscript['lines'].append(cmd + " # %s" % fail_msg) + postscript['lines'].append(self.report_head(source, fail_msg)) stats[basename] = {"failed": True, "walltime": 1e6, "ntests": ntests} if not baseline.get('failed', False): self.error_status |= (8 if return_code > 0 else 16) @@ -458,13 +465,13 @@ def report(self, source, timeout, return_code, results, output, pid=None): log(" Error in doctesting framework (bad result returned)\n%s\nTests run before error:" % ("*"*70)) log(output) log("*"*70) - postscript['lines'].append(cmd + " # Testing error: bad result") + postscript['lines'].append(self.report_head(source, "Testing error: bad result")) self.error_status |= 64 elif result_dict.err == 'noresult': log(" Error in doctesting framework (no result returned)\n%s\nTests run before error:" % ("*"*70)) log(output) log("*"*70) - postscript['lines'].append(cmd + " # Testing error: no result") + postscript['lines'].append(self.report_head(source, "Testing error: no result")) self.error_status |= 64 elif result_dict.err == 'tab': if len(result_dict.tab_linenos) > 5: @@ -473,11 +480,11 @@ def report(self, source, timeout, return_code, results, output, pid=None): if len(result_dict.tab_linenos) > 1: tabs = "s" + tabs log(" Error: TAB character found at line%s" % (tabs)) - postscript['lines'].append(cmd + " # Tab character found") + postscript['lines'].append(self.report_head(source, "Tab character found")) self.error_status |= 32 elif result_dict.err == 'line_number': log(" Error: Source line number found") - postscript['lines'].append(cmd + " # Source line number found") + postscript['lines'].append(self.report_head(source, "Source line number found")) self.error_status |= 256 elif result_dict.err is not None: # This case should not occur @@ -494,7 +501,7 @@ def report(self, source, timeout, return_code, results, output, pid=None): if output: log("Tests run before doctest exception:\n" + output) log("*"*70) - postscript['lines'].append(cmd + " # %s" % fail_msg) + postscript['lines'].append(self.report_head(source, fail_msg)) if hasattr(result_dict, 'tb'): log(result_dict.tb) if hasattr(result_dict, 'walltime'): @@ -506,9 +513,7 @@ def report(self, source, timeout, return_code, results, output, pid=None): f = result_dict.failures if f: fail_msg = "%s failed" % (count_noun(f, "doctest")) - if baseline.get('failed', False): - fail_msg += " [failed in baseline]" - postscript['lines'].append(cmd + " # %s" % fail_msg) + postscript['lines'].append(self.report_head(source, fail_msg)) if not baseline.get('failed', False): self.error_status |= 1 if f or result_dict.err == 'tab': From 179ebb4d4f2519fd069cbccfed240251fb48e41e Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 21 Dec 2023 12:11:15 -0800 Subject: [PATCH 16/26] .github/workflows/ci-conda-known-test-failures.json: Add references from commit messages --- .../ci-conda-known-test-failures.json | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci-conda-known-test-failures.json b/.github/workflows/ci-conda-known-test-failures.json index 12ae507bedc..348815e5720 100644 --- a/.github/workflows/ci-conda-known-test-failures.json +++ b/.github/workflows/ci-conda-known-test-failures.json @@ -3,13 +3,13 @@ "failed": true }, "sage.algebras.fusion_rings.fusion_ring": { - "failed": true + "failed": "see https://github.com/sagemath/sage/pull/36372" }, "sage.combinat.cluster_algebra_quiver.quiver": { - "failed": true + "failed": "failure seen in https://github.com/sagemath/sage/actions/runs/6836592771/job/18591690058#step:11:10059" }, "sage.geometry.cone": { - "failed": true + "failed": "failure seen in https://github.com/sagemath/sage/actions/runs/6827937663/job/18571052628#step:11:12362" }, "sage.groups.matrix_gps.finitely_generated_gap": { "failed": true @@ -18,31 +18,31 @@ "failed": true }, "sage.libs.gap.element": { - "failed": true + "failed": "failure seen in https://github.com/sagemath/sage/actions/runs/6840579851/job/18600012965#step:11:13016" }, "sage.libs.singular.singular": { "failed": true }, "sage.matrix.matrix2": { - "failed": true + "failed": "failure seen in https://github.com/sagemath/sage/actions/runs/6835143781/job/18588599649#step:11:16939" }, "sage.matrix.matrix_integer_sparse": { - "failed": true + "failed": "failure seen in https://github.com/sagemath/sage/actions/runs/6827937663/job/18571052628#step:11:12362" }, "sage.misc.lazy_import": { "failed": true }, "sage.misc.weak_dict": { - "failed": true + "failed": "failure seen in https://github.com/sagemath/sage/actions/runs/6870325919/job/18684964234#step:11:10059" }, "sage.modular.modform.l_series_gross_zagier": { - "failed": true + "failed": "failure seen in https://github.com/sagemath/sage/actions/runs/6859244281/job/18651257775#step:11:10102" }, "sage.parallel.map_reduce": { - "failed": true + "failed": "random failure https://github.com/sagemath/sage/issues/36939" }, "sage.rings.function_field.drinfeld_modules.morphism": { - "failed": true + "failed": "failure seen in https://github.com/sagemath/sage/actions/runs/6840502530/job/18599835766#step:11:10107" }, "sage.rings.polynomial.multi_polynomial_ideal": { "failed": true @@ -51,19 +51,19 @@ "failed": true }, "sage.rings.polynomial.polynomial_element": { - "failed": true + "failed": "see https://github.com/sagemath/sage/pull/36372" }, "sage.rings.polynomial.skew_polynomial_finite_field": { "failed": true }, "sage.schemes.elliptic_curves.ell_field": { - "failed": true + "failed": "random failure https://github.com/sagemath/sage/issues/36832" }, "sage.sets.recursively_enumerated_set": { - "failed": true + "failed": "see https://github.com/sagemath/sage/pull/36372" }, "sage.structure.coerce_actions": { - "failed": true + "failed": "random failure https://github.com/sagemath/sage/issues/35973" }, "sage.tests.gap_packages": { "failed": true From b41999bf330d591480cbe88d0b4893feb63a26a4 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 21 Dec 2023 12:40:00 -0800 Subject: [PATCH 17/26] .github/workflows/ci-conda-known-test-failures.json: Add details from https://github.com/sagemath/sage/pull/36372 --- .../ci-conda-known-test-failures.json | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci-conda-known-test-failures.json b/.github/workflows/ci-conda-known-test-failures.json index 348815e5720..47d0adcd8e9 100644 --- a/.github/workflows/ci-conda-known-test-failures.json +++ b/.github/workflows/ci-conda-known-test-failures.json @@ -1,15 +1,18 @@ { + "doc.en.constructions.calculus": { + "failed": "unreported random failures in plotting" + }, "sage_setup.clean": { - "failed": true + "failed": "_find_stale_files finds some stale files under sage/tests when executed under conda" }, "sage.algebras.fusion_rings.fusion_ring": { - "failed": "see https://github.com/sagemath/sage/pull/36372" + "failed": "unreported random timeouts" }, "sage.combinat.cluster_algebra_quiver.quiver": { "failed": "failure seen in https://github.com/sagemath/sage/actions/runs/6836592771/job/18591690058#step:11:10059" }, "sage.geometry.cone": { - "failed": "failure seen in https://github.com/sagemath/sage/actions/runs/6827937663/job/18571052628#step:11:12362" + "failed": "unreported random timeouts seen in https://github.com/sagemath/sage/actions/runs/6827937663/job/18571052628#step:11:12362" }, "sage.groups.matrix_gps.finitely_generated_gap": { "failed": true @@ -36,13 +39,16 @@ "failed": "failure seen in https://github.com/sagemath/sage/actions/runs/6870325919/job/18684964234#step:11:10059" }, "sage.modular.modform.l_series_gross_zagier": { - "failed": "failure seen in https://github.com/sagemath/sage/actions/runs/6859244281/job/18651257775#step:11:10102" + "failed": "unreported failure seen in https://github.com/sagemath/sage/actions/runs/6859244281/job/18651257775#step:11:10102" }, "sage.parallel.map_reduce": { "failed": "random failure https://github.com/sagemath/sage/issues/36939" }, + "sage.plot.plot": { + "failed": "unreported random failure (macOS)" + }, "sage.rings.function_field.drinfeld_modules.morphism": { - "failed": "failure seen in https://github.com/sagemath/sage/actions/runs/6840502530/job/18599835766#step:11:10107" + "failed": "unreported random failure seen in https://github.com/sagemath/sage/actions/runs/6840502530/job/18599835766#step:11:10107" }, "sage.rings.polynomial.multi_polynomial_ideal": { "failed": true @@ -51,7 +57,7 @@ "failed": true }, "sage.rings.polynomial.polynomial_element": { - "failed": "see https://github.com/sagemath/sage/pull/36372" + "failed": "unreported random failure in symbolic 'roots' (macOS)" }, "sage.rings.polynomial.skew_polynomial_finite_field": { "failed": true @@ -60,7 +66,7 @@ "failed": "random failure https://github.com/sagemath/sage/issues/36832" }, "sage.sets.recursively_enumerated_set": { - "failed": "see https://github.com/sagemath/sage/pull/36372" + "failed": "random failures related to AlarmInterrupt (macOS)" }, "sage.structure.coerce_actions": { "failed": "random failure https://github.com/sagemath/sage/issues/35973" From 63b8b108fee7e18a8c51d7e1460cea7ad9a02804 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 21 Dec 2023 12:40:58 -0800 Subject: [PATCH 18/26] src/sage/doctest/reporting.py: Include message in '[failed in baseline]' if provided --- src/sage/doctest/reporting.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sage/doctest/reporting.py b/src/sage/doctest/reporting.py index 42cc3b6d85e..a1e85af1a57 100644 --- a/src/sage/doctest/reporting.py +++ b/src/sage/doctest/reporting.py @@ -213,10 +213,13 @@ def report_head(self, source, fail_msg=None): baseline = self.controller.source_baseline(source) if fail_msg: cmd += " # " + fail_msg - if baseline.get('failed', False): + if failed := baseline.get('failed', False): if not fail_msg: cmd += " #" - cmd += " [failed in baseline]" + if failed is True: + cmd += " [failed in baseline]" + else: + cmd += f" [failed in baseline: {failed}]" return cmd def report(self, source, timeout, return_code, results, output, pid=None): From 4b0f525f6d24fc1167dd1b9375fb3d920d7fbe26 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 22 Dec 2023 20:00:06 -0800 Subject: [PATCH 19/26] .github/workflows/build.yml: Fix up path to coverage files --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2b87d6edb60..2f5e20f2dff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -192,7 +192,7 @@ jobs: - 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 combine .coverage/ ./venv/bin/python3 -m coverage xml mkdir -p coverage-report mv coverage.xml coverage-report/ From 0bfd2c933d671cb01f14733ba6eedd8bb149e498 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 22 Dec 2023 21:31:22 -0800 Subject: [PATCH 20/26] src/sage/doctest/reporting.py: Make 'AlarmInterrupt in doctesting framework' baseline-aware --- src/sage/doctest/reporting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sage/doctest/reporting.py b/src/sage/doctest/reporting.py index 42cc3b6d85e..cda0e8a3f20 100644 --- a/src/sage/doctest/reporting.py +++ b/src/sage/doctest/reporting.py @@ -508,7 +508,8 @@ def report(self, source, timeout, return_code, results, output, pid=None): stats[basename] = {"failed": True, "walltime": wall, "ntests": ntests} else: stats[basename] = {"failed": True, "walltime": 1e6, "ntests": ntests} - self.error_status |= 64 + if not baseline.get('failed', False): # e.g. AlarmInterrupt in doctesting framework + self.error_status |= 64 if result_dict.err is None or result_dict.err == 'tab': f = result_dict.failures if f: From ce3b849dc841e269357ef260ec6a9e1088143cc1 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Wed, 27 Dec 2023 20:18:36 -0800 Subject: [PATCH 21/26] .github/workflows/build.yml: Do not create GitHub Annotations for the tests of the new files, to avoid duplication --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2f5e20f2dff..71ba6d15351 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -158,7 +158,7 @@ jobs: if: (success() || failure()) && steps.build.outcome == 'success' run: | # We run tests with "sage -t --new"; this only tests the uncommitted changes. - ./sage -t --new -p2 --format github + ./sage -t --new -p2 working-directory: ./worktree-image env: MAKE: make -j2 --output-sync=recurse From b83dcf3102dd1b8a6d5c869895c4a80c9379e9aa Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 29 Dec 2023 08:38:30 -0800 Subject: [PATCH 22/26] src/sage/doctest/forker.py: Use urllib.parse.quote for github annotations --- src/sage/doctest/forker.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index 88887240c9f..e972031fbd1 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -1248,16 +1248,21 @@ def _failure_header(self, test, example, message='Failed example:'): 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 + from urllib.parse import quote + + def q(string): + return quote(string, safe=' /,"\'') + if message.startswith('Warning: '): - command = f'::warning title={message}' + command = f'::warning title={q(message)}' message = message[len('Warning: '):] elif self.baseline.get('failed', False): - command = f'::notice title={message}' + command = f'::notice title={q(message)}' message += ' [failed in baseline]' else: - command = f'::error title={message}' + command = f'::error title={q(message)}' if extra := getattr(example, 'extra', None): - message += f': {extra}' + message += f': {q(extra)}' if test.filename: command += f',file={test.filename}' if test.lineno is not None and example.lineno is not None: @@ -1266,7 +1271,7 @@ def _failure_header(self, test, example, message='Failed example:'): lineno = None else: command += f',line={example.lineno + 1}' - command += f'::{message}' + command += f'::{q(message)}' out.append(command) else: raise ValueError(f'unknown format option: {self.options.format}') @@ -1442,7 +1447,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!r}' + example.extra = f'Got: {got}' returnval = doctest.DocTestRunner.report_failure(self, out, test, example, got) if self.options.debug: self._fakeout.stop_spoofing() @@ -1594,7 +1599,7 @@ 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: " + repr("".join(traceback.format_exception(*exc_info))) + 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() From 96e043138e420c26c76171d77073e05b5855c26b Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 29 Dec 2023 11:43:39 -0800 Subject: [PATCH 23/26] src/sage/doctest/forker.py: Don't use urllib.parse.quote, only quote \n, pad short multi-line messages --- src/sage/doctest/forker.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/sage/doctest/forker.py b/src/sage/doctest/forker.py index e972031fbd1..67e587c2c9d 100644 --- a/src/sage/doctest/forker.py +++ b/src/sage/doctest/forker.py @@ -1248,21 +1248,16 @@ def _failure_header(self, test, example, message='Failed example:'): 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 - from urllib.parse import quote - - def q(string): - return quote(string, safe=' /,"\'') - if message.startswith('Warning: '): - command = f'::warning title={q(message)}' + command = f'::warning title={message}' message = message[len('Warning: '):] elif self.baseline.get('failed', False): - command = f'::notice title={q(message)}' + command = f'::notice title={message}' message += ' [failed in baseline]' else: - command = f'::error title={q(message)}' + command = f'::error title={message}' if extra := getattr(example, 'extra', None): - message += f': {q(extra)}' + message += f': {extra}' if test.filename: command += f',file={test.filename}' if test.lineno is not None and example.lineno is not None: @@ -1271,7 +1266,23 @@ def q(string): lineno = None else: command += f',line={example.lineno + 1}' - command += f'::{q(message)}' + # + # 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: raise ValueError(f'unknown format option: {self.options.format}') From 1d69f0798f503eb457ff44e2c7f0c96b5acf3495 Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Fri, 29 Dec 2023 14:57:58 -0800 Subject: [PATCH 24/26] .github/workflows/build.yml: Fix "Couldn't combine from non-existent path '.coverage/'" --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 71ba6d15351..6af89c85d71 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -192,7 +192,7 @@ jobs: - name: Prepare coverage results if: (success() || failure()) && steps.build.outcome == 'success' run: | - ./venv/bin/python3 -m coverage combine .coverage/ + ./venv/bin/python3 -m coverage combine ./venv/bin/python3 -m coverage xml mkdir -p coverage-report mv coverage.xml coverage-report/ From 173bc417bdec2b02b19e72c7a95a7dcbbd41a86a Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 4 Jan 2024 16:04:44 -0800 Subject: [PATCH 25/26] .github/workflows/build.yml: Pass --rcfile to coverage --- .github/workflows/build.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6af89c85d71..94fa81e4e8b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -175,9 +175,9 @@ jobs: - name: Pytest if: contains(github.ref, 'pytest') run: | - ../sage -python -m pip install coverage pytest-xdist - ../sage -python -m coverage run -m pytest -c tox.ini --doctest-modules || true - working-directory: ./worktree-image/src + ./sage -python -m pip install coverage pytest-xdist + ./sage -python -m coverage run --rcfile=src/tox.ini -m pytest -c src/tox.ini --doctest-modules || true + working-directory: ./worktree-image env: # Increase the length of the lines in the "short summary" COLUMNS: 120 @@ -186,14 +186,14 @@ jobs: if: (success() || failure()) && steps.build.outcome == 'success' run: | ./sage -python -m pip install coverage - ./sage -python -m coverage run ./src/bin/sage-runtests --all --long -p2 --format github --random-seed=286735480429121101562228604801325644303 + ./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 - ./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 From 4ecf7cad57fd0018cc917b5cc4859871d7e1c2bf Mon Sep 17 00:00:00 2001 From: Matthias Koeppe Date: Thu, 4 Jan 2024 19:11:37 -0800 Subject: [PATCH 26/26] src/tox.ini (coverage:report): Ignore errors, ignore empty --- src/tox.ini | 4 ++++ 1 file changed, 4 insertions(+) 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