passagemath-repl 10.5.1__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.
- passagemath_repl-10.5.1.data/scripts/sage-cachegrind +25 -0
- passagemath_repl-10.5.1.data/scripts/sage-callgrind +16 -0
- passagemath_repl-10.5.1.data/scripts/sage-cleaner +230 -0
- passagemath_repl-10.5.1.data/scripts/sage-coverage +327 -0
- passagemath_repl-10.5.1.data/scripts/sage-eval +14 -0
- passagemath_repl-10.5.1.data/scripts/sage-fixdoctests +710 -0
- passagemath_repl-10.5.1.data/scripts/sage-inline-fortran +12 -0
- passagemath_repl-10.5.1.data/scripts/sage-ipynb2rst +50 -0
- passagemath_repl-10.5.1.data/scripts/sage-ipython +16 -0
- passagemath_repl-10.5.1.data/scripts/sage-massif +25 -0
- passagemath_repl-10.5.1.data/scripts/sage-notebook +267 -0
- passagemath_repl-10.5.1.data/scripts/sage-omega +25 -0
- passagemath_repl-10.5.1.data/scripts/sage-preparse +302 -0
- passagemath_repl-10.5.1.data/scripts/sage-run +27 -0
- passagemath_repl-10.5.1.data/scripts/sage-run-cython +10 -0
- passagemath_repl-10.5.1.data/scripts/sage-runtests +9 -0
- passagemath_repl-10.5.1.data/scripts/sage-startuptime.py +163 -0
- passagemath_repl-10.5.1.data/scripts/sage-valgrind +34 -0
- passagemath_repl-10.5.1.dist-info/METADATA +77 -0
- passagemath_repl-10.5.1.dist-info/RECORD +162 -0
- passagemath_repl-10.5.1.dist-info/WHEEL +5 -0
- passagemath_repl-10.5.1.dist-info/top_level.txt +1 -0
- sage/all__sagemath_repl.py +119 -0
- sage/doctest/__init__.py +4 -0
- sage/doctest/__main__.py +236 -0
- sage/doctest/all.py +4 -0
- sage/doctest/check_tolerance.py +261 -0
- sage/doctest/control.py +1727 -0
- sage/doctest/external.py +534 -0
- sage/doctest/fixtures.py +383 -0
- sage/doctest/forker.py +2665 -0
- sage/doctest/marked_output.py +102 -0
- sage/doctest/parsing.py +1708 -0
- sage/doctest/parsing_test.py +79 -0
- sage/doctest/reporting.py +733 -0
- sage/doctest/rif_tol.py +124 -0
- sage/doctest/sources.py +1657 -0
- sage/doctest/test.py +584 -0
- sage/doctest/tests/1second.rst +4 -0
- sage/doctest/tests/99seconds.rst +4 -0
- sage/doctest/tests/abort.rst +5 -0
- sage/doctest/tests/atexit.rst +7 -0
- sage/doctest/tests/fail_and_die.rst +6 -0
- sage/doctest/tests/initial.rst +15 -0
- sage/doctest/tests/interrupt.rst +7 -0
- sage/doctest/tests/interrupt_diehard.rst +14 -0
- sage/doctest/tests/keyboardinterrupt.rst +11 -0
- sage/doctest/tests/longtime.rst +5 -0
- sage/doctest/tests/nodoctest +5 -0
- sage/doctest/tests/random_seed.rst +4 -0
- sage/doctest/tests/show_skipped.rst +18 -0
- sage/doctest/tests/sig_on.rst +9 -0
- sage/doctest/tests/simple_failure.rst +8 -0
- sage/doctest/tests/sleep_and_raise.rst +106 -0
- sage/doctest/tests/tolerance.rst +31 -0
- sage/doctest/util.py +750 -0
- sage/interfaces/cleaner.py +48 -0
- sage/interfaces/quit.py +163 -0
- sage/misc/all__sagemath_repl.py +51 -0
- sage/misc/banner.py +235 -0
- sage/misc/benchmark.py +221 -0
- sage/misc/classgraph.py +134 -0
- sage/misc/copying.py +22 -0
- sage/misc/cython.py +694 -0
- sage/misc/dev_tools.py +745 -0
- sage/misc/edit_module.py +304 -0
- sage/misc/explain_pickle.py +3079 -0
- sage/misc/gperftools.py +361 -0
- sage/misc/inline_fortran.py +212 -0
- sage/misc/messaging.py +86 -0
- sage/misc/pager.py +21 -0
- sage/misc/profiler.py +179 -0
- sage/misc/python.py +70 -0
- sage/misc/remote_file.py +53 -0
- sage/misc/sage_eval.py +249 -0
- sage/misc/sage_input.py +3621 -0
- sage/misc/sagedoc.py +1742 -0
- sage/misc/sh.py +38 -0
- sage/misc/trace.py +90 -0
- sage/repl/__init__.py +16 -0
- sage/repl/all.py +15 -0
- sage/repl/attach.py +625 -0
- sage/repl/configuration.py +186 -0
- sage/repl/display/__init__.py +1 -0
- sage/repl/display/fancy_repr.py +354 -0
- sage/repl/display/formatter.py +318 -0
- sage/repl/display/jsmol_iframe.py +290 -0
- sage/repl/display/pretty_print.py +153 -0
- sage/repl/display/util.py +163 -0
- sage/repl/image.py +302 -0
- sage/repl/inputhook.py +91 -0
- sage/repl/interface_magic.py +298 -0
- sage/repl/interpreter.py +854 -0
- sage/repl/ipython_extension.py +593 -0
- sage/repl/ipython_kernel/__init__.py +1 -0
- sage/repl/ipython_kernel/__main__.py +4 -0
- sage/repl/ipython_kernel/all_jupyter.py +10 -0
- sage/repl/ipython_kernel/install.py +301 -0
- sage/repl/ipython_kernel/interact.py +278 -0
- sage/repl/ipython_kernel/kernel.py +217 -0
- sage/repl/ipython_kernel/widgets.py +466 -0
- sage/repl/ipython_kernel/widgets_sagenb.py +587 -0
- sage/repl/ipython_tests.py +163 -0
- sage/repl/load.py +326 -0
- sage/repl/preparse.py +2218 -0
- sage/repl/prompts.py +90 -0
- sage/repl/rich_output/__init__.py +4 -0
- sage/repl/rich_output/backend_base.py +648 -0
- sage/repl/rich_output/backend_doctest.py +316 -0
- sage/repl/rich_output/backend_emacs.py +151 -0
- sage/repl/rich_output/backend_ipython.py +596 -0
- sage/repl/rich_output/buffer.py +311 -0
- sage/repl/rich_output/display_manager.py +829 -0
- sage/repl/rich_output/example.avi +0 -0
- sage/repl/rich_output/example.canvas3d +1 -0
- sage/repl/rich_output/example.dvi +0 -0
- sage/repl/rich_output/example.flv +0 -0
- sage/repl/rich_output/example.gif +0 -0
- sage/repl/rich_output/example.jpg +0 -0
- sage/repl/rich_output/example.mkv +0 -0
- sage/repl/rich_output/example.mov +0 -0
- sage/repl/rich_output/example.mp4 +0 -0
- sage/repl/rich_output/example.ogv +0 -0
- sage/repl/rich_output/example.pdf +0 -0
- sage/repl/rich_output/example.png +0 -0
- sage/repl/rich_output/example.svg +54 -0
- sage/repl/rich_output/example.webm +0 -0
- sage/repl/rich_output/example.wmv +0 -0
- sage/repl/rich_output/example_jmol.spt.zip +0 -0
- sage/repl/rich_output/example_wavefront_scene.mtl +7 -0
- sage/repl/rich_output/example_wavefront_scene.obj +17 -0
- sage/repl/rich_output/output_basic.py +391 -0
- sage/repl/rich_output/output_browser.py +103 -0
- sage/repl/rich_output/output_catalog.py +54 -0
- sage/repl/rich_output/output_graphics.py +320 -0
- sage/repl/rich_output/output_graphics3d.py +345 -0
- sage/repl/rich_output/output_video.py +231 -0
- sage/repl/rich_output/preferences.py +432 -0
- sage/repl/rich_output/pretty_print.py +339 -0
- sage/repl/rich_output/test_backend.py +201 -0
- sage/repl/user_globals.py +214 -0
- sage/tests/all.py +0 -0
- sage/tests/all__sagemath_repl.py +3 -0
- sage/tests/article_heuberger_krenn_kropf_fsm-in-sage.py +630 -0
- sage/tests/arxiv_0812_2725.py +351 -0
- sage/tests/benchmark.py +1925 -0
- sage/tests/book_schilling_zabrocki_kschur_primer.py +795 -0
- sage/tests/book_stein_ent.py +651 -0
- sage/tests/book_stein_modform.py +558 -0
- sage/tests/cmdline.py +796 -0
- sage/tests/combinatorial_hopf_algebras.py +52 -0
- sage/tests/finite_poset.py +623 -0
- sage/tests/functools_partial_src.py +27 -0
- sage/tests/gosper-sum.py +218 -0
- sage/tests/lazy_imports.py +28 -0
- sage/tests/modular_group_cohomology.py +80 -0
- sage/tests/numpy.py +21 -0
- sage/tests/parigp.py +76 -0
- sage/tests/startup.py +27 -0
- sage/tests/symbolic-series.py +76 -0
- sage/tests/sympy.py +16 -0
- sage/tests/test_deprecation.py +31 -0
@@ -0,0 +1,710 @@
|
|
1
|
+
#!python
|
2
|
+
"""
|
3
|
+
Given the output of doctest and a file, adjust the doctests so they won't fail.
|
4
|
+
|
5
|
+
Doctest failures due to exceptions are ignored.
|
6
|
+
|
7
|
+
AUTHORS::
|
8
|
+
|
9
|
+
- Nicolas M. Thiéry <nthiery at users dot sf dot net> Initial version (2008?)
|
10
|
+
|
11
|
+
- Andrew Mathas <andrew dot mathas at sydney dot edu dot au> 2013-02-14
|
12
|
+
Cleaned up the code and hacked it so that the script can now cope with the
|
13
|
+
situations when either the expected output or computed output are empty.
|
14
|
+
Added doctest to sage.tests.cmdline
|
15
|
+
"""
|
16
|
+
|
17
|
+
# ****************************************************************************
|
18
|
+
# Copyright (C) 2006 William Stein
|
19
|
+
# 2009 Nicolas M. Thiery
|
20
|
+
# 2013 Andrew Mathas
|
21
|
+
# 2014 Volker Braun
|
22
|
+
# 2020 Jonathan Kliem
|
23
|
+
# 2021 Frédéric Chapoton
|
24
|
+
# 2023 Matthias Koeppe
|
25
|
+
#
|
26
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
27
|
+
# as published by the Free Software Foundation; either version 2 of
|
28
|
+
# the License, or (at your option) any later version.
|
29
|
+
# https://www.gnu.org/licenses/
|
30
|
+
# ****************************************************************************
|
31
|
+
|
32
|
+
import itertools
|
33
|
+
import json
|
34
|
+
import os
|
35
|
+
import re
|
36
|
+
import shlex
|
37
|
+
import subprocess
|
38
|
+
import sys
|
39
|
+
|
40
|
+
from argparse import ArgumentParser
|
41
|
+
from collections import defaultdict
|
42
|
+
from pathlib import Path
|
43
|
+
|
44
|
+
from sage.doctest.control import DocTestDefaults, DocTestController
|
45
|
+
from sage.doctest.parsing import parse_file_optional_tags, parse_optional_tags, unparse_optional_tags, update_optional_tags
|
46
|
+
from sage.env import SAGE_ROOT
|
47
|
+
from sage.features import PythonModule
|
48
|
+
from sage.features.all import all_features, module_feature, name_feature
|
49
|
+
from sage.misc.cachefunc import cached_function
|
50
|
+
from sage.misc.temporary_file import tmp_filename
|
51
|
+
|
52
|
+
parser = ArgumentParser(description="Given an input file with doctests, this creates a modified file that passes the doctests (modulo any raised exceptions). By default, the input file is modified. You can also name an output file.")
|
53
|
+
parser.add_argument('-l', '--long', dest='long', action="store_true", default=False,
|
54
|
+
help="include tests tagged '# long time'")
|
55
|
+
parser.add_argument("--distribution", type=str, default=[], action='append',
|
56
|
+
help="distribution package to test, e.g., 'sagemath-graphs', 'sagemath-combinat[modules]'; sets defaults for --venv and --environment. This option can be repeated to test several distributions")
|
57
|
+
parser.add_argument("--fixed-point", default=False, action="store_true",
|
58
|
+
help="whether to repeat until stable")
|
59
|
+
parser.add_argument("--toxenv", type=str, default='sagepython-sagewheels-nopypi-norequirements',
|
60
|
+
help="tox environment name where 'sage -t' is to be run")
|
61
|
+
parser.add_argument("--venv", type=str, default='',
|
62
|
+
help="directory name of a venv where 'sage -t' is to be run")
|
63
|
+
parser.add_argument("--environment", type=str, default='',
|
64
|
+
help="name of a module that provides the global environment for tests, e.g., 'sage.all__sagemath_modules'; implies --keep-both and --full-tracebacks")
|
65
|
+
parser.add_argument("--no-test", default=False, action="store_true",
|
66
|
+
help="do not run the doctester, only rewrite '# optional/needs' tags; implies --only-tags")
|
67
|
+
parser.add_argument("--full-tracebacks", default=False, action="store_true",
|
68
|
+
help="include full tracebacks rather than '...'")
|
69
|
+
parser.add_argument("--only-tags", default=False, action="store_true",
|
70
|
+
help="only add '# optional/needs' tags where needed, ignore other failures")
|
71
|
+
parser.add_argument("--probe", metavar="FEATURES", type=str, default='',
|
72
|
+
help="check whether '# optional/needs' tags are still needed, remove those not needed")
|
73
|
+
parser.add_argument("--keep-both", default=False, action="store_true",
|
74
|
+
help="do not replace test results; duplicate the test instead, showing both results, and mark both copies '# optional'")
|
75
|
+
parser.add_argument("--overwrite", default=False, action="store_true",
|
76
|
+
help="never interpret a second filename as OUTPUT; overwrite the source files")
|
77
|
+
parser.add_argument("--no-overwrite", default=False, action="store_true",
|
78
|
+
help="never interpret a second filename as OUTPUT; output goes to files named INPUT.fixed")
|
79
|
+
parser.add_argument("--update-known-test-failures", default=False, action="store_true",
|
80
|
+
help="update the file pkgs/DISTRIBUTION/known-test-failures.json")
|
81
|
+
parser.add_argument("--verbose", default=False, action="store_true",
|
82
|
+
help="show details of all changes; implies --no-diff")
|
83
|
+
parser.add_argument("--no-diff", default=False, action="store_true",
|
84
|
+
help="don't show the 'git diff' of the modified files")
|
85
|
+
parser.add_argument("filename", nargs='*', help="input filenames; or (deprecated) INPUT_FILENAME OUTPUT_FILENAME if exactly two filenames are given and neither --overwrite nor --no-overwrite is present",
|
86
|
+
type=str)
|
87
|
+
|
88
|
+
runtest_default_environment = "sage.repl.ipython_kernel.all_jupyter"
|
89
|
+
|
90
|
+
def plain_distribution_and_extras(distribution):
|
91
|
+
# shortcuts / variants
|
92
|
+
distribution = distribution.replace('_', '-')
|
93
|
+
if not (distribution.startswith('sagemath-')
|
94
|
+
or distribution.startswith('sage-')):
|
95
|
+
distribution = f'sagemath-{distribution}'
|
96
|
+
# extras
|
97
|
+
m = re.fullmatch(r'([^[]*)(\[([^]]*)\])?', distribution)
|
98
|
+
return m.group(1), m.group(3)
|
99
|
+
|
100
|
+
def default_venv_environment_from_distribution(distribution, toxenv):
|
101
|
+
if distribution:
|
102
|
+
plain_distribution, extras = plain_distribution_and_extras(distribution)
|
103
|
+
tox_env_name = toxenv or 'sagepython-sagewheels-nopypi-norequirements'
|
104
|
+
if extras:
|
105
|
+
tox_env_name += '-' + extras.replace(',', '-')
|
106
|
+
default_venv = os.path.join(SAGE_ROOT, 'pkgs', plain_distribution, '.tox', tox_env_name)
|
107
|
+
if plain_distribution == 'sagemath-standard-no-symbolics':
|
108
|
+
default_environment = 'sage.all'
|
109
|
+
else:
|
110
|
+
default_environment = 'sage.all__' + plain_distribution.replace('-', '_')
|
111
|
+
else:
|
112
|
+
default_venv = ''
|
113
|
+
default_environment = runtest_default_environment
|
114
|
+
return default_venv, default_environment
|
115
|
+
|
116
|
+
|
117
|
+
@cached_function
|
118
|
+
def venv_explainer(distribution, venv=None, environment=None, toxenv=None):
|
119
|
+
venv_explainers = []
|
120
|
+
default_venv, default_environment = default_venv_environment_from_distribution(distribution, toxenv)
|
121
|
+
if venv:
|
122
|
+
if m := re.search(f'pkgs/(sage[^/]*)/[.]tox/((sagepython|sagewheels|nopypi|norequirements)-*)*([^/]*)$',
|
123
|
+
venv):
|
124
|
+
distribution, extras = m.group(1), m.group(4)
|
125
|
+
if extras:
|
126
|
+
distribution += '[' + extras.replace('-', ',') + ']'
|
127
|
+
default_venv_given_distribution, default_environment_given_distribution = default_venv_environment_from_distribution(distribution, toxenv)
|
128
|
+
|
129
|
+
if (Path(venv).resolve() == Path(default_venv_given_distribution).resolve()
|
130
|
+
or not environment or environment == default_environment_given_distribution):
|
131
|
+
venv_explainers.append(f'--distribution {shlex.quote(distribution)}')
|
132
|
+
default_venv, default_environment = default_venv_given_distribution, default_environment_given_distribution
|
133
|
+
|
134
|
+
if venv and Path(venv).resolve() != Path(default_venv).resolve():
|
135
|
+
venv_explainers.append(f'--venv {shlex.quote(venv)}')
|
136
|
+
if environment and environment != default_environment:
|
137
|
+
venv_explainers.append(f'--environment {environment}')
|
138
|
+
|
139
|
+
if venv_explainers:
|
140
|
+
return ' (with ' + ' '.join(venv_explainers) + ')'
|
141
|
+
return ''
|
142
|
+
|
143
|
+
|
144
|
+
sep = "**********************************************************************\n"
|
145
|
+
|
146
|
+
|
147
|
+
def process_block(block, src_in_lines, file_optional_tags, venv_explainer=''):
|
148
|
+
if args.verbose:
|
149
|
+
print(sep + block.rstrip())
|
150
|
+
|
151
|
+
# Extract the line, what was expected, and was got.
|
152
|
+
if not (m := re.match('File "([^"]*)", line ([0-9]+), in ', block)):
|
153
|
+
return
|
154
|
+
|
155
|
+
def print_line(num):
|
156
|
+
if args.verbose and (src := src_in_lines[num]):
|
157
|
+
if src:
|
158
|
+
for line in src.split('\n'):
|
159
|
+
line = line.strip()
|
160
|
+
if line.startswith("sage: ") or line.startswith("....: "):
|
161
|
+
line = line[6:]
|
162
|
+
print(f" {line}") # indent to match the displayed "Example" in the sage-runtest message
|
163
|
+
|
164
|
+
def update_line(num, new, message=None):
|
165
|
+
src_in_lines[num] = new
|
166
|
+
if args.verbose and message:
|
167
|
+
print(f"sage-fixdoctests: {message}")
|
168
|
+
print_line(num)
|
169
|
+
|
170
|
+
def append_to_line(num, new, message=None):
|
171
|
+
update_line(num, src_in_lines[num] + new, message=message)
|
172
|
+
|
173
|
+
def prepend_to_line(num, new, message=None):
|
174
|
+
update_line(num, new + src_in_lines[num], message=message)
|
175
|
+
|
176
|
+
def update_line_optional_tags(num, *args, message=None, **kwds):
|
177
|
+
update_line(num,
|
178
|
+
update_optional_tags(src_in_lines[num], *args, **kwds),
|
179
|
+
message=message)
|
180
|
+
|
181
|
+
filename = m.group(1)
|
182
|
+
first_line_num = line_num = int(m.group(2)) # 1-based line number of the first line of the example
|
183
|
+
|
184
|
+
if m := re.search(r"using.*block-scoped tag.*'(sage: .*)'.*to avoid repeating the tag", block):
|
185
|
+
indent = (len(src_in_lines[first_line_num - 1]) - len(src_in_lines[first_line_num - 1].lstrip()))
|
186
|
+
append_to_line(line_num - 2, '\n' + ' ' * indent + m.group(1),
|
187
|
+
message="Adding this block-scoped tag")
|
188
|
+
print_line(first_line_num - 1)
|
189
|
+
|
190
|
+
if m := re.search(r"updating.*block-scoped tag.*'sage: (.*)'.*to avoid repeating the tag", block):
|
191
|
+
update_line_optional_tags(first_line_num - 1, tags=parse_optional_tags('# ' + m.group(1)),
|
192
|
+
message="Adding this tag to the existing block-scoped tag")
|
193
|
+
|
194
|
+
if m := re.search(r"referenced here was set only in doctest marked '# (optional|needs)[-: ]*([^;']*)", block):
|
195
|
+
optional = m.group(2).split()
|
196
|
+
if src_in_lines[first_line_num - 1].strip() in ['"""', "'''"]:
|
197
|
+
# This happens due to a virtual doctest in src/sage/repl/user_globals.py
|
198
|
+
return
|
199
|
+
optional = set(optional) - set(file_optional_tags)
|
200
|
+
update_line_optional_tags(first_line_num - 1, add_tags=optional,
|
201
|
+
message=f"Adding the tag(s) {optional}")
|
202
|
+
|
203
|
+
if m := re.search(r"tag '# (optional|needs)[-: ]([^;']*)' may no longer be needed", block):
|
204
|
+
optional = m.group(2).split()
|
205
|
+
update_line_optional_tags(first_line_num - 1, remove_tags=optional,
|
206
|
+
message=f"Removing the tag(s) {optional}")
|
207
|
+
|
208
|
+
if m2 := re.search('(Expected:|Expected nothing|Exception raised:)\n', block):
|
209
|
+
m1 = re.search('Failed example:\n', block)
|
210
|
+
line_num += block[m1.end() : m2.start()].count('\n') - 1
|
211
|
+
# Now line_num is the 1-based line number of the last line of the example
|
212
|
+
|
213
|
+
if m2.group(1) == 'Expected nothing':
|
214
|
+
expected = ''
|
215
|
+
block = '\n' + block[m2.end():] # so that split('\nGot:\n') does not fail below
|
216
|
+
elif m2.group(1) == 'Exception raised:':
|
217
|
+
# In this case, the doctester does not show the expected output,
|
218
|
+
# so we do not know how many lines it spans; so we check for the next prompt or
|
219
|
+
# docstring end.
|
220
|
+
expected = []
|
221
|
+
indentation = ' ' * (len(src_in_lines[line_num - 1]) - len(src_in_lines[line_num - 1].lstrip()))
|
222
|
+
i = line_num
|
223
|
+
while ((not src_in_lines[i].rstrip() or src_in_lines[i].startswith(indentation))
|
224
|
+
and not re.match(' *(sage:|""")', src_in_lines[i])):
|
225
|
+
expected.append(src_in_lines[i])
|
226
|
+
i += 1
|
227
|
+
block = '\n'.join(expected) + '\nGot:\n' + block[m2.end():]
|
228
|
+
else:
|
229
|
+
block = block[m2.end():]
|
230
|
+
else:
|
231
|
+
return
|
232
|
+
|
233
|
+
# Error testing.
|
234
|
+
if m := re.search(r"(?:ModuleNotFoundError: No module named|ImportError: cannot import name '(.*?)' from) '(.*?)'|AttributeError: module '(.*)?' has no attribute '(.*?)'", block):
|
235
|
+
if m.group(1):
|
236
|
+
# "ImportError: cannot import name 'function_field_polymod' from 'sage.rings.function_field' (unknown location)"
|
237
|
+
module = m.group(2) + '.' + m.group(1)
|
238
|
+
elif m.group(2):
|
239
|
+
# "ModuleNotFoundError: No module named ..."
|
240
|
+
module = m.group(2)
|
241
|
+
else:
|
242
|
+
# AttributeError: module 'sage.rings' has no attribute 'qqbar'
|
243
|
+
module = m.group(3) + '.' + m.group(4)
|
244
|
+
asked_why = re.search('#.*(why|explain)', src_in_lines[first_line_num - 1])
|
245
|
+
optional = module_feature(module)
|
246
|
+
if optional and optional.name not in file_optional_tags:
|
247
|
+
update_line_optional_tags(first_line_num - 1, add_tags=[optional.name],
|
248
|
+
message=f"Module '{module}' may be provided by feature '{optional.name}'; adding this tag")
|
249
|
+
if not asked_why:
|
250
|
+
# When no explanation has been demanded,
|
251
|
+
# we just mark the doctest with the feature
|
252
|
+
return
|
253
|
+
# Otherwise, continue and show the backtrace as 'GOT'
|
254
|
+
|
255
|
+
if 'Traceback (most recent call last):' in block:
|
256
|
+
|
257
|
+
expected, got = block.split('\nGot:\n')
|
258
|
+
if args.full_tracebacks:
|
259
|
+
if re.fullmatch(' *\n', got):
|
260
|
+
got = got[re.end(0):]
|
261
|
+
# don't show doctester internals (anything before first "<doctest...>" frame
|
262
|
+
if m := re.search('( *Traceback.*\n *)(?s:.*?)(^ *File "<doctest)( [^>]*)>', got, re.MULTILINE):
|
263
|
+
got = m.group(1) + '...\n' + m.group(2) + '...' + got[m.end(3):]
|
264
|
+
while m := re.search(' *File "<doctest( [^>]*)>', got):
|
265
|
+
got = got[:m.start(1)] + '...' + got[m.end(1):]
|
266
|
+
# simplify filenames shown in backtrace
|
267
|
+
while m := re.search('"([-a-zA-Z0-9._/]*/site-packages)/sage/', got):
|
268
|
+
got = got[:m.start(1)] + '...' + got[m.end(1):]
|
269
|
+
|
270
|
+
last_frame = got.rfind('File "')
|
271
|
+
if (last_frame >= 0
|
272
|
+
and (index_NameError := got.rfind("NameError:")) >= 0
|
273
|
+
and got[last_frame:].startswith('File "<doctest')):
|
274
|
+
if args.verbose:
|
275
|
+
print("sage-fixdoctests: This is a NameError from the top level of the doctest") # so we keep it brief
|
276
|
+
if m := re.match("NameError: name '(.*)'", got[index_NameError:]):
|
277
|
+
name = m.group(1)
|
278
|
+
if name in ['I', 'i']:
|
279
|
+
add_tags = ['sage.symbolic'] # This is how we mark it currently (2023-08)
|
280
|
+
elif len(name) >= 2 and (feature := name_feature(name)) and feature.name != 'sage.all':
|
281
|
+
# Don't mark use of 'x' '# needs sage.symbolic'; that's almost always wrong
|
282
|
+
# Likewise for variables like 'R', 'r'
|
283
|
+
add_tags = [feature.name] # FIXME: This feature may actually already be present in line, block, or file. Move this lookup code into the doctester and issue more specific instructions
|
284
|
+
elif args.only_tags:
|
285
|
+
if args.verbose:
|
286
|
+
print("sage-fixdoctests: No feature providing this global is known; no action because of --only-tags")
|
287
|
+
return
|
288
|
+
else:
|
289
|
+
add_tags = [f"NameError ('{name}', {venv_explainer.lstrip().lstrip('(')}"]
|
290
|
+
else:
|
291
|
+
if args.only_tags:
|
292
|
+
if args.verbose:
|
293
|
+
print("sage-fixdoctests: No feature providing this global is known; no action because of --only-tags")
|
294
|
+
return
|
295
|
+
add_tags = [f"NameError{venv_explainer}"]
|
296
|
+
update_line_optional_tags(first_line_num - 1, add_tags=add_tags,
|
297
|
+
message=f"Adding tag {add_tags}")
|
298
|
+
return
|
299
|
+
got = got.splitlines()
|
300
|
+
else:
|
301
|
+
got = got.splitlines()
|
302
|
+
got = ['Traceback (most recent call last):', '...', got[-1].lstrip()]
|
303
|
+
elif block[-21:] == 'Got:\n <BLANKLINE>\n':
|
304
|
+
expected = block[:-22]
|
305
|
+
got = ['']
|
306
|
+
else:
|
307
|
+
expected, got = block.split('\nGot:\n')
|
308
|
+
got = re.sub(r'(doctest:warning).*^( *DeprecationWarning:)',
|
309
|
+
r'\1...\n\2',
|
310
|
+
got, 1, re.DOTALL | re.MULTILINE)
|
311
|
+
got = got.splitlines() # got can't be the empty string
|
312
|
+
|
313
|
+
if args.only_tags:
|
314
|
+
if args.verbose:
|
315
|
+
print("sage-fixdoctests: No action because of --only-tags")
|
316
|
+
return
|
317
|
+
|
318
|
+
expected = expected.splitlines()
|
319
|
+
|
320
|
+
if args.keep_both:
|
321
|
+
test_lines = ([update_optional_tags(src_in_lines[first_line_num - 1],
|
322
|
+
add_tags=[f'GOT{venv_explainer}'])]
|
323
|
+
+ src_in_lines[first_line_num : line_num])
|
324
|
+
update_line_optional_tags(first_line_num - 1, add_tags=['EXPECTED'],
|
325
|
+
message="Marking the doctest with idempotent tag EXPECTED, creating another copy with idempotent tag GOT")
|
326
|
+
indent = (len(src_in_lines[line_num - 1]) - len(src_in_lines[line_num - 1].lstrip()))
|
327
|
+
line_num += len(expected) # skip to the last line of the expected output
|
328
|
+
append_to_line(line_num - 1, '\n'.join([''] + test_lines)) # 2nd copy of the test
|
329
|
+
# now line_num is the last line of the 2nd copy of the test
|
330
|
+
expected = []
|
331
|
+
|
332
|
+
# If we expected nothing, and got something, then we need to insert the line before line_num
|
333
|
+
# and match indentation with line number line_num-1
|
334
|
+
if not expected:
|
335
|
+
indent = (len(src_in_lines[first_line_num - 1]) - len(src_in_lines[first_line_num - 1].lstrip()))
|
336
|
+
append_to_line(line_num - 1,
|
337
|
+
'\n' + '\n'.join('%s%s' % (' ' * indent, line.lstrip()) for line in got),
|
338
|
+
message="Adding the new output")
|
339
|
+
return
|
340
|
+
|
341
|
+
# Guess how much extra indenting ``got`` needs to match with the indentation
|
342
|
+
# of src_in_lines - we match the indentation with the line in ``got`` which
|
343
|
+
# has the smallest indentation after lstrip(). Note that the amount of indentation
|
344
|
+
# required could be negative if the ``got`` block is indented. In this case
|
345
|
+
# ``indent`` is set to zero.
|
346
|
+
indent = max(0, (len(src_in_lines[line_num]) - len(src_in_lines[line_num].lstrip())
|
347
|
+
- min(len(got[j]) - len(got[j].lstrip()) for j in range(len(got)))))
|
348
|
+
|
349
|
+
# Double check that what was expected was indeed in the source file and if
|
350
|
+
# it is not then then print a warning for the user which contains the
|
351
|
+
# problematic lines.
|
352
|
+
if any(expected[i].strip() != src_in_lines[line_num + i].strip()
|
353
|
+
for i in range(len(expected))):
|
354
|
+
import warnings
|
355
|
+
txt = "Did not manage to replace\n%s\n%s\n%s\nwith\n%s\n%s\n%s"
|
356
|
+
warnings.warn(txt % ('>' * 40, '\n'.join(expected), '>' * 40,
|
357
|
+
'<' * 40, '\n'.join(got), '<' * 40))
|
358
|
+
return
|
359
|
+
|
360
|
+
# If we got nothing when we expected something then we delete the line from the
|
361
|
+
# output, otherwise, add all of what we `got` onto the end of src_in_lines[line_num]
|
362
|
+
if got == ['']:
|
363
|
+
update_line(line_num, None,
|
364
|
+
message="Expected something, got nothing; deleting the old output")
|
365
|
+
else:
|
366
|
+
update_line(line_num, '\n'.join((' ' * indent + got[i]) for i in range(len(got))),
|
367
|
+
message="Replacing the old expected output with the new output")
|
368
|
+
|
369
|
+
# Mark any remaining `expected` lines as ``None`` so as to preserve the line numbering
|
370
|
+
for i in range(1, len(expected)):
|
371
|
+
update_line(line_num + i, None)
|
372
|
+
|
373
|
+
|
374
|
+
# set input and output files
|
375
|
+
def output_filename(filename):
|
376
|
+
if len(args.filename) == 2 and not args.overwrite and not args.no_overwrite:
|
377
|
+
if args.filename[0] == filename:
|
378
|
+
return args.filename[1]
|
379
|
+
else:
|
380
|
+
return None
|
381
|
+
return filename + ".fixed"
|
382
|
+
if args.no_overwrite:
|
383
|
+
return filename + ".fixed"
|
384
|
+
return filename
|
385
|
+
|
386
|
+
|
387
|
+
tested_doctesters = set()
|
388
|
+
venv_files = {} # distribution -> files that are not yet known to be fixed points in venv; we add and remove items
|
389
|
+
venv_ignored_files = {} # distribution -> files that should be ignored; we only add items
|
390
|
+
unprocessed_files = set()
|
391
|
+
|
392
|
+
|
393
|
+
class BadDistribution(Exception):
|
394
|
+
pass
|
395
|
+
|
396
|
+
|
397
|
+
def doctest_blocks(args, input_filenames, distribution=None, venv=None, environment=None):
|
398
|
+
executable = f'{os.path.relpath(venv)}/bin/sage' if venv else 'sage'
|
399
|
+
environment_args = f'--environment {environment} ' if environment and environment != runtest_default_environment else ''
|
400
|
+
long_args = f'--long ' if args.long else ''
|
401
|
+
probe_args = f'--probe {shlex.quote(args.probe)} ' if args.probe else ''
|
402
|
+
lib_args = f'--if-installed ' if venv else ''
|
403
|
+
doc_file = tmp_filename()
|
404
|
+
if venv or environment_args:
|
405
|
+
# Test the doctester, putting the output of the test into sage's temporary directory
|
406
|
+
input = os.path.join(os.path.relpath(SAGE_ROOT), 'src', 'sage', 'version.py')
|
407
|
+
cmdline = f'{shlex.quote(executable)} -t {environment_args}{long_args}{probe_args}'.rstrip()
|
408
|
+
if cmdline not in tested_doctesters:
|
409
|
+
if args.verbose:
|
410
|
+
print(f'sage-fixdoctests: Checking whether the doctester "{cmdline}" works')
|
411
|
+
cmdline += f' {shlex.quote(input)}'
|
412
|
+
if status := os.waitstatus_to_exitcode(os.system(f'{cmdline} > {shlex.quote(doc_file)}')):
|
413
|
+
raise BadDistribution(f"Doctester exited with error status {status}")
|
414
|
+
tested_doctesters.add(cmdline)
|
415
|
+
# Run the doctester, putting the output of the test into sage's temporary directory
|
416
|
+
input_args = " ".join(shlex.quote(f) for f in input_filenames)
|
417
|
+
cmdline = f'{shlex.quote(executable)} -t -p {environment_args}{long_args}{probe_args}{lib_args}{input_args}'
|
418
|
+
print(f'Running "{cmdline}"')
|
419
|
+
os.system(f'{cmdline} > {shlex.quote(doc_file)}')
|
420
|
+
|
421
|
+
with open(doc_file, 'r') as doc:
|
422
|
+
doc_out = doc.read()
|
423
|
+
|
424
|
+
# Remove skipped files, echo control messages
|
425
|
+
for m in re.finditer(r"^Skipping '(.*?)'.*$", doc_out, re.MULTILINE):
|
426
|
+
print('sage-runtests: ' + m.group(0))
|
427
|
+
if distribution is not None:
|
428
|
+
venv_files[distribution].discard(m.group(1))
|
429
|
+
venv_ignored_files[distribution].add(m.group(1))
|
430
|
+
|
431
|
+
return doc_out.split(sep)
|
432
|
+
|
433
|
+
|
434
|
+
def block_filename(block):
|
435
|
+
if not (m := re.match('File "([^"]*)", line ([0-9]+), in ', block)):
|
436
|
+
return None
|
437
|
+
return m.group(1)
|
438
|
+
|
439
|
+
|
440
|
+
def expanded_filename_args():
|
441
|
+
DD = DocTestDefaults(optional='all', warn_long=10000)
|
442
|
+
DC = DocTestController(DD, input_filenames)
|
443
|
+
DC.add_files()
|
444
|
+
DC.expand_files_into_sources()
|
445
|
+
for source in DC.sources:
|
446
|
+
yield source.path
|
447
|
+
|
448
|
+
|
449
|
+
def process_grouped_blocks(grouped_iterator, distribution=None, venv=None, environment=None):
|
450
|
+
|
451
|
+
seen = set()
|
452
|
+
|
453
|
+
explainer = venv_explainer(distribution, venv, environment)
|
454
|
+
|
455
|
+
for input, blocks in grouped_iterator:
|
456
|
+
|
457
|
+
if not input: # Blocks of noise
|
458
|
+
continue
|
459
|
+
if input in seen:
|
460
|
+
continue
|
461
|
+
seen.add(input)
|
462
|
+
|
463
|
+
with open(input, 'r') as test_file:
|
464
|
+
src_in = test_file.read()
|
465
|
+
src_in_lines = src_in.splitlines()
|
466
|
+
shallow_copy_of_src_in_lines = list(src_in_lines)
|
467
|
+
file_optional_tags = set(parse_file_optional_tags(enumerate(src_in_lines)))
|
468
|
+
persistent_tags_counts = defaultdict(int)
|
469
|
+
tags_counts = defaultdict(int)
|
470
|
+
|
471
|
+
for block in blocks:
|
472
|
+
try:
|
473
|
+
process_block(block, src_in_lines, file_optional_tags, venv_explainer=explainer)
|
474
|
+
except Exception:
|
475
|
+
print('sage-fixdoctests: Failure to process block')
|
476
|
+
print(block)
|
477
|
+
|
478
|
+
# Now source line numbers do not matter any more, and lines can be real lines again
|
479
|
+
src_in_lines = list(itertools.chain.from_iterable(
|
480
|
+
[] if line is None else [''] if not line else line.splitlines()
|
481
|
+
for line in src_in_lines))
|
482
|
+
|
483
|
+
# Remove duplicate optional tags and rewrite all '# optional' that should be '# needs'
|
484
|
+
persistent_optional_tags = {}
|
485
|
+
persistent_optional_tags_counted = False
|
486
|
+
for i, line in enumerate(src_in_lines):
|
487
|
+
if m := re.match(' *sage: *(.*)#', line):
|
488
|
+
tags, line_sans_tags, is_persistent = parse_optional_tags(line, return_string_sans_tags=True)
|
489
|
+
if is_persistent:
|
490
|
+
persistent_optional_tags = {tag: explanation
|
491
|
+
for tag, explanation in tags.items()
|
492
|
+
if explanation or tag not in file_optional_tags}
|
493
|
+
persistent_optional_tags_counted = False
|
494
|
+
line = update_optional_tags(line, tags=persistent_optional_tags, force_rewrite='standard')
|
495
|
+
if re.fullmatch(' *sage: *', line):
|
496
|
+
# persistent (block-scoped or file-scoped) tag was removed, so remove the whole line
|
497
|
+
line = None
|
498
|
+
else:
|
499
|
+
tags = {tag: explanation
|
500
|
+
for tag, explanation in tags.items()
|
501
|
+
if explanation or (tag not in file_optional_tags
|
502
|
+
and tag not in persistent_optional_tags)}
|
503
|
+
line = update_optional_tags(line, tags=tags, force_rewrite='standard')
|
504
|
+
if not persistent_optional_tags_counted:
|
505
|
+
persistent_tags_counts[frozenset(persistent_optional_tags)] += 1
|
506
|
+
persistent_optional_tags_counted = True
|
507
|
+
tags_counts[frozenset(tags)] += 1
|
508
|
+
src_in_lines[i] = line
|
509
|
+
elif line.strip() in ['', '"""', "'''"]: # Blank line or end of docstring
|
510
|
+
persistent_optional_tags = {}
|
511
|
+
persistent_optional_tags_counted = False
|
512
|
+
elif re.match(' *sage: ', line):
|
513
|
+
if not persistent_optional_tags_counted:
|
514
|
+
persistent_tags_counts[frozenset(persistent_optional_tags)] += 1
|
515
|
+
persistent_optional_tags_counted = True
|
516
|
+
tags_counts[frozenset()] += 1
|
517
|
+
|
518
|
+
if src_in_lines != shallow_copy_of_src_in_lines:
|
519
|
+
if (output := output_filename(input)) is None:
|
520
|
+
print(f"sage-fixdoctests: Not saving modifications made in '{input}'")
|
521
|
+
else:
|
522
|
+
with open(output, 'w') as test_output:
|
523
|
+
for line in src_in_lines:
|
524
|
+
if line is None:
|
525
|
+
continue
|
526
|
+
test_output.write(line)
|
527
|
+
test_output.write('\n')
|
528
|
+
# Show summary of changes
|
529
|
+
if input != output:
|
530
|
+
print("sage-fixdoctests: The fixed doctests have been saved as '{0}'.".format(output))
|
531
|
+
else:
|
532
|
+
relative = os.path.relpath(output, SAGE_ROOT)
|
533
|
+
print(f"sage-fixdoctests: The input file '{output}' has been overwritten.")
|
534
|
+
if not args.no_diff and not relative.startswith('..'):
|
535
|
+
subprocess.call(['git', '--no-pager', 'diff', relative], cwd=SAGE_ROOT)
|
536
|
+
for other_distribution, file_set in venv_files.items():
|
537
|
+
if input not in venv_ignored_files[other_distribution]:
|
538
|
+
file_set.add(input)
|
539
|
+
else:
|
540
|
+
print(f"sage-fixdoctests: No fixes made in '{input}'")
|
541
|
+
if distribution is not None:
|
542
|
+
venv_files[distribution].discard(input)
|
543
|
+
|
544
|
+
unprocessed_files.discard(input)
|
545
|
+
|
546
|
+
if args.verbose:
|
547
|
+
if file_optional_tags:
|
548
|
+
print(f"File tags: ")
|
549
|
+
print(f" {' '.join(sorted(file_optional_tags))}")
|
550
|
+
if persistent_tags_counts:
|
551
|
+
print(f"Doctest blocks by persistent tags: ")
|
552
|
+
for tags, count in sorted(persistent_tags_counts.items(),
|
553
|
+
key=lambda i: i[1], reverse=True):
|
554
|
+
print(f"{count:6} {' '.join(sorted(tags)) or '(untagged)'}")
|
555
|
+
if tags_counts:
|
556
|
+
print(f"Doctest examples by tags: ")
|
557
|
+
for tags, count in sorted(tags_counts.items(),
|
558
|
+
key=lambda i: i[1], reverse=True):
|
559
|
+
print(f"{count:6} {' '.join(sorted(tags)) or '(untagged)'}")
|
560
|
+
|
561
|
+
|
562
|
+
def fix_with_distribution(file_set, distribution=None, toxenv=None, verbose=False):
|
563
|
+
if verbose:
|
564
|
+
print("#" * 78)
|
565
|
+
print(f"sage-fixdoctests: Fixing with --distribution={shlex.quote(distribution)}")
|
566
|
+
default_venv, default_environment = default_venv_environment_from_distribution(distribution, toxenv)
|
567
|
+
venv = args.venv or default_venv
|
568
|
+
environment = args.environment or default_environment
|
569
|
+
file_set_to_process = sorted(file_set)
|
570
|
+
file_set.clear()
|
571
|
+
try:
|
572
|
+
doctests = doctest_blocks(args, file_set_to_process,
|
573
|
+
distribution=distribution, venv=venv, environment=environment)
|
574
|
+
process_grouped_blocks(itertools.groupby(doctests, block_filename), # modifies file_set
|
575
|
+
distribution=distribution, venv=venv, environment=environment)
|
576
|
+
except BadDistribution as e:
|
577
|
+
if args.ignore_bad_distributions:
|
578
|
+
print(f"sage-fixdoctests: {e}, ignoring")
|
579
|
+
else:
|
580
|
+
sys.exit(f"sage-fixdoctests: {e}")
|
581
|
+
|
582
|
+
|
583
|
+
if __name__ == "__main__":
|
584
|
+
|
585
|
+
args = parser.parse_args()
|
586
|
+
|
587
|
+
if args.verbose:
|
588
|
+
args.no_diff = True
|
589
|
+
|
590
|
+
args.ignore_bad_distributions = False # This could also be a switch
|
591
|
+
|
592
|
+
args.update_failures_distribution = args.distribution
|
593
|
+
|
594
|
+
if args.distribution == ['all']:
|
595
|
+
args.distribution = ['sagemath-categories',
|
596
|
+
'sagemath-modules',
|
597
|
+
'sagemath-pari',
|
598
|
+
'sagemath-graphs', 'sagemath-graphs[modules]', 'sagemath-graphs[modules,pari]',
|
599
|
+
'sagemath-groups',
|
600
|
+
'sagemath-combinat', 'sagemath-combinat[graphs]', 'sagemath-combinat[modules]',
|
601
|
+
'sagemath-polyhedra', 'sagemath-polyhedra[standard]',
|
602
|
+
'sagemath-schemes', 'sagemath-schemes[ntl]', 'sagemath-schemes[pari]',
|
603
|
+
'sagemath-symbolics',
|
604
|
+
''] # monolithic distribution
|
605
|
+
args.update_failures_distribution = args.distribution + ['sagemath-repl', # not included above because it knows too little and complains too much
|
606
|
+
'sagemath-bliss',
|
607
|
+
'sagemath-coxeter3',
|
608
|
+
'sagemath-flint',
|
609
|
+
'sagemath-glpk',
|
610
|
+
'sagemath-linbox',
|
611
|
+
'sagemath-plot',
|
612
|
+
'sagemath-standard-no-symbolics']
|
613
|
+
|
614
|
+
args.ignore_bad_distributions = True
|
615
|
+
|
616
|
+
if not args.filename:
|
617
|
+
if not args.update_known_test_failures:
|
618
|
+
sys.exit("sage-fixdoctests: At least one filename is required when --update-known-test-failures is not used")
|
619
|
+
if not args.distribution:
|
620
|
+
sys.exit("sage-fixdoctests: At least one --distribution argument is required for --update-known-test-failures")
|
621
|
+
|
622
|
+
if args.distribution or args.venv or args.environment:
|
623
|
+
args.keep_both = args.full_tracebacks = True
|
624
|
+
|
625
|
+
if len(args.distribution) > 1:
|
626
|
+
if args.venv or args.environment:
|
627
|
+
sys.exit("sage-fixdoctests: at most one --distribution argument can be combined with --venv and --environment")
|
628
|
+
elif not args.distribution:
|
629
|
+
args.distribution = ['']
|
630
|
+
|
631
|
+
if len(args.filename) == 2 and not args.overwrite and not args.no_overwrite:
|
632
|
+
print("sage-fixdoctests: When passing two filenames, the second one is taken as an output filename; "
|
633
|
+
"this is deprecated. To pass two input filenames, use the option --overwrite.")
|
634
|
+
input_filenames = [args.filename[0]]
|
635
|
+
else:
|
636
|
+
input_filenames = args.filename
|
637
|
+
|
638
|
+
try:
|
639
|
+
unprocessed_files = set(expanded_filename_args())
|
640
|
+
for distribution in args.distribution:
|
641
|
+
venv_files[distribution] = set(unprocessed_files) # make copies
|
642
|
+
venv_ignored_files[distribution] = set()
|
643
|
+
if args.no_test:
|
644
|
+
pass
|
645
|
+
elif len(args.distribution) == 1 and not args.fixed_point:
|
646
|
+
fix_with_distribution(set(unprocessed_files), args.distribution[0], toxenv=args.toxenv)
|
647
|
+
else:
|
648
|
+
for distribution, file_set in venv_files.items():
|
649
|
+
fix_with_distribution(file_set, distribution, verbose=True, toxenv=args.toxenv)
|
650
|
+
if args.fixed_point:
|
651
|
+
if args.probe:
|
652
|
+
print(f"sage-fixdoctests: Turning off --probe for the following iterations")
|
653
|
+
# This forces convergence to a fixed point
|
654
|
+
args.probe = ''
|
655
|
+
while True:
|
656
|
+
# Run a distribution with largest number of files remaining to be checked
|
657
|
+
# because of the startup overhead of sage-runtests
|
658
|
+
distribution, file_set = max(venv_files.items(), key=lambda df: len(df[1]))
|
659
|
+
if not file_set:
|
660
|
+
break
|
661
|
+
while file_set:
|
662
|
+
fix_with_distribution(file_set, distribution, verbose=True, toxenv=args.toxenv)
|
663
|
+
# Immediately re-run with the same distribution to continue chains of
|
664
|
+
# "NameError" / "variable was set only in doctest" fixes
|
665
|
+
|
666
|
+
# Each file must be processed by process_grouped_blocks at least once to clean up tags,
|
667
|
+
# even if sage-runtest does not have any complaints.
|
668
|
+
if unprocessed_files:
|
669
|
+
print(f"sage-fixdoctests: Processing unprocessed files")
|
670
|
+
process_grouped_blocks([(filename, [])
|
671
|
+
for filename in unprocessed_files])
|
672
|
+
|
673
|
+
if args.fixed_point:
|
674
|
+
print(f"sage-fixdoctests: Fixed point reached")
|
675
|
+
|
676
|
+
if args.update_known_test_failures:
|
677
|
+
if args.update_failures_distribution == ['']:
|
678
|
+
print("sage-fixdoctests: Ignoring switch --update-known-test-failures because no --distribution was given")
|
679
|
+
else:
|
680
|
+
for distribution in sorted(args.update_failures_distribution):
|
681
|
+
if distribution == '':
|
682
|
+
continue
|
683
|
+
plain_distribution, extras = plain_distribution_and_extras(distribution)
|
684
|
+
default_venv, _ = default_venv_environment_from_distribution(distribution, args.toxenv)
|
685
|
+
venv = args.venv or default_venv
|
686
|
+
try:
|
687
|
+
stats_filename = os.path.join(default_venv, '.sage/timings2.json')
|
688
|
+
with open(stats_filename, 'r') as stats_file:
|
689
|
+
stats = json.load(stats_file)
|
690
|
+
except FileNotFoundError:
|
691
|
+
print(f"sage-fixdoctests: {os.path.relpath(stats_filename, SAGE_ROOT)} "
|
692
|
+
"does not exist (ignoring)")
|
693
|
+
else:
|
694
|
+
for d in stats.values():
|
695
|
+
del d['walltime']
|
696
|
+
stats = {k: d for k, d in stats.items()
|
697
|
+
if d.get('failed') or d.get('ntests', True)}
|
698
|
+
if extras:
|
699
|
+
extras_suffix = '--' + '--'.join(extras.split(','))
|
700
|
+
else:
|
701
|
+
extras_suffix = ''
|
702
|
+
failures_file = os.path.join(SAGE_ROOT, 'pkgs', plain_distribution,
|
703
|
+
f'known-test-failures{extras_suffix}.json')
|
704
|
+
with open(failures_file, 'w') as f:
|
705
|
+
json.dump(stats, f, sort_keys=True, indent=4)
|
706
|
+
print(f"sage-fixdoctests: Updated {os.path.relpath(failures_file, SAGE_ROOT)}")
|
707
|
+
|
708
|
+
except Exception:
|
709
|
+
print(f"sage-fixdoctests: Internal error")
|
710
|
+
raise
|