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
sage/doctest/sources.py
ADDED
@@ -0,0 +1,1657 @@
|
|
1
|
+
# sage_setup: distribution = sagemath-repl
|
2
|
+
"""
|
3
|
+
Classes for sources of doctests
|
4
|
+
|
5
|
+
This module defines various classes for sources from which doctests
|
6
|
+
originate, such as files, functions or database entries.
|
7
|
+
|
8
|
+
AUTHORS:
|
9
|
+
|
10
|
+
- David Roe (2012-03-27) -- initial version, based on Robert Bradshaw's code.
|
11
|
+
"""
|
12
|
+
|
13
|
+
# ****************************************************************************
|
14
|
+
# Copyright (C) 2012-2013 David Roe <roed.math@gmail.com>
|
15
|
+
# 2012 Robert Bradshaw <robertwb@gmail.com>
|
16
|
+
# 2012 William Stein <wstein@gmail.com>
|
17
|
+
# 2013 R. Andrew Ohana
|
18
|
+
# 2013-2017 Jeroen Demeyer <jdemeyer@cage.ugent.be>
|
19
|
+
# 2013-2019 John H. Palmieri
|
20
|
+
# 2014 Volker Braun
|
21
|
+
# 2014-2022 Frédéric Chapoton
|
22
|
+
# 2017 Erik M. Bray
|
23
|
+
# 2021 Sébastien Labbé
|
24
|
+
# 2021-2023 Matthias Koeppe
|
25
|
+
#
|
26
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
27
|
+
#
|
28
|
+
# https://www.gnu.org/licenses/
|
29
|
+
# ****************************************************************************
|
30
|
+
|
31
|
+
import os
|
32
|
+
import sys
|
33
|
+
import re
|
34
|
+
import random
|
35
|
+
import doctest
|
36
|
+
from sage.cpython.string import bytes_to_str
|
37
|
+
from sage.repl.load import load
|
38
|
+
from sage.misc.lazy_attribute import lazy_attribute
|
39
|
+
from sage.misc.package_dir import is_package_or_sage_namespace_package_dir
|
40
|
+
from .parsing import SageDocTestParser
|
41
|
+
from .util import NestedName
|
42
|
+
from sage.structure.dynamic_class import dynamic_class
|
43
|
+
from sage.env import SAGE_SRC, SAGE_LIB
|
44
|
+
|
45
|
+
# Python file parsing
|
46
|
+
triple_quotes = re.compile(r"\s*[rRuU]*((''')|(\"\"\"))")
|
47
|
+
name_regex = re.compile(r".*\s(\w+)([(].*)?:")
|
48
|
+
|
49
|
+
# LaTeX file parsing
|
50
|
+
begin_verb = re.compile(r"\s*\\begin{verbatim}")
|
51
|
+
end_verb = re.compile(r"\s*\\end{verbatim}\s*(%link)?")
|
52
|
+
begin_lstli = re.compile(r"\s*\\begin{lstlisting}")
|
53
|
+
end_lstli = re.compile(r"\s*\\end{lstlisting}\s*(%link)?")
|
54
|
+
skip = re.compile(r".*%skip.*")
|
55
|
+
|
56
|
+
# ReST file parsing
|
57
|
+
link_all = re.compile(r"^\s*\.\.\s+linkall\s*$")
|
58
|
+
double_colon = re.compile(r"^(\s*).*::\s*$")
|
59
|
+
code_block = re.compile(r"^(\s*)[.][.]\s*code-block\s*::.*$")
|
60
|
+
|
61
|
+
whitespace = re.compile(r"\s*")
|
62
|
+
bitness_marker = re.compile('#.*(32|64)-bit')
|
63
|
+
bitness_value = '64' if sys.maxsize > (1 << 32) else '32'
|
64
|
+
|
65
|
+
# For neutralizing doctests
|
66
|
+
find_prompt = re.compile(r"^(\s*)(>>>|sage:)(.*)")
|
67
|
+
|
68
|
+
# For testing that enough doctests are created
|
69
|
+
sagestart = re.compile(r"^(\s*(>>> |sage: ))\s*[^#\s]")
|
70
|
+
untested = re.compile("(not implemented|not tested)")
|
71
|
+
|
72
|
+
# For parsing a PEP 0263 encoding declaration
|
73
|
+
pep_0263 = re.compile(br'^[ \t\v]*#.*?coding[:=]\s*([-\w.]+)')
|
74
|
+
|
75
|
+
# Source line number in warning output
|
76
|
+
doctest_line_number = re.compile(r"^\s*doctest:[0-9]")
|
77
|
+
|
78
|
+
|
79
|
+
def get_basename(path):
|
80
|
+
"""
|
81
|
+
This function returns the basename of the given path, e.g.
|
82
|
+
``sage.doctest.sources`` or ``doc.ru.tutorial.tour_advanced``.
|
83
|
+
|
84
|
+
EXAMPLES::
|
85
|
+
|
86
|
+
sage: from sage.doctest.sources import get_basename
|
87
|
+
sage: import os
|
88
|
+
sage: get_basename(sage.doctest.sources.__file__)
|
89
|
+
'sage.doctest.sources'
|
90
|
+
sage: get_basename(os.path.join(sage.structure.__path__[0], 'element.pxd'))
|
91
|
+
'sage.structure.element.pxd'
|
92
|
+
"""
|
93
|
+
if path is None:
|
94
|
+
return None
|
95
|
+
if not os.path.exists(path):
|
96
|
+
return path
|
97
|
+
path = os.path.abspath(path)
|
98
|
+
root = os.path.dirname(path)
|
99
|
+
# If the file is in the sage library, we can use our knowledge of
|
100
|
+
# the directory structure
|
101
|
+
dev = SAGE_SRC
|
102
|
+
sp = SAGE_LIB
|
103
|
+
if path.startswith(dev):
|
104
|
+
# there will be a branch name
|
105
|
+
i = path.find(os.path.sep, len(dev))
|
106
|
+
if i == -1:
|
107
|
+
# this source is the whole library....
|
108
|
+
return path
|
109
|
+
root = path[:i]
|
110
|
+
elif path.startswith(sp):
|
111
|
+
root = path[:len(sp)]
|
112
|
+
else:
|
113
|
+
# If this file is in some python package we can see how deep
|
114
|
+
# it goes.
|
115
|
+
while is_package_or_sage_namespace_package_dir(root):
|
116
|
+
root = os.path.dirname(root)
|
117
|
+
fully_qualified_path, ext = os.path.splitext(path[len(root) + 1:])
|
118
|
+
if os.path.split(path)[1] == '__init__.py':
|
119
|
+
fully_qualified_path = fully_qualified_path[:-9]
|
120
|
+
basename = fully_qualified_path.replace(os.path.sep, '.')
|
121
|
+
if ext in ['.pxd', '.pxi']:
|
122
|
+
# disambiguate from .pyx with the same basename
|
123
|
+
basename += ext
|
124
|
+
return basename
|
125
|
+
|
126
|
+
|
127
|
+
class DocTestSource:
|
128
|
+
"""
|
129
|
+
This class provides a common base class for different sources of doctests.
|
130
|
+
|
131
|
+
INPUT:
|
132
|
+
|
133
|
+
- ``options`` -- a :class:`sage.doctest.control.DocTestDefaults`
|
134
|
+
instance or equivalent
|
135
|
+
"""
|
136
|
+
def __init__(self, options):
|
137
|
+
"""
|
138
|
+
Initialization.
|
139
|
+
|
140
|
+
EXAMPLES::
|
141
|
+
|
142
|
+
sage: from sage.doctest.control import DocTestDefaults
|
143
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
144
|
+
sage: filename = sage.doctest.sources.__file__
|
145
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
146
|
+
sage: TestSuite(FDS).run()
|
147
|
+
"""
|
148
|
+
self.options = options
|
149
|
+
|
150
|
+
def __eq__(self, other):
|
151
|
+
"""
|
152
|
+
Comparison is just by comparison of attributes.
|
153
|
+
|
154
|
+
EXAMPLES::
|
155
|
+
|
156
|
+
sage: from sage.doctest.control import DocTestDefaults
|
157
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
158
|
+
sage: filename = sage.doctest.sources.__file__
|
159
|
+
sage: DD = DocTestDefaults()
|
160
|
+
sage: FDS = FileDocTestSource(filename, DD)
|
161
|
+
sage: FDS2 = FileDocTestSource(filename, DD)
|
162
|
+
sage: FDS == FDS2
|
163
|
+
True
|
164
|
+
"""
|
165
|
+
if type(self) is not type(other):
|
166
|
+
return False
|
167
|
+
return self.__dict__ == other.__dict__
|
168
|
+
|
169
|
+
def __ne__(self, other):
|
170
|
+
"""
|
171
|
+
Test for non-equality.
|
172
|
+
|
173
|
+
EXAMPLES::
|
174
|
+
|
175
|
+
sage: from sage.doctest.control import DocTestDefaults
|
176
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
177
|
+
sage: filename = sage.doctest.sources.__file__
|
178
|
+
sage: DD = DocTestDefaults()
|
179
|
+
sage: FDS = FileDocTestSource(filename, DD)
|
180
|
+
sage: FDS2 = FileDocTestSource(filename, DD)
|
181
|
+
sage: FDS != FDS2
|
182
|
+
False
|
183
|
+
"""
|
184
|
+
return not (self == other)
|
185
|
+
|
186
|
+
def _process_doc(self, doctests, doc, namespace, start):
|
187
|
+
"""
|
188
|
+
Appends doctests defined in ``doc`` to the list ``doctests``.
|
189
|
+
|
190
|
+
This function is called when a docstring block is completed
|
191
|
+
(either by ending a triple quoted string in a Python file,
|
192
|
+
unindenting from a comment block in a ReST file, or ending a
|
193
|
+
verbatim or lstlisting environment in a LaTeX file).
|
194
|
+
|
195
|
+
INPUT:
|
196
|
+
|
197
|
+
- ``doctests`` -- a running list of doctests to which the new
|
198
|
+
test(s) will be appended
|
199
|
+
|
200
|
+
- ``doc`` -- list of lines of a docstring, each including
|
201
|
+
the trailing newline
|
202
|
+
|
203
|
+
- ``namespace`` -- dictionary or
|
204
|
+
:class:`sage.doctest.util.RecordingDict`, used in the
|
205
|
+
creation of new :class:`doctest.DocTest` s
|
206
|
+
|
207
|
+
- ``start`` -- integer giving the line number of the start
|
208
|
+
of this docstring in the larger file
|
209
|
+
|
210
|
+
EXAMPLES::
|
211
|
+
|
212
|
+
sage: from sage.doctest.control import DocTestDefaults
|
213
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
214
|
+
sage: from sage.doctest.parsing import SageDocTestParser
|
215
|
+
sage: filename = sage.doctest.util.__file__
|
216
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
217
|
+
sage: doctests, _ = FDS.create_doctests({})
|
218
|
+
sage: manual_doctests = []
|
219
|
+
sage: for dt in doctests:
|
220
|
+
....: FDS.qualified_name = dt.name
|
221
|
+
....: FDS._process_doc(manual_doctests, dt.docstring, {}, dt.lineno-1)
|
222
|
+
sage: doctests == manual_doctests
|
223
|
+
True
|
224
|
+
"""
|
225
|
+
docstring = "".join(doc)
|
226
|
+
new_doctests = self.parse_docstring(docstring, namespace, start)
|
227
|
+
sig_on_count_doc_doctest = "sig_on_count() # check sig_on/off pairings (virtual doctest)\n"
|
228
|
+
for dt in new_doctests:
|
229
|
+
if len(dt.examples) > 0 and not (hasattr(dt.examples[-1], 'sage_source')
|
230
|
+
and dt.examples[-1].sage_source == sig_on_count_doc_doctest):
|
231
|
+
# Line number refers to the end of the docstring
|
232
|
+
sigon = doctest.Example(sig_on_count_doc_doctest, "0\n", lineno=docstring.count("\n"))
|
233
|
+
sigon.sage_source = sig_on_count_doc_doctest
|
234
|
+
sigon.optional_tags = frozenset(self.file_optional_tags)
|
235
|
+
sigon.probed_tags = frozenset()
|
236
|
+
dt.examples.append(sigon)
|
237
|
+
doctests.append(dt)
|
238
|
+
|
239
|
+
@lazy_attribute
|
240
|
+
def file_optional_tags(self):
|
241
|
+
r"""
|
242
|
+
Return the set of tags that should apply to all doctests in this source.
|
243
|
+
|
244
|
+
This default implementation just returns the empty set.
|
245
|
+
|
246
|
+
EXAMPLES::
|
247
|
+
|
248
|
+
sage: from sage.doctest.control import DocTestDefaults
|
249
|
+
sage: from sage.doctest.sources import StringDocTestSource, PythonSource
|
250
|
+
sage: from sage.structure.dynamic_class import dynamic_class
|
251
|
+
sage: s = "'''\n sage: 2 + 2\n 4\n'''"
|
252
|
+
sage: PythonStringSource = dynamic_class('PythonStringSource', (StringDocTestSource, PythonSource))
|
253
|
+
sage: PSS = PythonStringSource('<runtime>', s, DocTestDefaults(), 'runtime')
|
254
|
+
sage: PSS.file_optional_tags
|
255
|
+
set()
|
256
|
+
"""
|
257
|
+
return set()
|
258
|
+
|
259
|
+
def _create_doctests(self, namespace, tab_okay=None):
|
260
|
+
"""
|
261
|
+
Create a list of doctests defined in this source.
|
262
|
+
|
263
|
+
This function collects functionality common to file and string
|
264
|
+
sources, and is called by
|
265
|
+
:meth:`FileDocTestSource.create_doctests`.
|
266
|
+
|
267
|
+
INPUT:
|
268
|
+
|
269
|
+
- ``namespace`` -- dictionary or
|
270
|
+
:class:`sage.doctest.util.RecordingDict`, used in the
|
271
|
+
creation of new :class:`doctest.DocTest` s.
|
272
|
+
|
273
|
+
- ``tab_okay`` -- whether tabs are allowed in this source
|
274
|
+
|
275
|
+
OUTPUT:
|
276
|
+
|
277
|
+
- ``doctests`` -- list of doctests defined by this source
|
278
|
+
|
279
|
+
- ``extras`` -- dictionary with ``extras['tab']`` either
|
280
|
+
``False`` or a list of linenumbers on which tabs appear
|
281
|
+
|
282
|
+
EXAMPLES::
|
283
|
+
|
284
|
+
sage: from sage.doctest.control import DocTestDefaults
|
285
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
286
|
+
sage: from sage.doctest.util import NestedName
|
287
|
+
sage: filename = sage.doctest.sources.__file__
|
288
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
289
|
+
sage: FDS.qualified_name = NestedName('sage.doctest.sources')
|
290
|
+
sage: doctests, extras = FDS._create_doctests({})
|
291
|
+
sage: len(doctests)
|
292
|
+
43
|
293
|
+
sage: extras['tab']
|
294
|
+
False
|
295
|
+
sage: extras['line_number']
|
296
|
+
False
|
297
|
+
"""
|
298
|
+
if tab_okay is None:
|
299
|
+
tab_okay = isinstance(self, TexSource)
|
300
|
+
self._init()
|
301
|
+
self.line_shift = 0
|
302
|
+
self.parser = SageDocTestParser(self.options.optional,
|
303
|
+
self.options.long,
|
304
|
+
probed_tags=self.options.probe,
|
305
|
+
file_optional_tags=self.file_optional_tags)
|
306
|
+
self.linking = False
|
307
|
+
doctests = []
|
308
|
+
in_docstring = False
|
309
|
+
unparsed_doc = False
|
310
|
+
doc = []
|
311
|
+
start = None
|
312
|
+
tab_locations = []
|
313
|
+
contains_line_number = False
|
314
|
+
for lineno, line in self:
|
315
|
+
if doctest_line_number.search(line) is not None:
|
316
|
+
contains_line_number = True
|
317
|
+
if "\t" in line:
|
318
|
+
tab_locations.append(str(lineno+1))
|
319
|
+
if "SAGE_DOCTEST_ALLOW_TABS" in line:
|
320
|
+
tab_okay = True
|
321
|
+
just_finished = False
|
322
|
+
if in_docstring:
|
323
|
+
if self.ending_docstring(line):
|
324
|
+
in_docstring = False
|
325
|
+
just_finished = True
|
326
|
+
self._process_doc(doctests, doc, namespace, start)
|
327
|
+
unparsed_doc = False
|
328
|
+
else:
|
329
|
+
bitness = bitness_marker.search(line)
|
330
|
+
if bitness:
|
331
|
+
if bitness.groups()[0] != bitness_value:
|
332
|
+
self.line_shift += 1
|
333
|
+
continue
|
334
|
+
else:
|
335
|
+
line = line[:bitness.start()] + "\n"
|
336
|
+
if self.line_shift and (m := sagestart.match(line)):
|
337
|
+
# We insert empty doctest lines to make up for the removed lines
|
338
|
+
indent_and_prompt = m.group(1)
|
339
|
+
doc.extend([indent_and_prompt + "# inserted to compensate for removed conditional doctest output\n"]
|
340
|
+
* self.line_shift)
|
341
|
+
self.line_shift = 0
|
342
|
+
doc.append(line)
|
343
|
+
unparsed_doc = True
|
344
|
+
if not in_docstring and (not just_finished or self.start_finish_can_overlap):
|
345
|
+
# to get line numbers in linked docstrings correct we
|
346
|
+
# append a blank line to the doc list.
|
347
|
+
doc.append("\n")
|
348
|
+
if not line.strip():
|
349
|
+
continue
|
350
|
+
if self.starting_docstring(line):
|
351
|
+
in_docstring = True
|
352
|
+
if self.linking:
|
353
|
+
# If there's already a doctest, we overwrite it.
|
354
|
+
if len(doctests) > 0:
|
355
|
+
doctests.pop()
|
356
|
+
if start is None:
|
357
|
+
start = lineno
|
358
|
+
doc = []
|
359
|
+
else:
|
360
|
+
self.line_shift = 0
|
361
|
+
start = lineno
|
362
|
+
doc = []
|
363
|
+
# In ReST files we can end the file without decreasing the indentation level.
|
364
|
+
if unparsed_doc:
|
365
|
+
self._process_doc(doctests, doc, namespace, start)
|
366
|
+
|
367
|
+
extras = {"tab": not tab_okay and tab_locations,
|
368
|
+
"line_number": contains_line_number,
|
369
|
+
"optionals": self.parser.optionals}
|
370
|
+
if self.options.randorder is not None and self.options.randorder is not False:
|
371
|
+
# we want to randomize even when self.randorder = 0
|
372
|
+
random.seed(self.options.randorder)
|
373
|
+
randomized = []
|
374
|
+
while doctests:
|
375
|
+
i = random.randint(0, len(doctests) - 1)
|
376
|
+
randomized.append(doctests.pop(i))
|
377
|
+
return randomized, extras
|
378
|
+
else:
|
379
|
+
return doctests, extras
|
380
|
+
|
381
|
+
|
382
|
+
class StringDocTestSource(DocTestSource):
|
383
|
+
r"""
|
384
|
+
This class creates doctests from a string.
|
385
|
+
|
386
|
+
INPUT:
|
387
|
+
|
388
|
+
- ``basename`` -- string such as 'sage.doctests.sources', going
|
389
|
+
into the names of created doctests and examples
|
390
|
+
|
391
|
+
- ``source`` -- string, giving the source code to be parsed for
|
392
|
+
doctests
|
393
|
+
|
394
|
+
- ``options`` -- a :class:`sage.doctest.control.DocTestDefaults`
|
395
|
+
or equivalent
|
396
|
+
|
397
|
+
- ``printpath`` -- string, to be used in place of a filename
|
398
|
+
when doctest failures are displayed
|
399
|
+
|
400
|
+
- ``lineno_shift`` -- integer (default: 0) by which to shift
|
401
|
+
the line numbers of all doctests defined in this string
|
402
|
+
|
403
|
+
EXAMPLES::
|
404
|
+
|
405
|
+
sage: from sage.doctest.control import DocTestDefaults
|
406
|
+
sage: from sage.doctest.sources import StringDocTestSource, PythonSource
|
407
|
+
sage: from sage.structure.dynamic_class import dynamic_class
|
408
|
+
sage: s = "'''\n sage: 2 + 2\n 4\n'''"
|
409
|
+
sage: PythonStringSource = dynamic_class('PythonStringSource',(StringDocTestSource, PythonSource))
|
410
|
+
sage: PSS = PythonStringSource('<runtime>', s, DocTestDefaults(), 'runtime')
|
411
|
+
sage: dt, extras = PSS.create_doctests({})
|
412
|
+
sage: len(dt)
|
413
|
+
1
|
414
|
+
sage: extras['tab']
|
415
|
+
[]
|
416
|
+
sage: extras['line_number']
|
417
|
+
False
|
418
|
+
|
419
|
+
sage: s = "'''\n\tsage: 2 + 2\n\t4\n'''"
|
420
|
+
sage: PSS = PythonStringSource('<runtime>', s, DocTestDefaults(), 'runtime')
|
421
|
+
sage: dt, extras = PSS.create_doctests({})
|
422
|
+
sage: extras['tab']
|
423
|
+
['2', '3']
|
424
|
+
|
425
|
+
sage: s = "'''\n sage: import warnings; warnings.warn('foo')\n doctest:1: UserWarning: foo \n'''"
|
426
|
+
sage: PSS = PythonStringSource('<runtime>', s, DocTestDefaults(), 'runtime')
|
427
|
+
sage: dt, extras = PSS.create_doctests({})
|
428
|
+
sage: extras['line_number']
|
429
|
+
True
|
430
|
+
"""
|
431
|
+
def __init__(self, basename, source, options, printpath, lineno_shift=0):
|
432
|
+
r"""
|
433
|
+
Initialization.
|
434
|
+
|
435
|
+
TESTS::
|
436
|
+
|
437
|
+
sage: from sage.doctest.control import DocTestDefaults
|
438
|
+
sage: from sage.doctest.sources import StringDocTestSource, PythonSource
|
439
|
+
sage: from sage.structure.dynamic_class import dynamic_class
|
440
|
+
sage: s = "'''\n sage: 2 + 2\n 4\n'''"
|
441
|
+
sage: PythonStringSource = dynamic_class('PythonStringSource',(StringDocTestSource, PythonSource))
|
442
|
+
sage: PSS = PythonStringSource('<runtime>', s, DocTestDefaults(), 'runtime')
|
443
|
+
sage: TestSuite(PSS).run()
|
444
|
+
"""
|
445
|
+
self.qualified_name = NestedName(basename)
|
446
|
+
self.printpath = printpath
|
447
|
+
self.source = source
|
448
|
+
self.lineno_shift = lineno_shift
|
449
|
+
DocTestSource.__init__(self, options)
|
450
|
+
|
451
|
+
def __iter__(self):
|
452
|
+
r"""
|
453
|
+
Iterating over this source yields pairs ``(lineno, line)``.
|
454
|
+
|
455
|
+
EXAMPLES::
|
456
|
+
|
457
|
+
sage: from sage.doctest.control import DocTestDefaults
|
458
|
+
sage: from sage.doctest.sources import StringDocTestSource, PythonSource
|
459
|
+
sage: from sage.structure.dynamic_class import dynamic_class
|
460
|
+
sage: s = "'''\n sage: 2 + 2\n 4\n'''"
|
461
|
+
sage: PythonStringSource = dynamic_class('PythonStringSource',(StringDocTestSource, PythonSource))
|
462
|
+
sage: PSS = PythonStringSource('<runtime>', s, DocTestDefaults(), 'runtime')
|
463
|
+
sage: for n, line in PSS:
|
464
|
+
....: print("{} {}".format(n, line))
|
465
|
+
0 '''
|
466
|
+
1 sage: 2 + 2
|
467
|
+
2 4
|
468
|
+
3 '''
|
469
|
+
"""
|
470
|
+
for lineno, line in enumerate(self.source.split('\n')):
|
471
|
+
yield lineno + self.lineno_shift, line + '\n'
|
472
|
+
|
473
|
+
def create_doctests(self, namespace):
|
474
|
+
r"""
|
475
|
+
Create doctests from this string.
|
476
|
+
|
477
|
+
INPUT:
|
478
|
+
|
479
|
+
- ``namespace`` -- dictionary or :class:`sage.doctest.util.RecordingDict`
|
480
|
+
|
481
|
+
OUTPUT:
|
482
|
+
|
483
|
+
- ``doctests`` -- list of doctests defined by this string
|
484
|
+
|
485
|
+
- ``tab_locations`` -- either ``False`` or a list of linenumbers
|
486
|
+
on which tabs appear
|
487
|
+
|
488
|
+
EXAMPLES::
|
489
|
+
|
490
|
+
sage: from sage.doctest.control import DocTestDefaults
|
491
|
+
sage: from sage.doctest.sources import StringDocTestSource, PythonSource
|
492
|
+
sage: from sage.structure.dynamic_class import dynamic_class
|
493
|
+
sage: s = "'''\n sage: 2 + 2\n 4\n'''"
|
494
|
+
sage: PythonStringSource = dynamic_class('PythonStringSource',(StringDocTestSource, PythonSource))
|
495
|
+
sage: PSS = PythonStringSource('<runtime>', s, DocTestDefaults(), 'runtime')
|
496
|
+
sage: dt, tabs = PSS.create_doctests({})
|
497
|
+
sage: for t in dt:
|
498
|
+
....: print("{} {}".format(t.name, t.examples[0].sage_source))
|
499
|
+
<runtime> 2 + 2
|
500
|
+
"""
|
501
|
+
return self._create_doctests(namespace)
|
502
|
+
|
503
|
+
|
504
|
+
class FileDocTestSource(DocTestSource):
|
505
|
+
"""
|
506
|
+
This class creates doctests from a file.
|
507
|
+
|
508
|
+
INPUT:
|
509
|
+
|
510
|
+
- ``path`` -- string; the filename
|
511
|
+
|
512
|
+
- ``options`` -- a :class:`sage.doctest.control.DocTestDefaults`
|
513
|
+
instance or equivalent
|
514
|
+
|
515
|
+
EXAMPLES::
|
516
|
+
|
517
|
+
sage: from sage.doctest.control import DocTestDefaults
|
518
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
519
|
+
sage: filename = sage.doctest.sources.__file__
|
520
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
521
|
+
sage: FDS.basename
|
522
|
+
'sage.doctest.sources'
|
523
|
+
|
524
|
+
TESTS::
|
525
|
+
|
526
|
+
sage: TestSuite(FDS).run()
|
527
|
+
|
528
|
+
::
|
529
|
+
|
530
|
+
sage: from sage.doctest.control import DocTestDefaults
|
531
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
532
|
+
sage: filename = tmp_filename(ext='.txtt')
|
533
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
534
|
+
Traceback (most recent call last):
|
535
|
+
...
|
536
|
+
ValueError: unknown extension for the file to test (=...txtt),
|
537
|
+
valid extensions are: .py, .pyx, .pxd, .pxi, .sage, .spyx, .tex, .rst, .rst.txt
|
538
|
+
"""
|
539
|
+
def __init__(self, path, options):
|
540
|
+
"""
|
541
|
+
Initialization.
|
542
|
+
|
543
|
+
EXAMPLES::
|
544
|
+
|
545
|
+
sage: from sage.doctest.control import DocTestDefaults
|
546
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
547
|
+
sage: filename = sage.doctest.sources.__file__
|
548
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults(randorder=0))
|
549
|
+
sage: FDS.options.randorder
|
550
|
+
0
|
551
|
+
"""
|
552
|
+
self.path = path
|
553
|
+
DocTestSource.__init__(self, options)
|
554
|
+
if path.endswith('.rst.txt'):
|
555
|
+
ext = '.rst.txt'
|
556
|
+
else:
|
557
|
+
base, ext = os.path.splitext(path)
|
558
|
+
valid_code_ext = ('.py', '.pyx', '.pxd', '.pxi', '.sage', '.spyx')
|
559
|
+
if ext in valid_code_ext:
|
560
|
+
self.__class__ = dynamic_class('PythonFileSource', (FileDocTestSource, PythonSource))
|
561
|
+
self.encoding = "utf-8"
|
562
|
+
elif ext == '.tex':
|
563
|
+
self.__class__ = dynamic_class('TexFileSource', (FileDocTestSource, TexSource))
|
564
|
+
self.encoding = "utf-8"
|
565
|
+
elif ext == '.rst' or ext == '.rst.txt':
|
566
|
+
self.__class__ = dynamic_class('RestFileSource', (FileDocTestSource, RestSource))
|
567
|
+
self.encoding = "utf-8"
|
568
|
+
else:
|
569
|
+
valid_ext = ", ".join(valid_code_ext + ('.tex', '.rst', '.rst.txt'))
|
570
|
+
raise ValueError("unknown extension for the file to test (={}),"
|
571
|
+
" valid extensions are: {}".format(path, valid_ext))
|
572
|
+
|
573
|
+
def __iter__(self):
|
574
|
+
r"""
|
575
|
+
Iterating over this source yields pairs ``(lineno, line)``.
|
576
|
+
|
577
|
+
EXAMPLES::
|
578
|
+
|
579
|
+
sage: from sage.doctest.control import DocTestDefaults
|
580
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
581
|
+
sage: filename = tmp_filename(ext='.py')
|
582
|
+
sage: s = "'''\n sage: 2 + 2\n 4\n'''"
|
583
|
+
sage: with open(filename, 'w') as f:
|
584
|
+
....: _ = f.write(s)
|
585
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
586
|
+
sage: for n, line in FDS:
|
587
|
+
....: print("{} {}".format(n, line))
|
588
|
+
0 '''
|
589
|
+
1 sage: 2 + 2
|
590
|
+
2 4
|
591
|
+
3 '''
|
592
|
+
|
593
|
+
The encoding is "utf-8" by default::
|
594
|
+
|
595
|
+
sage: FDS.encoding
|
596
|
+
'utf-8'
|
597
|
+
|
598
|
+
We create a file with a Latin-1 encoding without declaring it::
|
599
|
+
|
600
|
+
sage: s = b"'''\nRegardons le polyn\xF4me...\n'''\n"
|
601
|
+
sage: with open(filename, 'wb') as f:
|
602
|
+
....: _ = f.write(s)
|
603
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
604
|
+
sage: L = list(FDS)
|
605
|
+
Traceback (most recent call last):
|
606
|
+
...
|
607
|
+
UnicodeDecodeError: 'utf...8' codec can...t decode byte 0xf4 in position 18: invalid continuation byte
|
608
|
+
|
609
|
+
This works if we add a PEP 0263 encoding declaration::
|
610
|
+
|
611
|
+
sage: s = b"#!/usr/bin/env python\n# -*- coding: latin-1 -*-\n" + s
|
612
|
+
sage: with open(filename, 'wb') as f:
|
613
|
+
....: _ = f.write(s)
|
614
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
615
|
+
sage: L = list(FDS)
|
616
|
+
sage: FDS.encoding
|
617
|
+
'latin-1'
|
618
|
+
"""
|
619
|
+
with open(self.path, 'rb') as source:
|
620
|
+
for lineno, line in enumerate(source):
|
621
|
+
if lineno < 2:
|
622
|
+
match = pep_0263.search(line)
|
623
|
+
if match:
|
624
|
+
self.encoding = bytes_to_str(match.group(1), 'ascii')
|
625
|
+
yield lineno, line.decode(self.encoding)
|
626
|
+
|
627
|
+
@lazy_attribute
|
628
|
+
def printpath(self):
|
629
|
+
"""
|
630
|
+
Whether the path is printed absolutely or relatively depends on an option.
|
631
|
+
|
632
|
+
EXAMPLES::
|
633
|
+
|
634
|
+
sage: from sage.doctest.control import DocTestDefaults
|
635
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
636
|
+
sage: import os
|
637
|
+
sage: filename = os.path.realpath(sage.doctest.sources.__file__)
|
638
|
+
sage: root = os.path.join(os.path.dirname(filename), '..')
|
639
|
+
sage: cwd = os.getcwd()
|
640
|
+
sage: os.chdir(root)
|
641
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults(randorder=0,
|
642
|
+
....: abspath=False))
|
643
|
+
sage: FDS.printpath
|
644
|
+
'doctest/sources.py'
|
645
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults(randorder=0,
|
646
|
+
....: abspath=True))
|
647
|
+
sage: FDS.printpath
|
648
|
+
'.../sage/doctest/sources.py'
|
649
|
+
sage: os.chdir(cwd)
|
650
|
+
"""
|
651
|
+
if self.options.abspath:
|
652
|
+
return os.path.abspath(self.path)
|
653
|
+
else:
|
654
|
+
relpath = os.path.relpath(self.path)
|
655
|
+
if relpath.startswith(".." + os.path.sep):
|
656
|
+
return self.path
|
657
|
+
else:
|
658
|
+
return relpath
|
659
|
+
|
660
|
+
@lazy_attribute
|
661
|
+
def basename(self):
|
662
|
+
"""
|
663
|
+
The basename of this file source, e.g. ``sage.doctest.sources``.
|
664
|
+
|
665
|
+
EXAMPLES::
|
666
|
+
|
667
|
+
sage: from sage.doctest.control import DocTestDefaults
|
668
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
669
|
+
sage: filename = os.path.join(SAGE_SRC,'sage','rings','integer.pyx')
|
670
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
671
|
+
sage: FDS.basename
|
672
|
+
'sage.rings.integer'
|
673
|
+
"""
|
674
|
+
return get_basename(self.path)
|
675
|
+
|
676
|
+
@lazy_attribute
|
677
|
+
def in_lib(self):
|
678
|
+
"""
|
679
|
+
Whether this file is to be treated as a module in a Python package.
|
680
|
+
|
681
|
+
Such files aren't loaded before running tests.
|
682
|
+
|
683
|
+
This uses :func:`~sage.misc.package_dir.is_package_or_sage_namespace_package_dir`
|
684
|
+
but can be overridden via :class:`~sage.doctest.control.DocTestDefaults`.
|
685
|
+
|
686
|
+
EXAMPLES::
|
687
|
+
|
688
|
+
sage: from sage.doctest.control import DocTestDefaults
|
689
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
690
|
+
sage: import os
|
691
|
+
sage: filename = os.path.join(SAGE_SRC, 'sage', 'rings', 'integer.pyx')
|
692
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
693
|
+
sage: FDS.in_lib
|
694
|
+
True
|
695
|
+
sage: filename = os.path.join(SAGE_SRC, 'sage', 'doctest', 'tests', 'abort.rst')
|
696
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
697
|
+
sage: FDS.in_lib
|
698
|
+
False
|
699
|
+
|
700
|
+
You can override the default::
|
701
|
+
|
702
|
+
sage: FDS = FileDocTestSource("hello_world.py", DocTestDefaults())
|
703
|
+
sage: FDS.in_lib
|
704
|
+
False
|
705
|
+
sage: FDS = FileDocTestSource("hello_world.py", DocTestDefaults(force_lib=True))
|
706
|
+
sage: FDS.in_lib
|
707
|
+
True
|
708
|
+
"""
|
709
|
+
return (self.options.force_lib
|
710
|
+
or is_package_or_sage_namespace_package_dir(os.path.dirname(self.path)))
|
711
|
+
|
712
|
+
@lazy_attribute
|
713
|
+
def file_optional_tags(self):
|
714
|
+
"""
|
715
|
+
Return the set of tags that should apply to all doctests in this source.
|
716
|
+
|
717
|
+
EXAMPLES::
|
718
|
+
|
719
|
+
sage: from sage.doctest.control import DocTestDefaults
|
720
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
721
|
+
sage: filename = sage.repl.user_globals.__file__
|
722
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
723
|
+
sage: FDS.file_optional_tags
|
724
|
+
{'sage.modules': None}
|
725
|
+
"""
|
726
|
+
from .parsing import parse_file_optional_tags
|
727
|
+
return parse_file_optional_tags(self)
|
728
|
+
|
729
|
+
def create_doctests(self, namespace):
|
730
|
+
r"""
|
731
|
+
Return a list of doctests for this file.
|
732
|
+
|
733
|
+
INPUT:
|
734
|
+
|
735
|
+
- ``namespace`` -- dictionary or :class:`sage.doctest.util.RecordingDict`
|
736
|
+
|
737
|
+
OUTPUT:
|
738
|
+
|
739
|
+
- ``doctests`` -- list of doctests defined in this file
|
740
|
+
|
741
|
+
- ``extras`` -- dictionary
|
742
|
+
|
743
|
+
EXAMPLES::
|
744
|
+
|
745
|
+
sage: from sage.doctest.control import DocTestDefaults
|
746
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
747
|
+
sage: filename = sage.doctest.sources.__file__
|
748
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
749
|
+
sage: doctests, extras = FDS.create_doctests(globals())
|
750
|
+
sage: len(doctests)
|
751
|
+
43
|
752
|
+
sage: extras['tab']
|
753
|
+
False
|
754
|
+
|
755
|
+
We give a self referential example::
|
756
|
+
|
757
|
+
sage: doctests[20].name
|
758
|
+
'sage.doctest.sources.FileDocTestSource.create_doctests'
|
759
|
+
sage: doctests[20].examples[8].source
|
760
|
+
'doctests[Integer(20)].examples[Integer(8)].source\n'
|
761
|
+
|
762
|
+
TESTS:
|
763
|
+
|
764
|
+
We check that we correctly process results that depend on 32
|
765
|
+
vs 64 bit architecture::
|
766
|
+
|
767
|
+
sage: import sys
|
768
|
+
sage: bitness = '64' if sys.maxsize > (1 << 32) else '32'
|
769
|
+
sage: gp.get_precision() == 38 # needs sage.libs.pari
|
770
|
+
False # 32-bit
|
771
|
+
True # 64-bit
|
772
|
+
sage: ex = doctests[20].examples[11]
|
773
|
+
sage: ((bitness == '64' and ex.want == 'True \n') # needs sage.libs.pari
|
774
|
+
....: or (bitness == '32' and ex.want == 'False \n'))
|
775
|
+
True
|
776
|
+
|
777
|
+
We check that lines starting with a # aren't doctested::
|
778
|
+
|
779
|
+
#sage: raise RuntimeError
|
780
|
+
"""
|
781
|
+
if not os.path.exists(self.path):
|
782
|
+
import errno
|
783
|
+
raise OSError(errno.ENOENT, "File does not exist", self.path)
|
784
|
+
base, filename = os.path.split(self.path)
|
785
|
+
_, ext = os.path.splitext(filename)
|
786
|
+
if not self.in_lib and ext in ('.py', '.pyx', '.sage', '.spyx'):
|
787
|
+
cwd = os.getcwd()
|
788
|
+
if base:
|
789
|
+
os.chdir(base)
|
790
|
+
try:
|
791
|
+
load(filename, namespace) # errors raised here will be caught in DocTestTask
|
792
|
+
finally:
|
793
|
+
os.chdir(cwd)
|
794
|
+
self.qualified_name = NestedName(self.basename)
|
795
|
+
return self._create_doctests(namespace)
|
796
|
+
|
797
|
+
def _test_enough_doctests(self, check_extras=True, verbose=True):
|
798
|
+
r"""
|
799
|
+
This function checks to see that the doctests are not getting
|
800
|
+
unexpectedly skipped.
|
801
|
+
|
802
|
+
It uses a different (and simpler) code path than the doctest
|
803
|
+
creation functions. In particular, it does not understand
|
804
|
+
file-level and block-level # optional / needs tags.
|
805
|
+
|
806
|
+
INPUT:
|
807
|
+
|
808
|
+
- ``check_extras`` -- boolean (default: ``True``); whether to check if
|
809
|
+
doctests are created that do not correspond to either a ``sage:``
|
810
|
+
or a ``>>>`` prompt
|
811
|
+
|
812
|
+
- ``verbose`` -- boolean (default: ``True``); whether to print
|
813
|
+
offending line numbers when there are missing or extra tests
|
814
|
+
|
815
|
+
TESTS::
|
816
|
+
|
817
|
+
sage: # not tested (because the output will change when source files are changed)
|
818
|
+
sage: from sage.doctest.control import DocTestDefaults
|
819
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
820
|
+
sage: cwd = os.getcwd()
|
821
|
+
sage: os.chdir(SAGE_SRC)
|
822
|
+
sage: import itertools
|
823
|
+
sage: for path, dirs, files in itertools.chain(os.walk('sage'), os.walk('doc')):
|
824
|
+
....: path = os.path.relpath(path)
|
825
|
+
....: dirs.sort(); files.sort()
|
826
|
+
....: for F in files:
|
827
|
+
....: _, ext = os.path.splitext(F)
|
828
|
+
....: if ext in ('.py', '.pyx', '.pxd', '.pxi', '.sage', '.spyx', '.rst'):
|
829
|
+
....: filename = os.path.join(path, F)
|
830
|
+
....: FDS = FileDocTestSource(filename, DocTestDefaults(long=True, optional=True, force_lib=True))
|
831
|
+
....: FDS._test_enough_doctests(verbose=False)
|
832
|
+
There are 4 unexpected tests being run in sage/doctest/parsing.py
|
833
|
+
There are 1 unexpected tests being run in sage/doctest/reporting.py
|
834
|
+
sage: os.chdir(cwd)
|
835
|
+
"""
|
836
|
+
expected = []
|
837
|
+
rest = isinstance(self, RestSource)
|
838
|
+
if rest:
|
839
|
+
skipping = False
|
840
|
+
in_block = False
|
841
|
+
last_line = ''
|
842
|
+
for lineno, line in self:
|
843
|
+
if not line.strip():
|
844
|
+
continue
|
845
|
+
if rest:
|
846
|
+
if line.strip().startswith(".. nodoctest"):
|
847
|
+
return
|
848
|
+
# We need to track blocks in order to figure out whether we're skipping.
|
849
|
+
if in_block:
|
850
|
+
indent = whitespace.match(line).end()
|
851
|
+
if indent <= starting_indent:
|
852
|
+
in_block = False
|
853
|
+
skipping = False
|
854
|
+
if not in_block:
|
855
|
+
m1 = double_colon.match(line)
|
856
|
+
m2 = code_block.match(line.lower())
|
857
|
+
starting = (m1 and not line.strip().startswith(".. ")) or m2
|
858
|
+
if starting:
|
859
|
+
if ".. skip" in last_line:
|
860
|
+
skipping = True
|
861
|
+
in_block = True
|
862
|
+
starting_indent = whitespace.match(line).end()
|
863
|
+
last_line = line
|
864
|
+
if (not rest or in_block) and sagestart.match(line) and not ((rest and skipping) or untested.search(line.lower())):
|
865
|
+
expected.append(lineno+1)
|
866
|
+
actual = []
|
867
|
+
tests, _ = self.create_doctests({})
|
868
|
+
for dt in tests:
|
869
|
+
if dt.examples:
|
870
|
+
for ex in dt.examples[:-1]: # the last entry is a sig_on_count()
|
871
|
+
actual.append(dt.lineno + ex.lineno + 1)
|
872
|
+
shortfall = sorted(set(expected).difference(set(actual)))
|
873
|
+
extras = sorted(set(actual).difference(set(expected)))
|
874
|
+
if len(actual) == len(expected):
|
875
|
+
if not shortfall:
|
876
|
+
return
|
877
|
+
dif = extras[0] - shortfall[0]
|
878
|
+
for e, s in zip(extras[1:],shortfall[1:]):
|
879
|
+
if dif != e - s:
|
880
|
+
break
|
881
|
+
else:
|
882
|
+
print("There are %s tests in %s that are shifted by %s" % (len(shortfall), self.path, dif))
|
883
|
+
if verbose:
|
884
|
+
print(" The correct line numbers are %s" % (", ".join(str(n) for n in shortfall)))
|
885
|
+
return
|
886
|
+
elif len(actual) < len(expected):
|
887
|
+
print("There are %s tests in %s that are not being run" % (len(expected) - len(actual), self.path))
|
888
|
+
elif check_extras:
|
889
|
+
print("There are %s unexpected tests being run in %s" % (len(actual) - len(expected), self.path))
|
890
|
+
if verbose:
|
891
|
+
if shortfall:
|
892
|
+
print(" Tests on lines %s are not run" % (", ".join(str(n) for n in shortfall)))
|
893
|
+
if check_extras and extras:
|
894
|
+
print(" Tests on lines %s seem extraneous" % (", ".join(str(n) for n in extras)))
|
895
|
+
|
896
|
+
|
897
|
+
class SourceLanguage:
|
898
|
+
"""
|
899
|
+
An abstract class for functions that depend on the programming language of a doctest source.
|
900
|
+
|
901
|
+
Currently supported languages include Python, ReST and LaTeX.
|
902
|
+
"""
|
903
|
+
def parse_docstring(self, docstring, namespace, start):
|
904
|
+
"""
|
905
|
+
Return a list of doctest defined in this docstring.
|
906
|
+
|
907
|
+
This function is called by :meth:`DocTestSource._process_doc`.
|
908
|
+
The default implementation, defined here, is to use the
|
909
|
+
:class:`sage.doctest.parsing.SageDocTestParser` attached to
|
910
|
+
this source to get doctests from the docstring.
|
911
|
+
|
912
|
+
INPUT:
|
913
|
+
|
914
|
+
- ``docstring`` -- string containing documentation and tests
|
915
|
+
|
916
|
+
- ``namespace`` -- dictionary or :class:`sage.doctest.util.RecordingDict`
|
917
|
+
|
918
|
+
- ``start`` -- integer; one less than the starting line number
|
919
|
+
|
920
|
+
EXAMPLES::
|
921
|
+
|
922
|
+
sage: from sage.doctest.control import DocTestDefaults
|
923
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
924
|
+
sage: from sage.doctest.parsing import SageDocTestParser
|
925
|
+
sage: from sage.doctest.util import NestedName
|
926
|
+
sage: filename = sage.doctest.util.__file__
|
927
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
928
|
+
sage: doctests, _ = FDS.create_doctests({})
|
929
|
+
sage: for dt in doctests:
|
930
|
+
....: FDS.qualified_name = dt.name
|
931
|
+
....: dt.examples = dt.examples[:-1] # strip off the sig_on() test
|
932
|
+
....: assert(FDS.parse_docstring(dt.docstring,{},dt.lineno-1)[0] == dt)
|
933
|
+
"""
|
934
|
+
return [self.parser.get_doctest(docstring, namespace, str(self.qualified_name),
|
935
|
+
self.printpath, start + 1)]
|
936
|
+
|
937
|
+
|
938
|
+
class PythonSource(SourceLanguage):
|
939
|
+
"""
|
940
|
+
This class defines the functions needed for the extraction of doctests from python sources.
|
941
|
+
|
942
|
+
EXAMPLES::
|
943
|
+
|
944
|
+
sage: from sage.doctest.control import DocTestDefaults
|
945
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
946
|
+
sage: filename = sage.doctest.sources.__file__
|
947
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
948
|
+
sage: type(FDS)
|
949
|
+
<class 'sage.doctest.sources.PythonFileSource'>
|
950
|
+
"""
|
951
|
+
# The same line can't both start and end a docstring
|
952
|
+
start_finish_can_overlap = False
|
953
|
+
|
954
|
+
def _init(self):
|
955
|
+
"""
|
956
|
+
This function is called before creating doctests from a Python source.
|
957
|
+
|
958
|
+
EXAMPLES::
|
959
|
+
|
960
|
+
sage: from sage.doctest.control import DocTestDefaults
|
961
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
962
|
+
sage: filename = sage.doctest.sources.__file__
|
963
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
964
|
+
sage: FDS._init()
|
965
|
+
sage: FDS.last_indent
|
966
|
+
-1
|
967
|
+
"""
|
968
|
+
self.last_indent = -1
|
969
|
+
self.last_line = None
|
970
|
+
self.quotetype = None
|
971
|
+
self.paren_count = 0
|
972
|
+
self.bracket_count = 0
|
973
|
+
self.curly_count = 0
|
974
|
+
self.code_wrapping = False
|
975
|
+
|
976
|
+
def _update_quotetype(self, line):
|
977
|
+
r"""
|
978
|
+
Update the track of what kind of quoted string we are in.
|
979
|
+
|
980
|
+
We need to track whether we're inside a triple quoted
|
981
|
+
string, since a triple quoted string that starts a line
|
982
|
+
could be the end of a string and thus not the beginning of a
|
983
|
+
doctest (see sage.misc.sageinspect for an example).
|
984
|
+
|
985
|
+
To do this tracking we need to track whether we're inside a
|
986
|
+
string at all, since ''' inside a string doesn't start a
|
987
|
+
triple quote (see the top of this file for an example).
|
988
|
+
|
989
|
+
We also need to track parentheses and brackets, since we only
|
990
|
+
want to update our record of last line and indentation level
|
991
|
+
when the line is actually over.
|
992
|
+
|
993
|
+
EXAMPLES::
|
994
|
+
|
995
|
+
sage: from sage.doctest.control import DocTestDefaults
|
996
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
997
|
+
sage: filename = sage.doctest.sources.__file__
|
998
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
999
|
+
sage: FDS._init()
|
1000
|
+
sage: FDS._update_quotetype('\"\"\"'); print(" ".join(list(FDS.quotetype)))
|
1001
|
+
" " "
|
1002
|
+
sage: FDS._update_quotetype("'''"); print(" ".join(list(FDS.quotetype)))
|
1003
|
+
" " "
|
1004
|
+
sage: FDS._update_quotetype('\"\"\"'); print(FDS.quotetype)
|
1005
|
+
None
|
1006
|
+
sage: FDS._update_quotetype("triple_quotes = re.compile(\"\\s*[rRuU]*((''')|(\\\"\\\"\\\"))\")")
|
1007
|
+
sage: print(FDS.quotetype)
|
1008
|
+
None
|
1009
|
+
sage: FDS._update_quotetype("''' Single line triple quoted string \\''''")
|
1010
|
+
sage: print(FDS.quotetype)
|
1011
|
+
None
|
1012
|
+
sage: FDS._update_quotetype("' Lots of \\\\\\\\'")
|
1013
|
+
sage: print(FDS.quotetype)
|
1014
|
+
None
|
1015
|
+
"""
|
1016
|
+
def _update_parens(start, end=None):
|
1017
|
+
self.paren_count += line.count("(",start,end) - line.count(")",start,end)
|
1018
|
+
self.bracket_count += line.count("[",start,end) - line.count("]",start,end)
|
1019
|
+
self.curly_count += line.count("{",start,end) - line.count("}",start,end)
|
1020
|
+
pos = 0
|
1021
|
+
while pos < len(line):
|
1022
|
+
if self.quotetype is None:
|
1023
|
+
next_single = line.find("'",pos)
|
1024
|
+
next_double = line.find('"',pos)
|
1025
|
+
if next_single == -1 and next_double == -1:
|
1026
|
+
next_comment = line.find("#",pos)
|
1027
|
+
if next_comment == -1:
|
1028
|
+
_update_parens(pos)
|
1029
|
+
else:
|
1030
|
+
_update_parens(pos,next_comment)
|
1031
|
+
break
|
1032
|
+
elif next_single == -1:
|
1033
|
+
m = next_double
|
1034
|
+
elif next_double == -1:
|
1035
|
+
m = next_single
|
1036
|
+
else:
|
1037
|
+
m = min(next_single, next_double)
|
1038
|
+
next_comment = line.find('#',pos,m)
|
1039
|
+
if next_comment != -1:
|
1040
|
+
_update_parens(pos,next_comment)
|
1041
|
+
break
|
1042
|
+
_update_parens(pos,m)
|
1043
|
+
if m+2 < len(line) and line[m] == line[m+1] == line[m+2]:
|
1044
|
+
self.quotetype = line[m:m+3]
|
1045
|
+
pos = m+3
|
1046
|
+
else:
|
1047
|
+
self.quotetype = line[m]
|
1048
|
+
pos = m+1
|
1049
|
+
else:
|
1050
|
+
next = line.find(self.quotetype,pos)
|
1051
|
+
if next == -1:
|
1052
|
+
break
|
1053
|
+
elif next == 0 or line[next-1] != '\\':
|
1054
|
+
pos = next + len(self.quotetype)
|
1055
|
+
self.quotetype = None
|
1056
|
+
else:
|
1057
|
+
# We need to worry about the possibility that
|
1058
|
+
# there are an even number of backslashes before
|
1059
|
+
# the quote, in which case it is not escaped
|
1060
|
+
count = 1
|
1061
|
+
slashpos = next - 2
|
1062
|
+
while slashpos >= pos and line[slashpos] == '\\':
|
1063
|
+
count += 1
|
1064
|
+
slashpos -= 1
|
1065
|
+
if count % 2 == 0:
|
1066
|
+
pos = next + len(self.quotetype)
|
1067
|
+
self.quotetype = None
|
1068
|
+
else:
|
1069
|
+
# The possible ending quote was escaped.
|
1070
|
+
pos = next + 1
|
1071
|
+
|
1072
|
+
def starting_docstring(self, line):
|
1073
|
+
"""
|
1074
|
+
Determines whether the input line starts a docstring.
|
1075
|
+
|
1076
|
+
If the input line does start a docstring (a triple quote),
|
1077
|
+
then this function updates ``self.qualified_name``.
|
1078
|
+
|
1079
|
+
INPUT:
|
1080
|
+
|
1081
|
+
- ``line`` -- string; one line of an input file
|
1082
|
+
|
1083
|
+
OUTPUT: either ``None`` or a Match object
|
1084
|
+
|
1085
|
+
EXAMPLES::
|
1086
|
+
|
1087
|
+
sage: from sage.doctest.control import DocTestDefaults
|
1088
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
1089
|
+
sage: from sage.doctest.util import NestedName
|
1090
|
+
sage: filename = sage.doctest.sources.__file__
|
1091
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
1092
|
+
sage: FDS._init()
|
1093
|
+
sage: FDS.starting_docstring("r'''")
|
1094
|
+
<...Match object...>
|
1095
|
+
sage: FDS.ending_docstring("'''")
|
1096
|
+
<...Match object...>
|
1097
|
+
sage: FDS.qualified_name = NestedName(FDS.basename)
|
1098
|
+
sage: FDS.starting_docstring("class MyClass():")
|
1099
|
+
sage: FDS.starting_docstring(" def hello_world(self):")
|
1100
|
+
sage: FDS.starting_docstring(" '''")
|
1101
|
+
<...Match object...>
|
1102
|
+
sage: FDS.qualified_name
|
1103
|
+
sage.doctest.sources.MyClass.hello_world
|
1104
|
+
sage: FDS.ending_docstring(" '''")
|
1105
|
+
<...Match object...>
|
1106
|
+
sage: FDS.starting_docstring("class NewClass():")
|
1107
|
+
sage: FDS.starting_docstring(" '''")
|
1108
|
+
<...Match object...>
|
1109
|
+
sage: FDS.ending_docstring(" '''")
|
1110
|
+
<...Match object...>
|
1111
|
+
sage: FDS.qualified_name
|
1112
|
+
sage.doctest.sources.NewClass
|
1113
|
+
sage: FDS.starting_docstring("print(")
|
1114
|
+
sage: FDS.starting_docstring(" '''Not a docstring")
|
1115
|
+
sage: FDS.starting_docstring(" ''')")
|
1116
|
+
sage: FDS.starting_docstring("def foo():")
|
1117
|
+
sage: FDS.starting_docstring(" '''This is a docstring'''")
|
1118
|
+
<...Match object...>
|
1119
|
+
"""
|
1120
|
+
indent = whitespace.match(line).end()
|
1121
|
+
quotematch = None
|
1122
|
+
if self.quotetype is None and not self.code_wrapping:
|
1123
|
+
# We're not inside a triple quote and not inside code like
|
1124
|
+
# print(
|
1125
|
+
# """Not a docstring
|
1126
|
+
# """)
|
1127
|
+
|
1128
|
+
if line[indent] != '#' and (indent == 0 or indent > self.last_indent):
|
1129
|
+
quotematch = triple_quotes.match(line)
|
1130
|
+
# It would be nice to only run the name_regex when
|
1131
|
+
# quotematch wasn't None, but then we mishandle classes
|
1132
|
+
# that don't have a docstring.
|
1133
|
+
if not self.code_wrapping and self.last_indent >= 0 and indent > self.last_indent:
|
1134
|
+
name = name_regex.match(self.last_line)
|
1135
|
+
if name:
|
1136
|
+
name = name.groups()[0]
|
1137
|
+
self.qualified_name[indent] = name
|
1138
|
+
elif quotematch:
|
1139
|
+
self.qualified_name[indent] = '?'
|
1140
|
+
self._update_quotetype(line)
|
1141
|
+
if line[indent] != '#' and not self.code_wrapping:
|
1142
|
+
self.last_line, self.last_indent = line, indent
|
1143
|
+
self.code_wrapping = not (self.paren_count == self.bracket_count == self.curly_count == 0)
|
1144
|
+
return quotematch
|
1145
|
+
|
1146
|
+
def ending_docstring(self, line):
|
1147
|
+
r"""
|
1148
|
+
Determines whether the input line ends a docstring.
|
1149
|
+
|
1150
|
+
INPUT:
|
1151
|
+
|
1152
|
+
- ``line`` -- string, one line of an input file
|
1153
|
+
|
1154
|
+
OUTPUT: an object that, when evaluated in a boolean context, gives
|
1155
|
+
``True`` or ``False`` depending on whether the input line marks the
|
1156
|
+
end of a docstring
|
1157
|
+
|
1158
|
+
EXAMPLES::
|
1159
|
+
|
1160
|
+
sage: from sage.doctest.control import DocTestDefaults
|
1161
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
1162
|
+
sage: from sage.doctest.util import NestedName
|
1163
|
+
sage: filename = sage.doctest.sources.__file__
|
1164
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
1165
|
+
sage: FDS._init()
|
1166
|
+
sage: FDS.quotetype = "'''"
|
1167
|
+
sage: FDS.ending_docstring("'''")
|
1168
|
+
<...Match object...>
|
1169
|
+
sage: FDS.ending_docstring('\"\"\"')
|
1170
|
+
"""
|
1171
|
+
quotematch = triple_quotes.match(line)
|
1172
|
+
if quotematch is not None and quotematch.groups()[0] != self.quotetype:
|
1173
|
+
quotematch = None
|
1174
|
+
self._update_quotetype(line)
|
1175
|
+
return quotematch
|
1176
|
+
|
1177
|
+
def _neutralize_doctests(self, reindent):
|
1178
|
+
r"""
|
1179
|
+
Return a string containing the source of ``self``, but with
|
1180
|
+
doctests modified so they are not tested.
|
1181
|
+
|
1182
|
+
This function is used in creating doctests for ReST files,
|
1183
|
+
since docstrings of Python functions defined inside verbatim
|
1184
|
+
blocks screw up Python's doctest parsing.
|
1185
|
+
|
1186
|
+
INPUT:
|
1187
|
+
|
1188
|
+
- ``reindent`` -- integer; the number of spaces to indent
|
1189
|
+
the result
|
1190
|
+
|
1191
|
+
EXAMPLES::
|
1192
|
+
|
1193
|
+
sage: from sage.doctest.control import DocTestDefaults
|
1194
|
+
sage: from sage.doctest.sources import StringDocTestSource, PythonSource
|
1195
|
+
sage: from sage.structure.dynamic_class import dynamic_class
|
1196
|
+
sage: s = "'''\n sage: 2 + 2\n 4\n'''"
|
1197
|
+
sage: PythonStringSource = dynamic_class('PythonStringSource',
|
1198
|
+
....: (StringDocTestSource, PythonSource))
|
1199
|
+
sage: PSS = PythonStringSource('<runtime>', s, DocTestDefaults(), 'runtime')
|
1200
|
+
sage: print(PSS._neutralize_doctests(0))
|
1201
|
+
'''
|
1202
|
+
safe: 2 + 2
|
1203
|
+
4
|
1204
|
+
'''
|
1205
|
+
"""
|
1206
|
+
neutralized = []
|
1207
|
+
in_docstring = False
|
1208
|
+
self._init()
|
1209
|
+
for lineno, line in self:
|
1210
|
+
if not line.strip():
|
1211
|
+
neutralized.append(line)
|
1212
|
+
elif in_docstring:
|
1213
|
+
if self.ending_docstring(line):
|
1214
|
+
in_docstring = False
|
1215
|
+
neutralized.append(" "*reindent + find_prompt.sub(r"\1safe:\3",line))
|
1216
|
+
else:
|
1217
|
+
if self.starting_docstring(line):
|
1218
|
+
in_docstring = True
|
1219
|
+
neutralized.append(" "*reindent + line)
|
1220
|
+
return "".join(neutralized)
|
1221
|
+
|
1222
|
+
|
1223
|
+
class TexSource(SourceLanguage):
|
1224
|
+
"""
|
1225
|
+
This class defines the functions needed for the extraction of
|
1226
|
+
doctests from a LaTeX source.
|
1227
|
+
|
1228
|
+
EXAMPLES::
|
1229
|
+
|
1230
|
+
sage: from sage.doctest.control import DocTestDefaults
|
1231
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
1232
|
+
sage: filename = "sage_paper.tex"
|
1233
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
1234
|
+
sage: type(FDS)
|
1235
|
+
<class 'sage.doctest.sources.TexFileSource'>
|
1236
|
+
"""
|
1237
|
+
# The same line can't both start and end a docstring
|
1238
|
+
start_finish_can_overlap = False
|
1239
|
+
|
1240
|
+
def _init(self):
|
1241
|
+
"""
|
1242
|
+
This function is called before creating doctests from a Tex file.
|
1243
|
+
|
1244
|
+
EXAMPLES::
|
1245
|
+
|
1246
|
+
sage: from sage.doctest.control import DocTestDefaults
|
1247
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
1248
|
+
sage: filename = "sage_paper.tex"
|
1249
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
1250
|
+
sage: FDS._init()
|
1251
|
+
sage: FDS.skipping
|
1252
|
+
False
|
1253
|
+
"""
|
1254
|
+
self.skipping = False
|
1255
|
+
|
1256
|
+
def starting_docstring(self, line):
|
1257
|
+
r"""
|
1258
|
+
Determines whether the input line starts a docstring.
|
1259
|
+
|
1260
|
+
Docstring blocks in tex files are defined by verbatim or
|
1261
|
+
lstlisting environments, and can be linked together by adding
|
1262
|
+
%link immediately after the \end{verbatim} or \end{lstlisting}.
|
1263
|
+
|
1264
|
+
Within a verbatim (or lstlisting) block, you can tell Sage not to
|
1265
|
+
process the rest of the block by including a %skip line.
|
1266
|
+
|
1267
|
+
INPUT:
|
1268
|
+
|
1269
|
+
- ``line`` -- string, one line of an input file
|
1270
|
+
|
1271
|
+
OUTPUT: boolean; whether the input line marks the start of a docstring
|
1272
|
+
(verbatim block)
|
1273
|
+
|
1274
|
+
EXAMPLES::
|
1275
|
+
|
1276
|
+
sage: from sage.doctest.control import DocTestDefaults
|
1277
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
1278
|
+
sage: filename = "sage_paper.tex"
|
1279
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
1280
|
+
sage: FDS._init()
|
1281
|
+
|
1282
|
+
We start docstrings with \begin{verbatim} or \begin{lstlisting}::
|
1283
|
+
|
1284
|
+
sage: FDS.starting_docstring(r"\begin{verbatim}")
|
1285
|
+
True
|
1286
|
+
sage: FDS.starting_docstring(r"\begin{lstlisting}")
|
1287
|
+
True
|
1288
|
+
sage: FDS.skipping
|
1289
|
+
False
|
1290
|
+
sage: FDS.ending_docstring("sage: 2+2")
|
1291
|
+
False
|
1292
|
+
sage: FDS.ending_docstring("4")
|
1293
|
+
False
|
1294
|
+
|
1295
|
+
To start ignoring the rest of the verbatim block, use %skip::
|
1296
|
+
|
1297
|
+
sage: FDS.ending_docstring("%skip")
|
1298
|
+
True
|
1299
|
+
sage: FDS.skipping
|
1300
|
+
True
|
1301
|
+
sage: FDS.starting_docstring("sage: raise RuntimeError")
|
1302
|
+
False
|
1303
|
+
|
1304
|
+
You can even pretend to start another verbatim block while skipping::
|
1305
|
+
|
1306
|
+
sage: FDS.starting_docstring(r"\begin{verbatim}")
|
1307
|
+
False
|
1308
|
+
sage: FDS.skipping
|
1309
|
+
True
|
1310
|
+
|
1311
|
+
To stop skipping end the verbatim block::
|
1312
|
+
|
1313
|
+
sage: FDS.starting_docstring(r"\end{verbatim} %link")
|
1314
|
+
False
|
1315
|
+
sage: FDS.skipping
|
1316
|
+
False
|
1317
|
+
|
1318
|
+
Linking works even when the block was ended while skipping::
|
1319
|
+
|
1320
|
+
sage: FDS.linking
|
1321
|
+
True
|
1322
|
+
sage: FDS.starting_docstring(r"\begin{verbatim}")
|
1323
|
+
True
|
1324
|
+
"""
|
1325
|
+
if self.skipping:
|
1326
|
+
if self.ending_docstring(line, check_skip=False):
|
1327
|
+
self.skipping = False
|
1328
|
+
return False
|
1329
|
+
return bool(begin_verb.match(line) or begin_lstli.match(line))
|
1330
|
+
|
1331
|
+
def ending_docstring(self, line, check_skip=True):
|
1332
|
+
r"""
|
1333
|
+
Determines whether the input line ends a docstring.
|
1334
|
+
|
1335
|
+
Docstring blocks in tex files are defined by verbatim or
|
1336
|
+
lstlisting environments, and can be linked together by adding
|
1337
|
+
%link immediately after the \end{verbatim} or \end{lstlisting}.
|
1338
|
+
|
1339
|
+
Within a verbatim (or lstlisting) block, you can tell Sage not to
|
1340
|
+
process the rest of the block by including a %skip line.
|
1341
|
+
|
1342
|
+
INPUT:
|
1343
|
+
|
1344
|
+
- ``line`` -- string, one line of an input file
|
1345
|
+
|
1346
|
+
- ``check_skip`` -- boolean (default: ``True``); used internally in
|
1347
|
+
``starting_docstring``
|
1348
|
+
|
1349
|
+
OUTPUT: boolean; whether the input line marks the end of a docstring
|
1350
|
+
(verbatim block)
|
1351
|
+
|
1352
|
+
EXAMPLES::
|
1353
|
+
|
1354
|
+
sage: from sage.doctest.control import DocTestDefaults
|
1355
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
1356
|
+
sage: filename = "sage_paper.tex"
|
1357
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
1358
|
+
sage: FDS._init()
|
1359
|
+
sage: FDS.ending_docstring(r"\end{verbatim}")
|
1360
|
+
True
|
1361
|
+
sage: FDS.ending_docstring(r"\end{lstlisting}")
|
1362
|
+
True
|
1363
|
+
sage: FDS.linking
|
1364
|
+
False
|
1365
|
+
|
1366
|
+
Use %link to link with the next verbatim block::
|
1367
|
+
|
1368
|
+
sage: FDS.ending_docstring(r"\end{verbatim}%link")
|
1369
|
+
True
|
1370
|
+
sage: FDS.linking
|
1371
|
+
True
|
1372
|
+
|
1373
|
+
%skip also ends a docstring block::
|
1374
|
+
|
1375
|
+
sage: FDS.ending_docstring("%skip")
|
1376
|
+
True
|
1377
|
+
"""
|
1378
|
+
m = end_verb.match(line)
|
1379
|
+
if m:
|
1380
|
+
if m.groups()[0]:
|
1381
|
+
self.linking = True
|
1382
|
+
else:
|
1383
|
+
self.linking = False
|
1384
|
+
return True
|
1385
|
+
m = end_lstli.match(line)
|
1386
|
+
if m:
|
1387
|
+
if m.groups()[0]:
|
1388
|
+
self.linking = True
|
1389
|
+
else:
|
1390
|
+
self.linking = False
|
1391
|
+
return True
|
1392
|
+
if check_skip and skip.match(line):
|
1393
|
+
self.skipping = True
|
1394
|
+
return True
|
1395
|
+
return False
|
1396
|
+
|
1397
|
+
|
1398
|
+
class RestSource(SourceLanguage):
|
1399
|
+
"""
|
1400
|
+
This class defines the functions needed for the extraction of
|
1401
|
+
doctests from ReST sources.
|
1402
|
+
|
1403
|
+
EXAMPLES::
|
1404
|
+
|
1405
|
+
sage: from sage.doctest.control import DocTestDefaults
|
1406
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
1407
|
+
sage: filename = "sage_doc.rst"
|
1408
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
1409
|
+
sage: type(FDS)
|
1410
|
+
<class 'sage.doctest.sources.RestFileSource'>
|
1411
|
+
"""
|
1412
|
+
# The same line can both start and end a docstring
|
1413
|
+
start_finish_can_overlap = True
|
1414
|
+
|
1415
|
+
def _init(self):
|
1416
|
+
"""
|
1417
|
+
This function is called before creating doctests from a ReST file.
|
1418
|
+
|
1419
|
+
EXAMPLES::
|
1420
|
+
|
1421
|
+
sage: from sage.doctest.control import DocTestDefaults
|
1422
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
1423
|
+
sage: filename = "sage_doc.rst"
|
1424
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
1425
|
+
sage: FDS._init()
|
1426
|
+
sage: FDS.link_all
|
1427
|
+
False
|
1428
|
+
"""
|
1429
|
+
self.link_all = False
|
1430
|
+
self.last_line = ""
|
1431
|
+
self.last_indent = -1
|
1432
|
+
self.first_line = False
|
1433
|
+
self.skipping = False
|
1434
|
+
|
1435
|
+
def starting_docstring(self, line):
|
1436
|
+
"""
|
1437
|
+
A line ending with a double colon starts a verbatim block in a ReST file,
|
1438
|
+
as does a line containing ``.. CODE-BLOCK:: language``.
|
1439
|
+
|
1440
|
+
This function also determines whether the docstring block
|
1441
|
+
should be joined with the previous one, or should be skipped.
|
1442
|
+
|
1443
|
+
INPUT:
|
1444
|
+
|
1445
|
+
- ``line`` -- string; one line of an input file
|
1446
|
+
|
1447
|
+
OUTPUT: either ``None`` or a Match object
|
1448
|
+
|
1449
|
+
EXAMPLES::
|
1450
|
+
|
1451
|
+
sage: from sage.doctest.control import DocTestDefaults
|
1452
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
1453
|
+
sage: filename = "sage_doc.rst"
|
1454
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
1455
|
+
sage: FDS._init()
|
1456
|
+
sage: FDS.starting_docstring("Hello world::")
|
1457
|
+
True
|
1458
|
+
sage: FDS.ending_docstring(" sage: 2 + 2")
|
1459
|
+
False
|
1460
|
+
sage: FDS.ending_docstring(" 4")
|
1461
|
+
False
|
1462
|
+
sage: FDS.ending_docstring("We are now done")
|
1463
|
+
True
|
1464
|
+
sage: FDS.starting_docstring(".. link")
|
1465
|
+
sage: FDS.starting_docstring("::")
|
1466
|
+
True
|
1467
|
+
sage: FDS.linking
|
1468
|
+
True
|
1469
|
+
"""
|
1470
|
+
if link_all.match(line):
|
1471
|
+
self.link_all = True
|
1472
|
+
if self.skipping:
|
1473
|
+
end_block = self.ending_docstring(line)
|
1474
|
+
if end_block:
|
1475
|
+
self.skipping = False
|
1476
|
+
else:
|
1477
|
+
return False
|
1478
|
+
m1 = double_colon.match(line)
|
1479
|
+
m2 = code_block.match(line.lower())
|
1480
|
+
starting = (m1 and not line.strip().startswith(".. ")) or m2
|
1481
|
+
if starting:
|
1482
|
+
self.linking = self.link_all or '.. link' in self.last_line
|
1483
|
+
self.first_line = True
|
1484
|
+
m = m1 or m2
|
1485
|
+
indent = len(m.groups()[0])
|
1486
|
+
if '.. skip' in self.last_line:
|
1487
|
+
self.skipping = True
|
1488
|
+
starting = False
|
1489
|
+
else:
|
1490
|
+
indent = self.last_indent
|
1491
|
+
self.last_line, self.last_indent = line, indent
|
1492
|
+
return starting
|
1493
|
+
|
1494
|
+
def ending_docstring(self, line):
|
1495
|
+
"""
|
1496
|
+
When the indentation level drops below the initial level the
|
1497
|
+
block ends.
|
1498
|
+
|
1499
|
+
INPUT:
|
1500
|
+
|
1501
|
+
- ``line`` -- string; one line of an input file
|
1502
|
+
|
1503
|
+
OUTPUT: boolean; whether the verbatim block is ending
|
1504
|
+
|
1505
|
+
EXAMPLES::
|
1506
|
+
|
1507
|
+
sage: from sage.doctest.control import DocTestDefaults
|
1508
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
1509
|
+
sage: filename = "sage_doc.rst"
|
1510
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
1511
|
+
sage: FDS._init()
|
1512
|
+
sage: FDS.starting_docstring("Hello world::")
|
1513
|
+
True
|
1514
|
+
sage: FDS.ending_docstring(" sage: 2 + 2")
|
1515
|
+
False
|
1516
|
+
sage: FDS.ending_docstring(" 4")
|
1517
|
+
False
|
1518
|
+
sage: FDS.ending_docstring("We are now done")
|
1519
|
+
True
|
1520
|
+
"""
|
1521
|
+
if not line.strip():
|
1522
|
+
return False
|
1523
|
+
indent = whitespace.match(line).end()
|
1524
|
+
if self.first_line:
|
1525
|
+
self.first_line = False
|
1526
|
+
if indent <= self.last_indent:
|
1527
|
+
# We didn't indent at all
|
1528
|
+
return True
|
1529
|
+
self.last_indent = indent
|
1530
|
+
return indent < self.last_indent
|
1531
|
+
|
1532
|
+
def parse_docstring(self, docstring, namespace, start):
|
1533
|
+
r"""
|
1534
|
+
Return a list of doctest defined in this docstring.
|
1535
|
+
|
1536
|
+
Code blocks in a REST file can contain python functions with
|
1537
|
+
their own docstrings in addition to in-line doctests. We want
|
1538
|
+
to include the tests from these inner docstrings, but Python's
|
1539
|
+
doctesting module has a problem if we just pass on the whole
|
1540
|
+
block, since it expects to get just a docstring, not the
|
1541
|
+
Python code as well.
|
1542
|
+
|
1543
|
+
Our solution is to create a new doctest source from this code
|
1544
|
+
block and append the doctests created from that source. We
|
1545
|
+
then replace the occurrences of "sage:" and ">>>" occurring
|
1546
|
+
inside a triple quote with "safe:" so that the doctest module
|
1547
|
+
doesn't treat them as tests.
|
1548
|
+
|
1549
|
+
EXAMPLES::
|
1550
|
+
|
1551
|
+
sage: from sage.doctest.control import DocTestDefaults
|
1552
|
+
sage: from sage.doctest.sources import FileDocTestSource
|
1553
|
+
sage: from sage.doctest.parsing import SageDocTestParser
|
1554
|
+
sage: from sage.doctest.util import NestedName
|
1555
|
+
sage: filename = "sage_doc.rst"
|
1556
|
+
sage: FDS = FileDocTestSource(filename, DocTestDefaults())
|
1557
|
+
sage: FDS.parser = SageDocTestParser(set(['sage']))
|
1558
|
+
sage: FDS.qualified_name = NestedName('sage_doc')
|
1559
|
+
sage: s = "Some text::\n\n def example_python_function(a, \
|
1560
|
+
....: b):\n '''\n Brief description \
|
1561
|
+
....: of function.\n\n EXAMPLES::\n\n \
|
1562
|
+
....: sage: test1()\n sage: test2()\n \
|
1563
|
+
....: '''\n return a + b\n\n sage: test3()\n\nMore \
|
1564
|
+
....: ReST documentation."
|
1565
|
+
sage: tests = FDS.parse_docstring(s, {}, 100)
|
1566
|
+
sage: len(tests)
|
1567
|
+
2
|
1568
|
+
sage: for ex in tests[0].examples:
|
1569
|
+
....: print(ex.sage_source)
|
1570
|
+
test3()
|
1571
|
+
sage: for ex in tests[1].examples:
|
1572
|
+
....: print(ex.sage_source)
|
1573
|
+
test1()
|
1574
|
+
test2()
|
1575
|
+
sig_on_count() # check sig_on/off pairings (virtual doctest)
|
1576
|
+
"""
|
1577
|
+
PythonStringSource = dynamic_class("sage.doctest.sources.PythonStringSource",
|
1578
|
+
(StringDocTestSource, PythonSource))
|
1579
|
+
min_indent = self.parser._min_indent(docstring)
|
1580
|
+
pysource = '\n'.join(l[min_indent:] for l in docstring.split('\n'))
|
1581
|
+
inner_source = PythonStringSource(self.basename, pysource,
|
1582
|
+
self.options,
|
1583
|
+
self.printpath,
|
1584
|
+
lineno_shift=start + 1)
|
1585
|
+
inner_doctests, _ = inner_source._create_doctests(namespace, True)
|
1586
|
+
safe_docstring = inner_source._neutralize_doctests(min_indent)
|
1587
|
+
outer_doctest = self.parser.get_doctest(safe_docstring, namespace,
|
1588
|
+
str(self.qualified_name),
|
1589
|
+
self.printpath, start + 1)
|
1590
|
+
return [outer_doctest] + inner_doctests
|
1591
|
+
|
1592
|
+
|
1593
|
+
class DictAsObject(dict):
|
1594
|
+
"""
|
1595
|
+
A simple subclass of dict that inserts the items from the initializing dictionary into attributes.
|
1596
|
+
|
1597
|
+
EXAMPLES::
|
1598
|
+
|
1599
|
+
sage: from sage.doctest.sources import DictAsObject
|
1600
|
+
sage: D = DictAsObject({'a':2})
|
1601
|
+
sage: D.a
|
1602
|
+
2
|
1603
|
+
"""
|
1604
|
+
def __init__(self, attrs):
|
1605
|
+
"""
|
1606
|
+
Initialization.
|
1607
|
+
|
1608
|
+
INPUT:
|
1609
|
+
|
1610
|
+
- ``attrs`` -- dictionary
|
1611
|
+
|
1612
|
+
EXAMPLES::
|
1613
|
+
|
1614
|
+
sage: from sage.doctest.sources import DictAsObject
|
1615
|
+
sage: D = DictAsObject({'a':2})
|
1616
|
+
sage: D.a == D['a']
|
1617
|
+
True
|
1618
|
+
sage: D.a
|
1619
|
+
2
|
1620
|
+
"""
|
1621
|
+
super().__init__(attrs)
|
1622
|
+
self.__dict__.update(attrs)
|
1623
|
+
|
1624
|
+
def __setitem__(self, ky, val):
|
1625
|
+
"""
|
1626
|
+
We preserve the ability to access entries through either the
|
1627
|
+
dictionary or attribute interfaces.
|
1628
|
+
|
1629
|
+
EXAMPLES::
|
1630
|
+
|
1631
|
+
sage: from sage.doctest.sources import DictAsObject
|
1632
|
+
sage: D = DictAsObject({})
|
1633
|
+
sage: D['a'] = 2
|
1634
|
+
sage: D.a
|
1635
|
+
2
|
1636
|
+
"""
|
1637
|
+
super().__setitem__(ky, val)
|
1638
|
+
try:
|
1639
|
+
super().__setattr__(ky, val)
|
1640
|
+
except TypeError:
|
1641
|
+
pass
|
1642
|
+
|
1643
|
+
def __setattr__(self, ky, val):
|
1644
|
+
"""
|
1645
|
+
We preserve the ability to access entries through either the
|
1646
|
+
dictionary or attribute interfaces.
|
1647
|
+
|
1648
|
+
EXAMPLES::
|
1649
|
+
|
1650
|
+
sage: from sage.doctest.sources import DictAsObject
|
1651
|
+
sage: D = DictAsObject({})
|
1652
|
+
sage: D.a = 2
|
1653
|
+
sage: D['a']
|
1654
|
+
2
|
1655
|
+
"""
|
1656
|
+
super().__setitem__(ky, val)
|
1657
|
+
super().__setattr__(ky, val)
|