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,25 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
# We reuse the gdb pythonstartup script.
|
4
|
+
PYTHONSTARTUP=`dirname $0`/sage-ipython
|
5
|
+
export PYTHONSTARTUP
|
6
|
+
echo $PYTHONSTARTUP
|
7
|
+
if [ ! -d "$DOT_SAGE/valgrind" ]; then
|
8
|
+
mkdir "$DOT_SAGE/valgrind"
|
9
|
+
fi
|
10
|
+
|
11
|
+
LOG="$DOT_SAGE"/valgrind/sage-cachegrind.%p
|
12
|
+
echo "Log file is $LOG"
|
13
|
+
|
14
|
+
CACHEGRIND_FLAGS="--log-file=$LOG "; export CACHEGRIND_FLAGS
|
15
|
+
if [ "$SAGE_CACHEGRIND_FLAGS" ]; then
|
16
|
+
echo "Overwriting cachegrind flags with:"
|
17
|
+
echo $SAGE_CACHEGRIND_FLAGS
|
18
|
+
CACHEGRIND_FLAGS=$SAGE_CACHEGRIND_FLAGS; export CACHEGRIND_FLAGS
|
19
|
+
else
|
20
|
+
echo "Using default flags:"
|
21
|
+
echo $CACHEGRIND_FLAGS
|
22
|
+
fi
|
23
|
+
|
24
|
+
valgrind --tool=cachegrind $CACHEGRIND_FLAGS sage-python -i
|
25
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
if [ ! -d "$DOT_SAGE/valgrind" ]; then
|
4
|
+
mkdir "$DOT_SAGE/valgrind"
|
5
|
+
fi
|
6
|
+
LOG="$DOT_SAGE"/valgrind/sage-callgrind.%p
|
7
|
+
|
8
|
+
CALLGRIND_FLAGS="--callgrind-out-file=$LOG "
|
9
|
+
if [ "$SAGE_CALLGRIND_FLAGS" ]; then
|
10
|
+
echo "Overwriting callgrind flags with: $SAGE_CALLGRIND_FLAGS"
|
11
|
+
CALLGRIND_FLAGS=$SAGE_CALLGRIND_FLAGS
|
12
|
+
else
|
13
|
+
echo "Using default flags: $CALLGRIND_FLAGS"
|
14
|
+
fi
|
15
|
+
|
16
|
+
valgrind --tool=callgrind $CALLGRIND_FLAGS sage-ipython "$@" -i
|
@@ -0,0 +1,230 @@
|
|
1
|
+
#!python
|
2
|
+
|
3
|
+
#*****************************************************************************
|
4
|
+
# This is the sage monitor *daemon*, which cleans up after Sage.
|
5
|
+
# Some things that it cleans up:
|
6
|
+
# * $DOT_SAGE/temp/HOSTNAME/pid directories
|
7
|
+
# * Processes that Sage spawns. If a copy of Sage isn't
|
8
|
+
# running, then any process it spawned should have its
|
9
|
+
# process group killed
|
10
|
+
#*****************************************************************************
|
11
|
+
# Copyright (C) 2005, 2006, 2007 William Stein <wstein@gmail.com>
|
12
|
+
#
|
13
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
14
|
+
# as published by the Free Software Foundation; either version 2 of
|
15
|
+
# the License, or (at your option) any later version.
|
16
|
+
# http://www.gnu.org/licenses/
|
17
|
+
#*****************************************************************************
|
18
|
+
|
19
|
+
|
20
|
+
import os, shutil, sys, time, socket, errno, signal, atexit
|
21
|
+
|
22
|
+
|
23
|
+
HOSTNAME = os.environ.get('HOSTNAME', socket.gethostname())
|
24
|
+
DOT_SAGE = os.environ['DOT_SAGE']
|
25
|
+
|
26
|
+
SAGE_TMP_ROOT = os.path.join(DOT_SAGE, 'temp', HOSTNAME)
|
27
|
+
logfile = os.path.join(SAGE_TMP_ROOT, 'cleaner.log')
|
28
|
+
pidfile = os.path.join(SAGE_TMP_ROOT, 'cleaner.pid')
|
29
|
+
# pidfile used by earlier versions of sage-cleaner (before #15457)
|
30
|
+
old_pidfile = os.path.join(DOT_SAGE, 'temp', 'cleaner-%s.pid'%HOSTNAME)
|
31
|
+
|
32
|
+
import logging
|
33
|
+
logger = logging.getLogger(__name__)
|
34
|
+
|
35
|
+
|
36
|
+
def mkdir_p(path):
|
37
|
+
"""
|
38
|
+
A "mkdir -p" command that makes intermediate directories if necessary
|
39
|
+
"""
|
40
|
+
try:
|
41
|
+
os.makedirs(path)
|
42
|
+
except OSError:
|
43
|
+
if not os.path.isdir(path):
|
44
|
+
raise
|
45
|
+
|
46
|
+
|
47
|
+
def rm_rf(file_or_path):
|
48
|
+
"""
|
49
|
+
Force recursive delete, ignoring errors.
|
50
|
+
"""
|
51
|
+
try:
|
52
|
+
os.unlink(file_or_path)
|
53
|
+
except OSError as e:
|
54
|
+
if e.errno == errno.EISDIR or e.errno == errno.EPERM:
|
55
|
+
shutil.rmtree(file_or_path, ignore_errors=True)
|
56
|
+
|
57
|
+
|
58
|
+
def is_running(pid):
|
59
|
+
"""
|
60
|
+
Return True if and only if there is a process with id pid running.
|
61
|
+
"""
|
62
|
+
try:
|
63
|
+
os.kill(pid,0)
|
64
|
+
return True
|
65
|
+
except OSError:
|
66
|
+
return False
|
67
|
+
|
68
|
+
|
69
|
+
def cleanup():
|
70
|
+
tmp_dirs = os.listdir(SAGE_TMP_ROOT)
|
71
|
+
try:
|
72
|
+
tmp_dirs.remove('cleaner.pid')
|
73
|
+
tmp_dirs.remove('cleaner.log')
|
74
|
+
except ValueError:
|
75
|
+
logger.error('No cleaner pid/log in SAGE_TMP_ROOT')
|
76
|
+
# Convert strings to integers
|
77
|
+
pid_list = []
|
78
|
+
for dir_entry in tmp_dirs:
|
79
|
+
try:
|
80
|
+
pid_list.append(int(dir_entry))
|
81
|
+
except ValueError:
|
82
|
+
badfile = os.path.join(SAGE_TMP_ROOT, dir_entry)
|
83
|
+
logger.warning('File %s must not be in SAGE_TMP_ROOT, deleting', badfile)
|
84
|
+
rm_rf(badfile)
|
85
|
+
|
86
|
+
logger.info("Checking PIDs %s", pid_list)
|
87
|
+
for parent_pid in pid_list:
|
88
|
+
try:
|
89
|
+
if not is_running(parent_pid):
|
90
|
+
logger.info("Process %s is no longer running, so we clean up", parent_pid)
|
91
|
+
dir_name = os.path.join(SAGE_TMP_ROOT, str(parent_pid))
|
92
|
+
spawned_processes = os.path.join(dir_name, 'spawned_processes')
|
93
|
+
if not os.path.isfile(spawned_processes) \
|
94
|
+
or kill_spawned_jobs(spawned_processes, parent_pid):
|
95
|
+
logger.info('Deleting %s', dir_name)
|
96
|
+
rm_rf(dir_name)
|
97
|
+
except Exception:
|
98
|
+
logger.error("Exception while cleaning up PID %s:", parent_pid, exc_info=True)
|
99
|
+
|
100
|
+
return len(pid_list)
|
101
|
+
|
102
|
+
def cleanup_cruft():
|
103
|
+
""" remove directories leftover from improper shutdown """
|
104
|
+
tmp_dirs = os.listdir(SAGE_TMP_ROOT)
|
105
|
+
for dir_entry in tmp_dirs:
|
106
|
+
baddir = os.path.join(SAGE_TMP_ROOT, dir_entry)
|
107
|
+
if os.path.isdir(baddir):
|
108
|
+
logger.warning('Removing old directory %s from SAGE_TMP_ROOT', baddir)
|
109
|
+
rm_rf(baddir)
|
110
|
+
|
111
|
+
def kill_spawned_jobs(jobfile, parent_pid):
|
112
|
+
logger.info("Killing %s's spawned jobs", parent_pid)
|
113
|
+
killed_them_all = True
|
114
|
+
for job in open(jobfile).readlines():
|
115
|
+
try:
|
116
|
+
pid, cmd = job.strip().split(' ', 1)
|
117
|
+
pid = int(pid)
|
118
|
+
logger.info("--> Killing %r with PID %s and parent PID %s", cmd, pid, parent_pid)
|
119
|
+
except Exception:
|
120
|
+
logger.error("Exception while processing job %r from %s, ignoring", job, jobfile)
|
121
|
+
continue
|
122
|
+
try:
|
123
|
+
pgrp = os.getpgid(pid)
|
124
|
+
logger.info("--> Killing process group %s", pgrp)
|
125
|
+
os.killpg(pgrp, signal.SIGKILL)
|
126
|
+
except OSError:
|
127
|
+
pass
|
128
|
+
if is_running(pid):
|
129
|
+
logger.error("--> Failed to kill %s", pid)
|
130
|
+
# try again later
|
131
|
+
killed_them_all = False
|
132
|
+
return killed_them_all
|
133
|
+
|
134
|
+
|
135
|
+
def exit_handler():
|
136
|
+
logger.info("Removing pidfile and logfile")
|
137
|
+
rm_rf(pidfile)
|
138
|
+
rm_rf(logfile)
|
139
|
+
|
140
|
+
|
141
|
+
def setup_daemon():
|
142
|
+
mkdir_p(SAGE_TMP_ROOT)
|
143
|
+
|
144
|
+
logger.setLevel(logging.DEBUG)
|
145
|
+
h = logging.StreamHandler() # log to stderr
|
146
|
+
h.setLevel(logging.DEBUG)
|
147
|
+
logger.addHandler(h)
|
148
|
+
h = logging.FileHandler(filename=logfile) # log to logfile
|
149
|
+
h.setLevel(logging.INFO)
|
150
|
+
logger.addHandler(h)
|
151
|
+
|
152
|
+
logger.info("SAGE_TMP_ROOT = %s", SAGE_TMP_ROOT)
|
153
|
+
|
154
|
+
# Check old pidfile (to allow old and new sage-cleaners to peacefully coexist)
|
155
|
+
try:
|
156
|
+
pid = int(open(old_pidfile).read())
|
157
|
+
except (IOError, ValueError):
|
158
|
+
pass
|
159
|
+
else:
|
160
|
+
if is_running(pid):
|
161
|
+
logger.info("old sage-cleaner is already running with PID %s, exiting", pid)
|
162
|
+
sys.exit(0)
|
163
|
+
|
164
|
+
try:
|
165
|
+
pid = int(open(pidfile).read())
|
166
|
+
except (IOError, ValueError):
|
167
|
+
pass
|
168
|
+
else:
|
169
|
+
if is_running(pid):
|
170
|
+
logger.info("sage-cleaner is already running with PID %s, exiting", pid)
|
171
|
+
sys.exit(0)
|
172
|
+
with open(pidfile, 'w') as f:
|
173
|
+
f.write(str(os.getpid())) # initialize pid file
|
174
|
+
atexit.register(exit_handler)
|
175
|
+
|
176
|
+
|
177
|
+
def fix_old_mistakes():
|
178
|
+
"""
|
179
|
+
Experience is simply the name we give our mistakes.
|
180
|
+
"""
|
181
|
+
# inconsistently escaped hyphens with underscores (https://github.com/sagemath/sage/issues/14055)
|
182
|
+
wrong_hostname = HOSTNAME.replace('-','_').replace('/','_').replace('\\','_')
|
183
|
+
wrong_sage_tmp_root = os.path.join(DOT_SAGE, 'temp', wrong_hostname)
|
184
|
+
if wrong_sage_tmp_root != SAGE_TMP_ROOT and os.path.exists(wrong_sage_tmp_root):
|
185
|
+
logger.warning('Deleting invalid temp dir %s', wrong_sage_tmp_root)
|
186
|
+
rm_rf(wrong_sage_tmp_root)
|
187
|
+
|
188
|
+
# SAGE_TMP in DOT_SAGE/tmp instead of DOT_SAGE/temp
|
189
|
+
import glob
|
190
|
+
old_root = glob.glob(os.path.join(DOT_SAGE, 'tmp', HOSTNAME+'-*'))
|
191
|
+
if wrong_hostname != HOSTNAME:
|
192
|
+
old_root += glob.glob(os.path.join(DOT_SAGE, 'tmp', wrong_hostname+'-*'))
|
193
|
+
for old_tmp in old_root:
|
194
|
+
logger.warning('Deleting invalid temp dir %s', old_tmp)
|
195
|
+
rm_rf(old_tmp)
|
196
|
+
|
197
|
+
# PID file before it was moved into SAGE_TMP_ROOT
|
198
|
+
rm_rf(old_pidfile)
|
199
|
+
|
200
|
+
|
201
|
+
if __name__ == '__main__':
|
202
|
+
setup_daemon()
|
203
|
+
fix_old_mistakes()
|
204
|
+
logger.info("Starting sage-cleaner with PID %s", os.getpid())
|
205
|
+
cleanup_cruft()
|
206
|
+
|
207
|
+
if len(sys.argv) > 1:
|
208
|
+
wait = int(sys.argv[1])
|
209
|
+
else:
|
210
|
+
wait = 10
|
211
|
+
|
212
|
+
# Initial cleanup, ignore time
|
213
|
+
running_sages = cleanup()
|
214
|
+
cleanup_time = 0.0
|
215
|
+
count = 1
|
216
|
+
|
217
|
+
# In the first 10 iterations, continue anyway (even if there are
|
218
|
+
# no Sage processes running) because it can happen that Sage is
|
219
|
+
# not yet started.
|
220
|
+
while count < 10 or running_sages > 0:
|
221
|
+
# Time to wait = "wait" plus 20 times the time of last cleanup().
|
222
|
+
# This ensures that sage-cleaner causes a load of at most 5%.
|
223
|
+
time.sleep(wait + 20*cleanup_time)
|
224
|
+
count += 1
|
225
|
+
t0 = time.time()
|
226
|
+
running_sages = cleanup()
|
227
|
+
cleanup_time = time.time() - t0
|
228
|
+
logger.debug("cleanup() #{:d} took {:.2f}s".format(count, cleanup_time))
|
229
|
+
|
230
|
+
logger.info("sage-cleaner is finished")
|
@@ -0,0 +1,327 @@
|
|
1
|
+
#!python
|
2
|
+
import os
|
3
|
+
import sys
|
4
|
+
from tokenize import (NEWLINE, COMMENT, INDENT, DEDENT, STRING, NL,
|
5
|
+
NAME, OP, generate_tokens)
|
6
|
+
import argparse
|
7
|
+
|
8
|
+
parser = argparse.ArgumentParser(description='Look into Sage files for wrong doctests.')
|
9
|
+
parser.add_argument('filename', type=str, nargs='*', help='filename or a directory')
|
10
|
+
parser.add_argument('--all', action='store_true', help='give summary info about all files in the Sage library')
|
11
|
+
parser.add_argument('--only-bad', action='store_true', help='only print info for bad formatted files')
|
12
|
+
parser.add_argument('--summary', action='store_true', help='only print a short summary')
|
13
|
+
|
14
|
+
args = parser.parse_args()
|
15
|
+
|
16
|
+
|
17
|
+
def coverage_all(directory):
|
18
|
+
os.chdir(directory)
|
19
|
+
r = os.popen('sage-coverage * | grep SCORE').readlines()
|
20
|
+
|
21
|
+
s = []
|
22
|
+
scr = 0
|
23
|
+
total = 0
|
24
|
+
for x in r:
|
25
|
+
y = x.lstrip('SCORE ')
|
26
|
+
i = y.rfind(' of ')
|
27
|
+
j = y.rfind(')')
|
28
|
+
n = int(y[i+4:j])
|
29
|
+
|
30
|
+
i = y.rfind(':')
|
31
|
+
j = y.rfind('%')
|
32
|
+
scr += float(y[i+1:j]) * float(n)
|
33
|
+
|
34
|
+
total += n
|
35
|
+
|
36
|
+
s.append(y)
|
37
|
+
|
38
|
+
print(''.join(s))
|
39
|
+
|
40
|
+
# Issue #5859: Don't crash if there isn't anything to test.
|
41
|
+
score = 100.0
|
42
|
+
if total != 0:
|
43
|
+
score = (float(scr) / total)
|
44
|
+
|
45
|
+
print("Overall weighted coverage score: {:.1f}%".format(score))
|
46
|
+
print("Total number of functions: {}".format(total))
|
47
|
+
|
48
|
+
# Print up to 3 doctest coverage goals.
|
49
|
+
i = 0
|
50
|
+
for goal in [70, 75, 80, 85, 90, 95, 99]:
|
51
|
+
if score < goal:
|
52
|
+
i += 1
|
53
|
+
if i > 3: break
|
54
|
+
need = int((goal*total - scr)/100.0)
|
55
|
+
print("We need {:>4} more function{} to get to {}% coverage."
|
56
|
+
.format(need, "" if (need == 1) else "s", goal))
|
57
|
+
|
58
|
+
if args.all:
|
59
|
+
if not args.filename:
|
60
|
+
coverage_all(os.path.join(os.environ["SAGE_SRC"], 'sage'))
|
61
|
+
elif len(args.filename) == 1:
|
62
|
+
coverage_all(args.filename[0])
|
63
|
+
else:
|
64
|
+
print("sage-coverage: error: --all only accepts one filename argument")
|
65
|
+
sys.exit(1)
|
66
|
+
sys.exit(0)
|
67
|
+
|
68
|
+
if not args.filename:
|
69
|
+
print("sage-coverage: error: if --all is not given, at least one filename argument is expected")
|
70
|
+
sys.exit(1)
|
71
|
+
|
72
|
+
# Collect coverage results for one file
|
73
|
+
class CoverageResults:
|
74
|
+
def __init__(self, filename=""):
|
75
|
+
"""
|
76
|
+
INPUT:
|
77
|
+
|
78
|
+
- ``filename`` -- name of the file, only for display purposes.
|
79
|
+
"""
|
80
|
+
self.no_doc = []
|
81
|
+
self.no_test = []
|
82
|
+
self.good = []
|
83
|
+
self.possibly_wrong = []
|
84
|
+
self.filename = filename
|
85
|
+
|
86
|
+
def report(self):
|
87
|
+
"""
|
88
|
+
Print coverage results.
|
89
|
+
"""
|
90
|
+
num_functions = len(self.good) + len(self.no_doc) + len(self.no_test)
|
91
|
+
if not num_functions:
|
92
|
+
print("No functions in", self.filename)
|
93
|
+
return
|
94
|
+
|
95
|
+
score = (100.0 * len(self.good)) / float(num_functions)
|
96
|
+
|
97
|
+
print("SCORE {}: {:.1f}% ({} of {})".format(self.filename, score, len(self.good), num_functions))
|
98
|
+
|
99
|
+
if self.no_doc:
|
100
|
+
print("\nMissing documentation:")
|
101
|
+
for f in self.no_doc:
|
102
|
+
print(" *", f)
|
103
|
+
if self.no_test:
|
104
|
+
print("\nMissing doctests:")
|
105
|
+
for f in self.no_test:
|
106
|
+
print(" *", f)
|
107
|
+
|
108
|
+
if self.possibly_wrong:
|
109
|
+
print("\nPossibly wrong (function name doesn't occur in doctests):")
|
110
|
+
for f in self.possibly_wrong:
|
111
|
+
print(" *", f)
|
112
|
+
|
113
|
+
|
114
|
+
def handle_function(self, name, fullname, docstring):
|
115
|
+
"""
|
116
|
+
Check coverage of one function and store result.
|
117
|
+
|
118
|
+
INPUT:
|
119
|
+
|
120
|
+
- ``name`` -- bare function name (e.g. "foo")
|
121
|
+
|
122
|
+
- ``fullname`` -- complete function definition (e.g. "def foo(arg=None)")
|
123
|
+
|
124
|
+
- ``docstring`` -- the docstring, or ``None`` if there is no docstring
|
125
|
+
"""
|
126
|
+
# Skip certain names
|
127
|
+
if name in ['__dealloc__', '__new__', '_']:
|
128
|
+
return
|
129
|
+
|
130
|
+
if not docstring:
|
131
|
+
self.no_doc.append(fullname)
|
132
|
+
return
|
133
|
+
|
134
|
+
if "pytest" in docstring:
|
135
|
+
self.good.append(fullname)
|
136
|
+
return
|
137
|
+
|
138
|
+
if "sage: " not in docstring:
|
139
|
+
self.no_test.append(fullname)
|
140
|
+
return
|
141
|
+
|
142
|
+
# If the name is of the form _xxx_, then the doctest is always
|
143
|
+
# considered indirect.
|
144
|
+
if name[0] == "_" and name[-1] == "_":
|
145
|
+
is_indirect = True
|
146
|
+
else:
|
147
|
+
is_indirect = "indirect doctest" in docstring
|
148
|
+
|
149
|
+
if not is_indirect and not name in docstring:
|
150
|
+
self.possibly_wrong.append(fullname)
|
151
|
+
self.good.append(fullname)
|
152
|
+
|
153
|
+
|
154
|
+
def check_file(self, f):
|
155
|
+
"""
|
156
|
+
Check the coverage of one file.
|
157
|
+
|
158
|
+
INPUT:
|
159
|
+
|
160
|
+
- ``f``: an open file
|
161
|
+
|
162
|
+
OUTPUT: ``self``
|
163
|
+
"""
|
164
|
+
# Where are we in a function definition?
|
165
|
+
BEGINOFLINE = 0 # Beginning of new logical line
|
166
|
+
UNKNOWN = -99 # Not at all in a function definition
|
167
|
+
DEFNAMES = 1 # In function definition before first open paren
|
168
|
+
DEFARGS = 2 # In function arguments or between closing paren and final colon
|
169
|
+
DOCSTRING = -1 # Looking for docstring
|
170
|
+
|
171
|
+
state = BEGINOFLINE
|
172
|
+
|
173
|
+
# Previous token type seen
|
174
|
+
prevtyp = NEWLINE
|
175
|
+
|
176
|
+
# Indentation level
|
177
|
+
indent = 0
|
178
|
+
|
179
|
+
# Indentation level of last "def" statement
|
180
|
+
# or None if no such statement.
|
181
|
+
defindent = None
|
182
|
+
|
183
|
+
for (typ, tok, start, end, logical_line) in generate_tokens(f.readline):
|
184
|
+
# Completely ignore comments or continuation newlines
|
185
|
+
if typ == COMMENT or typ == NL:
|
186
|
+
continue
|
187
|
+
|
188
|
+
# Handle indentation
|
189
|
+
if typ == INDENT:
|
190
|
+
indent += 1
|
191
|
+
continue
|
192
|
+
elif typ == DEDENT:
|
193
|
+
indent -= 1
|
194
|
+
if (defindent is not None and indent <= defindent):
|
195
|
+
defindent = None
|
196
|
+
continue
|
197
|
+
|
198
|
+
# Check for "def" or "cpdef" ("cdef" functions don't need to be documented).
|
199
|
+
# Skip nested functions (with indent > defindent).
|
200
|
+
if state == BEGINOFLINE:
|
201
|
+
if typ == NAME and (tok in ["def", "cpdef"]) and (defindent is None or indent <= defindent):
|
202
|
+
state = DEFNAMES
|
203
|
+
deffullname = "line %s: "%start[0]
|
204
|
+
defparen = 0 # Number of open parentheses
|
205
|
+
else:
|
206
|
+
state = UNKNOWN
|
207
|
+
|
208
|
+
if state == DOCSTRING:
|
209
|
+
if typ != NEWLINE:
|
210
|
+
docstring = None
|
211
|
+
if typ == STRING:
|
212
|
+
docstring = tok
|
213
|
+
self.handle_function(defname, deffullname, docstring)
|
214
|
+
state = UNKNOWN
|
215
|
+
|
216
|
+
if state == DEFNAMES:
|
217
|
+
if typ == NAME:
|
218
|
+
if tok == "class": # Make sure that cdef classes are ignored
|
219
|
+
state = UNKNOWN
|
220
|
+
# Last NAME token before opening parenthesis is
|
221
|
+
# the function name.
|
222
|
+
defname = tok
|
223
|
+
elif tok == '(':
|
224
|
+
state = DEFARGS
|
225
|
+
else:
|
226
|
+
state = UNKNOWN
|
227
|
+
|
228
|
+
if state == DEFARGS:
|
229
|
+
if tok == '(':
|
230
|
+
defparen += 1
|
231
|
+
elif tok == ')':
|
232
|
+
defparen -= 1
|
233
|
+
elif defparen == 0 and tok == ':':
|
234
|
+
state = DOCSTRING
|
235
|
+
defindent = indent
|
236
|
+
elif typ == NEWLINE:
|
237
|
+
state = UNKNOWN
|
238
|
+
|
239
|
+
if state > 0:
|
240
|
+
# Append tok string to deffullname
|
241
|
+
if prevtyp == NAME and typ == NAME:
|
242
|
+
deffullname += ' '
|
243
|
+
elif prevtyp == OP and deffullname[-1] in ",":
|
244
|
+
deffullname += ' '
|
245
|
+
deffullname += tok
|
246
|
+
|
247
|
+
# New line?
|
248
|
+
if state == UNKNOWN and typ == NEWLINE:
|
249
|
+
state = BEGINOFLINE
|
250
|
+
|
251
|
+
prevtyp = typ
|
252
|
+
|
253
|
+
return self
|
254
|
+
|
255
|
+
|
256
|
+
# Data reported by --summary
|
257
|
+
good = 0
|
258
|
+
no_doc = 0
|
259
|
+
no_test = 0
|
260
|
+
possibly_wrong = 0
|
261
|
+
bad_files = []
|
262
|
+
|
263
|
+
first = True
|
264
|
+
|
265
|
+
|
266
|
+
def go(filename):
|
267
|
+
r"""
|
268
|
+
If ``filename`` is a file, launch the inspector on this file. If
|
269
|
+
``filename`` is a directory then recursively launch this function on the
|
270
|
+
files it contains.
|
271
|
+
"""
|
272
|
+
if os.path.isdir(filename):
|
273
|
+
for F in sorted(os.listdir(filename)):
|
274
|
+
go(os.path.join(filename, F))
|
275
|
+
if not os.path.exists(filename):
|
276
|
+
print("File %s does not exist."%filename, file=sys.stderr)
|
277
|
+
sys.exit(1)
|
278
|
+
|
279
|
+
if not (filename.endswith('.py')
|
280
|
+
or filename.endswith('.pyx')
|
281
|
+
or filename.endswith('.sage')):
|
282
|
+
return
|
283
|
+
|
284
|
+
# Filter pytest files which are not supposed to have doctests
|
285
|
+
if filename.endswith('_test.py'):
|
286
|
+
return
|
287
|
+
|
288
|
+
with open(filename, 'r') as f:
|
289
|
+
cr = CoverageResults(filename).check_file(f)
|
290
|
+
bad = cr.no_doc or cr.no_test or cr.possibly_wrong
|
291
|
+
|
292
|
+
# Update the global variables
|
293
|
+
if args.summary:
|
294
|
+
global good, no_doc, no_test, possibly_wrong, bad_files
|
295
|
+
no_doc += len(cr.no_doc)
|
296
|
+
no_test += len(cr.no_test)
|
297
|
+
possibly_wrong += len(cr.possibly_wrong)
|
298
|
+
good += len(cr.good)
|
299
|
+
if bad:
|
300
|
+
bad_files.append(filename)
|
301
|
+
return
|
302
|
+
if not bad and args.only_bad:
|
303
|
+
return
|
304
|
+
|
305
|
+
global first
|
306
|
+
if first:
|
307
|
+
print('-' * 72)
|
308
|
+
first = False
|
309
|
+
|
310
|
+
cr.report() # Print the report
|
311
|
+
print('-' * 72)
|
312
|
+
|
313
|
+
|
314
|
+
for arg in args.filename:
|
315
|
+
go(arg)
|
316
|
+
|
317
|
+
if args.summary:
|
318
|
+
num_functions = good + no_doc + no_test
|
319
|
+
score = (100.0 * good) / float(num_functions)
|
320
|
+
print("Global score: {:.1f}% ({} of {})\n".format(score, good, num_functions))
|
321
|
+
print("{} files with wrong documentation".format(len(bad_files)))
|
322
|
+
print("{} functions with no doc".format(no_doc))
|
323
|
+
print("{} functions with no test".format(no_test))
|
324
|
+
print("{} doctest are potentially wrong".format(possibly_wrong))
|
325
|
+
print("\nFiles with wrong documentation:")
|
326
|
+
print("-------------------------------")
|
327
|
+
print("\n".join(" {}".format(filename) for filename in bad_files))
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!python
|
2
|
+
|
3
|
+
import sys
|
4
|
+
from sage.all import *
|
5
|
+
try:
|
6
|
+
from sage.calculus.predefined import x
|
7
|
+
except ImportError:
|
8
|
+
pass
|
9
|
+
from sage.repl.preparse import preparse
|
10
|
+
|
11
|
+
if len(sys.argv) > 1:
|
12
|
+
s = preparse(" ".join(sys.argv[1:]))
|
13
|
+
eval(compile(s,'<cmdline>','exec'))
|
14
|
+
|