Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/doctest/forker.py
8817 views
1
"""
2
Processes for running doctests
3
4
This module controls the processes started by Sage that actually run
5
the doctests.
6
7
EXAMPLES:
8
9
The following examples are used in doctesting this file::
10
11
sage: doctest_var = 42; doctest_var^2
12
1764
13
sage: R.<a> = ZZ[]
14
sage: a + doctest_var
15
a + 42
16
17
AUTHORS:
18
19
- David Roe (2012-03-27) -- initial version, based on Robert Bradshaw's code.
20
"""
21
22
#*****************************************************************************
23
# Copyright (C) 2012 David Roe <[email protected]>
24
# Robert Bradshaw <[email protected]>
25
# William Stein <[email protected]>
26
# Copyright (C) 2013 Jeroen Demeyer <[email protected]>
27
#
28
# Distributed under the terms of the GNU General Public License (GPL)
29
# as published by the Free Software Foundation; either version 2 of
30
# the License, or (at your option) any later version.
31
# http://www.gnu.org/licenses/
32
#*****************************************************************************
33
34
from __future__ import print_function
35
36
import hashlib, multiprocessing, os, sys, time, warnings, signal, linecache
37
import doctest, traceback
38
import sage.misc.randstate as randstate
39
from util import Timer, RecordingDict, count_noun
40
from sources import DictAsObject
41
from parsing import OriginalSource, reduce_hex
42
from sage.structure.sage_object import SageObject
43
from parsing import SageOutputChecker, pre_hash, get_source
44
45
46
def init_sage():
47
"""
48
Import the Sage library.
49
50
This function is called once at the beginning of a doctest run
51
(rather than once for each file). It imports the Sage library,
52
sets DOCTEST_MODE to True, and invalidates any interfaces.
53
54
EXAMPLES::
55
56
sage: from sage.doctest.forker import init_sage
57
sage: sage.doctest.DOCTEST_MODE = False
58
sage: init_sage()
59
sage: sage.doctest.DOCTEST_MODE
60
True
61
62
Check that pexpect interfaces are invalidated, but still work::
63
64
sage: gap.eval("my_test_var := 42;")
65
'42'
66
sage: gap.eval("my_test_var;")
67
'42'
68
sage: init_sage()
69
sage: gap('Group((1,2,3)(4,5), (3,4))')
70
Group( [ (1,2,3)(4,5), (3,4) ] )
71
sage: gap.eval("my_test_var;")
72
Traceback (most recent call last):
73
...
74
RuntimeError: Gap produced error output...
75
76
Check that SymPy equation pretty printer is limited in doctest
77
mode to default width (80 chars)::
78
79
sage: from sympy import sympify
80
sage: from sympy.printing.pretty.pretty import PrettyPrinter
81
sage: s = sympify('+x^'.join(str(i) for i in range(30)))
82
sage: print PrettyPrinter(settings={'wrap_line':True}).doprint(s)
83
29 28 27 26 25 24 23 22 21 20 19 18 17
84
x + x + x + x + x + x + x + x + x + x + x + x + x +
85
<BLANKLINE>
86
16 15 14 13 12 11 10 9 8 7 6 5 4 3
87
x + x + x + x + x + x + x + x + x + x + x + x + x + x + x
88
<BLANKLINE>
89
2
90
+ x
91
"""
92
# Do this once before forking.
93
import sage.doctest
94
sage.doctest.DOCTEST_MODE=True
95
import sage.all_cmdline
96
sage.interfaces.quit.invalidate_all()
97
import sage.misc.displayhook
98
sys.displayhook = sage.misc.displayhook.DisplayHook(sys.displayhook)
99
100
# Switch on extra debugging
101
from sage.structure.debug_options import debug
102
debug.refine_category_hash_check = True
103
104
# Disable IPython colors during doctests
105
from sage.misc.interpreter import DEFAULT_SAGE_CONFIG
106
DEFAULT_SAGE_CONFIG['TerminalInteractiveShell']['colors'] = 'NoColor'
107
108
# We import readline before forking, otherwise Pdb doesn't work
109
# os OS X: http://trac.sagemath.org/14289
110
import readline
111
112
# Disable SymPy terminal width detection
113
from sympy.printing.pretty.stringpict import stringPict
114
stringPict.terminal_width = lambda self:0
115
116
117
def warning_function(file):
118
r"""
119
Creates a function that prints warnings to the given file.
120
121
INPUT:
122
123
- ``file`` -- an open file handle.
124
125
OUPUT:
126
127
- a function that prings warnings to the given file.
128
129
EXAMPLES::
130
131
sage: from sage.doctest.forker import warning_function
132
sage: import os
133
sage: F = os.tmpfile()
134
sage: wrn = warning_function(F)
135
sage: wrn("bad stuff", UserWarning, "myfile.py", 0)
136
sage: F.seek(0)
137
sage: F.read()
138
'doctest:0: UserWarning: bad stuff\n'
139
"""
140
def doctest_showwarning(message, category, filename, lineno, file=file, line=None):
141
try:
142
file.write(warnings.formatwarning(message, category, 'doctest', lineno, line))
143
except IOError:
144
pass # the file (probably stdout) is invalid
145
return doctest_showwarning
146
147
148
class SageSpoofInOut(SageObject):
149
r"""
150
We replace the standard :class:`doctest._SpoofOut` for three reasons:
151
152
- we need to divert the output of C programs that don't print
153
through sys.stdout,
154
- we want the ability to recover partial output from doctest
155
processes that segfault.
156
- we also redirect stdin (usually from /dev/null) during doctests.
157
158
This class defines streams ``self.real_stdin``, ``self.real_stdout``
159
and ``self.real_stderr`` which refer to the original streams.
160
161
INPUT:
162
163
- ``outfile`` -- (default: ``os.tmpfile()``) a seekable open file
164
object to which stdout and stderr should be redirected.
165
166
- ``infile`` -- (default: ``open(os.devnull)``) an open file object
167
from which stdin should be redirected.
168
169
EXAMPLES::
170
171
sage: import os, subprocess
172
sage: from sage.doctest.forker import SageSpoofInOut
173
sage: O = os.tmpfile()
174
sage: S = SageSpoofInOut(O)
175
sage: try:
176
....: S.start_spoofing()
177
....: print "hello world"
178
....: finally:
179
....: S.stop_spoofing()
180
....:
181
sage: S.getvalue()
182
'hello world\n'
183
sage: O.seek(0)
184
sage: S = SageSpoofInOut(outfile=sys.stdout, infile=O)
185
sage: try:
186
....: S.start_spoofing()
187
....: _ = subprocess.check_call("cat")
188
....: finally:
189
....: S.stop_spoofing()
190
....:
191
hello world
192
"""
193
def __init__(self, outfile=None, infile=None):
194
"""
195
Initialization.
196
197
TESTS::
198
199
sage: from sage.doctest.forker import SageSpoofInOut
200
sage: SageSpoofInOut(os.tmpfile(), os.tmpfile())
201
<class 'sage.doctest.forker.SageSpoofInOut'>
202
"""
203
if infile is None:
204
self.infile = open(os.devnull)
205
else:
206
self.infile = infile
207
if outfile is None:
208
self.outfile = os.tmpfile()
209
else:
210
self.outfile = outfile
211
self.spoofing = False
212
self.real_stdin = os.fdopen(os.dup(sys.stdin.fileno()), "r")
213
self.real_stdout = os.fdopen(os.dup(sys.stdout.fileno()), "a")
214
self.real_stderr = os.fdopen(os.dup(sys.stderr.fileno()), "a")
215
self.position = 0
216
217
def __del__(self):
218
"""
219
Stop spoofing.
220
221
TESTS::
222
223
sage: from sage.doctest.forker import SageSpoofInOut
224
sage: spoof = SageSpoofInOut()
225
sage: spoof.start_spoofing()
226
sage: print "Spoofed!" # No output
227
sage: del spoof
228
sage: print "Not spoofed!"
229
Not spoofed!
230
"""
231
self.stop_spoofing()
232
233
def start_spoofing(self):
234
r"""
235
Set stdin to read from ``self.infile`` and stdout to print to
236
``self.outfile``.
237
238
EXAMPLES::
239
240
sage: import os
241
sage: from sage.doctest.forker import SageSpoofInOut
242
sage: O = os.tmpfile()
243
sage: S = SageSpoofInOut(O)
244
sage: try:
245
....: S.start_spoofing()
246
....: print "this is not printed"
247
....: finally:
248
....: S.stop_spoofing()
249
....:
250
sage: S.getvalue()
251
'this is not printed\n'
252
sage: O.seek(0)
253
sage: S = SageSpoofInOut(infile=O)
254
sage: try:
255
....: S.start_spoofing()
256
....: v = sys.stdin.read()
257
....: finally:
258
....: S.stop_spoofing()
259
....:
260
sage: v
261
'this is not printed\n'
262
263
We also catch non-Python output::
264
265
sage: try:
266
....: S.start_spoofing()
267
....: retval = os.system('''echo "Hello there"\nif [ $? -eq 0 ]; then\necho "good"\nfi''')
268
....: finally:
269
....: S.stop_spoofing()
270
....:
271
sage: S.getvalue()
272
'Hello there\ngood\n'
273
"""
274
if not self.spoofing:
275
sys.stdout.flush()
276
sys.stderr.flush()
277
self.outfile.flush()
278
os.dup2(self.infile.fileno(), sys.stdin.fileno())
279
os.dup2(self.outfile.fileno(), sys.stdout.fileno())
280
os.dup2(self.outfile.fileno(), sys.stderr.fileno())
281
self.spoofing = True
282
283
def stop_spoofing(self):
284
"""
285
Reset stdin and stdout to their original values.
286
287
EXAMPLES::
288
289
sage: from sage.doctest.forker import SageSpoofInOut
290
sage: S = SageSpoofInOut()
291
sage: try:
292
....: S.start_spoofing()
293
....: print "this is not printed"
294
....: finally:
295
....: S.stop_spoofing()
296
....:
297
sage: print "this is now printed"
298
this is now printed
299
"""
300
if self.spoofing:
301
sys.stdout.flush()
302
sys.stderr.flush()
303
self.real_stdout.flush()
304
self.real_stderr.flush()
305
os.dup2(self.real_stdin.fileno(), sys.stdin.fileno())
306
os.dup2(self.real_stdout.fileno(), sys.stdout.fileno())
307
os.dup2(self.real_stderr.fileno(), sys.stderr.fileno())
308
self.spoofing = False
309
310
def getvalue(self):
311
r"""
312
Gets the value that has been printed to ``outfile`` since the
313
last time this function was called.
314
315
EXAMPLES::
316
317
sage: from sage.doctest.forker import SageSpoofInOut
318
sage: S = SageSpoofInOut()
319
sage: try:
320
....: S.start_spoofing()
321
....: print "step 1"
322
....: finally:
323
....: S.stop_spoofing()
324
....:
325
sage: S.getvalue()
326
'step 1\n'
327
sage: try:
328
....: S.start_spoofing()
329
....: print "step 2"
330
....: finally:
331
....: S.stop_spoofing()
332
....:
333
sage: S.getvalue()
334
'step 2\n'
335
"""
336
sys.stdout.flush()
337
self.outfile.seek(self.position)
338
result = self.outfile.read()
339
self.position = self.outfile.tell()
340
if not result.endswith("\n"):
341
result += "\n"
342
return result
343
344
345
class SageDocTestRunner(doctest.DocTestRunner):
346
def __init__(self, *args, **kwds):
347
"""
348
A customized version of DocTestRunner that tracks dependencies
349
of doctests.
350
351
INPUT:
352
353
- ``stdout`` -- an open file to restore for debugging
354
355
- ``checker`` -- None, or an instance of
356
:class:`doctest.OutputChecker`
357
358
- ``verbose`` -- boolean, determines whether verbose printing
359
is enabled.
360
361
- ``optionflags`` -- Controls the comparison with the expected
362
output. See :mod:`testmod` for more information.
363
364
EXAMPLES::
365
366
sage: from sage.doctest.parsing import SageOutputChecker
367
sage: from sage.doctest.forker import SageDocTestRunner
368
sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
369
sage: import doctest, sys, os
370
sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
371
sage: DTR
372
<sage.doctest.forker.SageDocTestRunner instance at ...>
373
"""
374
O = kwds.pop('outtmpfile', None)
375
self.msgfile = kwds.pop('msgfile', None)
376
self.options = kwds.pop('sage_options')
377
doctest.DocTestRunner.__init__(self, *args, **kwds)
378
self._fakeout = SageSpoofInOut(O)
379
if self.msgfile is None:
380
self.msgfile = self._fakeout.real_stdout
381
self.history = []
382
self.references = []
383
self.setters = {}
384
self.running_global_digest = hashlib.md5()
385
386
def _run(self, test, compileflags, out):
387
"""
388
This function replaces :meth:`doctest.DocTestRunner.__run`.
389
390
It changes the following behavior:
391
392
- We call :meth:`SageDocTestRunner.execute` rather than just
393
exec
394
395
- We don't truncate _fakeout after each example since we want
396
the output file to be readable by the calling
397
:class:`SageWorker`.
398
399
Since it needs to be able to read stdout, it should be called
400
while spoofing using :class:`SageSpoofInOut`.
401
402
EXAMPLES::
403
404
sage: from sage.doctest.parsing import SageOutputChecker
405
sage: from sage.doctest.forker import SageDocTestRunner
406
sage: from sage.doctest.sources import FileDocTestSource
407
sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
408
sage: from sage.env import SAGE_SRC
409
sage: import doctest, sys, os
410
sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
411
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py')
412
sage: FDS = FileDocTestSource(filename,DD)
413
sage: doctests, extras = FDS.create_doctests(globals())
414
sage: DTR.run(doctests[0], clear_globs=False) # indirect doctest
415
TestResults(failed=0, attempted=4)
416
"""
417
# Keep track of the number of failures and tries.
418
failures = tries = 0
419
420
# Save the option flags (since option directives can be used
421
# to modify them).
422
original_optionflags = self.optionflags
423
424
SUCCESS, FAILURE, BOOM = range(3) # `outcome` state
425
426
check = self._checker.check_output
427
428
# Process each example.
429
for examplenum, example in enumerate(test.examples):
430
431
# If REPORT_ONLY_FIRST_FAILURE is set, then suppress
432
# reporting after the first failure.
433
quiet = (self.optionflags & doctest.REPORT_ONLY_FIRST_FAILURE and
434
failures > 0)
435
436
# Merge in the example's options.
437
self.optionflags = original_optionflags
438
if example.options:
439
for (optionflag, val) in example.options.items():
440
if val:
441
self.optionflags |= optionflag
442
else:
443
self.optionflags &= ~optionflag
444
445
# If 'SKIP' is set, then skip this example.
446
if self.optionflags & doctest.SKIP:
447
continue
448
449
# Record that we started this example.
450
tries += 1
451
# We print the example we're running for easier debugging
452
# if this file times out or crashes.
453
with OriginalSource(example):
454
print("sage: " + example.source[:-1] + " ## line %s ##"%(test.lineno + example.lineno + 1))
455
# Update the position so that result comparison works
456
throwaway = self._fakeout.getvalue()
457
if not quiet:
458
self.report_start(out, test, example)
459
460
# Flush files before running the example, so we know for
461
# sure that everything is reported properly if the test
462
# crashes.
463
sys.stdout.flush()
464
sys.stderr.flush()
465
self.msgfile.flush()
466
467
# Use a special filename for compile(), so we can retrieve
468
# the source code during interactive debugging (see
469
# __patched_linecache_getlines).
470
filename = '<doctest %s[%d]>' % (test.name, examplenum)
471
472
# Run the example in the given context (globs), and record
473
# any exception that gets raised. But for SystemExit, we
474
# simply propagate the exception.
475
exception = None
476
try:
477
# Don't blink! This is where the user's code gets run.
478
compiled = compile(example.source, filename, "single",
479
compileflags, 1)
480
self.execute(example, compiled, test.globs)
481
except SystemExit:
482
raise
483
except BaseException:
484
exception = sys.exc_info()
485
finally:
486
if self.debugger is not None:
487
self.debugger.set_continue() # ==== Example Finished ====
488
489
got = self._fakeout.getvalue() # the actual output
490
outcome = FAILURE # guilty until proved innocent or insane
491
492
# If the example executed without raising any exceptions,
493
# verify its output.
494
if exception is None:
495
if check(example.want, got, self.optionflags):
496
outcome = SUCCESS
497
498
# The example raised an exception: check if it was expected.
499
else:
500
exc_info = sys.exc_info()
501
exc_msg = traceback.format_exception_only(*exc_info[:2])[-1]
502
if not quiet:
503
got += doctest._exception_traceback(exc_info)
504
505
# If `example.exc_msg` is None, then we weren't expecting
506
# an exception.
507
if example.exc_msg is None:
508
outcome = BOOM
509
510
# We expected an exception: see whether it matches.
511
elif check(example.exc_msg, exc_msg, self.optionflags):
512
outcome = SUCCESS
513
514
# Another chance if they didn't care about the detail.
515
elif self.optionflags & doctest.IGNORE_EXCEPTION_DETAIL:
516
m1 = re.match(r'(?:[^:]*\.)?([^:]*:)', example.exc_msg)
517
m2 = re.match(r'(?:[^:]*\.)?([^:]*:)', exc_msg)
518
if m1 and m2 and check(m1.group(1), m2.group(1),
519
self.optionflags):
520
outcome = SUCCESS
521
522
# Report the outcome.
523
if outcome is SUCCESS:
524
if self.options.warn_long and example.walltime > self.options.warn_long:
525
self.report_overtime(out, test, example, got)
526
failures += 1
527
elif not quiet:
528
self.report_success(out, test, example, got)
529
elif outcome is FAILURE:
530
if not quiet:
531
self.report_failure(out, test, example, got, test.globs)
532
failures += 1
533
elif outcome is BOOM:
534
if not quiet:
535
self.report_unexpected_exception(out, test, example,
536
exc_info)
537
failures += 1
538
else:
539
assert False, ("unknown outcome", outcome)
540
541
# Restore the option flags (in case they were modified)
542
self.optionflags = original_optionflags
543
544
# Record and return the number of failures and tries.
545
self._DocTestRunner__record_outcome(test, failures, tries)
546
return doctest.TestResults(failures, tries)
547
548
def run(self, test, compileflags=None, out=None, clear_globs=True):
549
"""
550
Runs the examples in a given doctest.
551
552
This function replaces :class:`doctest.DocTestRunner.run`
553
since it needs to handle spoofing. It also leaves the display
554
hook in place.
555
556
INPUT:
557
558
- ``test`` -- an instance of :class:`doctest.DocTest`
559
560
- ``compileflags`` -- the set of compiler flags used to
561
execute examples (passed in to the :func:`compile`). If
562
None, they are filled in from the result of
563
:func:`doctest._extract_future_flags` applied to
564
``test.globs``.
565
566
- ``out`` -- a function for writing the output (defaults to
567
:func:`sys.stdout.write`).
568
569
- ``clear_globs`` -- boolean (default True): whether to clear
570
the namespace after running this doctest.
571
572
OUTPUT:
573
574
- ``f`` -- integer, the number of examples that failed
575
576
- ``t`` -- the number of examples tried
577
578
EXAMPLES::
579
580
sage: from sage.doctest.parsing import SageOutputChecker
581
sage: from sage.doctest.forker import SageDocTestRunner
582
sage: from sage.doctest.sources import FileDocTestSource
583
sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
584
sage: from sage.env import SAGE_SRC
585
sage: import doctest, sys, os
586
sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
587
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py')
588
sage: FDS = FileDocTestSource(filename,DD)
589
sage: doctests, extras = FDS.create_doctests(globals())
590
sage: DTR.run(doctests[0], clear_globs=False)
591
TestResults(failed=0, attempted=4)
592
"""
593
self.setters = {}
594
randstate.set_random_seed(long(0))
595
warnings.showwarning = warning_function(sys.stdout)
596
self.running_doctest_digest = hashlib.md5()
597
self.test = test
598
if compileflags is None:
599
compileflags = doctest._extract_future_flags(test.globs)
600
# We use this slightly modified version of Pdb because it
601
# interacts better with the doctesting framework (like allowing
602
# doctests for sys.settrace()). Since we already have output
603
# spoofing in place, there is no need for redirection.
604
if self.options.debug:
605
self.debugger = doctest._OutputRedirectingPdb(sys.stdout)
606
self.debugger.reset()
607
else:
608
self.debugger = None
609
self.save_linecache_getlines = linecache.getlines
610
linecache.getlines = self._DocTestRunner__patched_linecache_getlines
611
if out is None:
612
def out(s):
613
self.msgfile.write(s)
614
615
self._fakeout.start_spoofing()
616
# If self.options.initial is set, we show only the first failure in each doctest block.
617
self.no_failure_yet = True
618
try:
619
return self._run(test, compileflags, out)
620
finally:
621
self._fakeout.stop_spoofing()
622
linecache.getlines = self.save_linecache_getlines
623
if clear_globs:
624
test.globs.clear()
625
626
def summarize(self, verbose=None):
627
"""
628
Print results of testing to ``self.msgfile`` and return number
629
of failures and tests run.
630
631
INPUT:
632
633
- ``verbose`` -- whether to print lots of stuff
634
635
OUTPUT:
636
637
- returns ``(f, t)``, a :class:`doctest.TestResults` instance
638
giving the number of failures and the total number of tests
639
run.
640
641
EXAMPLES::
642
643
sage: from sage.doctest.parsing import SageOutputChecker
644
sage: from sage.doctest.forker import SageDocTestRunner
645
sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
646
sage: import doctest, sys, os
647
sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
648
sage: DTR._name2ft['sage.doctest.forker'] = (1,120)
649
sage: results = DTR.summarize()
650
**********************************************************************
651
1 item had failures:
652
1 of 120 in sage.doctest.forker
653
sage: results
654
TestResults(failed=1, attempted=120)
655
"""
656
if verbose is None:
657
verbose = self._verbose
658
m = self.msgfile
659
notests = []
660
passed = []
661
failed = []
662
totalt = totalf = 0
663
for x in self._name2ft.items():
664
name, (f, t) = x
665
assert f <= t
666
totalt += t
667
totalf += f
668
if not t:
669
notests.append(name)
670
elif not f:
671
passed.append( (name, t) )
672
else:
673
failed.append(x)
674
if verbose:
675
if notests:
676
print(count_noun(len(notests), "item"), "had no tests:", file=m)
677
notests.sort()
678
for thing in notests:
679
print(" %s"%thing, file=m)
680
if passed:
681
print(count_noun(len(passed), "item"), "passed all tests:", file=m)
682
passed.sort()
683
for thing, count in passed:
684
print(" %s in %s"%(count_noun(count, "test", pad_number=3, pad_noun=True), thing), file=m)
685
if failed:
686
print(self.DIVIDER, file=m)
687
print(count_noun(len(failed), "item"), "had failures:", file=m)
688
failed.sort()
689
for thing, (f, t) in failed:
690
print(" %3d of %3d in %s"%(f, t, thing), file=m)
691
if verbose:
692
print(count_noun(totalt, "test") + " in " + count_noun(len(self._name2ft), "item") + ".", file=m)
693
print("%s passed and %s failed."%(totalt - totalf, totalf), file=m)
694
if totalf:
695
print("***Test Failed***", file=m)
696
else:
697
print("Test passed.", file=m)
698
m.flush()
699
return doctest.TestResults(totalf, totalt)
700
701
def update_digests(self, example):
702
"""
703
Update global and doctest digests.
704
705
Sage's doctest runner tracks the state of doctests so that
706
their dependencies are known. For example, in the following
707
two lines ::
708
709
sage: R.<x> = ZZ[]
710
sage: f = x^2 + 1
711
712
it records that the second line depends on the first since the
713
first INSERTS ``x`` into the global namespace and the second
714
line RETRIEVES ``x`` from the global namespace.
715
716
This function updates the hashes that record these
717
dependencies.
718
719
INPUT:
720
721
- ``example`` -- a :class:`doctest.Example` instance
722
723
EXAMPLES::
724
725
sage: from sage.doctest.parsing import SageOutputChecker
726
sage: from sage.doctest.forker import SageDocTestRunner
727
sage: from sage.doctest.sources import FileDocTestSource
728
sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
729
sage: from sage.env import SAGE_SRC
730
sage: import doctest, sys, os, hashlib
731
sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
732
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py')
733
sage: FDS = FileDocTestSource(filename,DD)
734
sage: doctests, extras = FDS.create_doctests(globals())
735
sage: DTR.running_global_digest.hexdigest()
736
'd41d8cd98f00b204e9800998ecf8427e'
737
sage: DTR.running_doctest_digest = hashlib.md5()
738
sage: ex = doctests[0].examples[0]; ex.predecessors = None
739
sage: DTR.update_digests(ex)
740
sage: DTR.running_global_digest.hexdigest()
741
'3cb44104292c3a3ab4da3112ce5dc35c'
742
"""
743
s = pre_hash(get_source(example))
744
self.running_global_digest.update(s)
745
self.running_doctest_digest.update(s)
746
if example.predecessors is not None:
747
digest = hashlib.md5(s)
748
digest.update(reduce_hex(e.running_state for e in example.predecessors))
749
example.running_state = digest.hexdigest()
750
751
def execute(self, example, compiled, globs):
752
"""
753
Runs the given example, recording dependencies.
754
755
Rather than using a basic dictionary, Sage's doctest runner
756
uses a :class:`sage.doctest.util.RecordingDict`, which records
757
every time a value is set or retrieved. Executing the given
758
code with this recording dictionary as the namespace allows
759
Sage to track dependencies between doctest lines. For
760
example, in the following two lines ::
761
762
sage: R.<x> = ZZ[]
763
sage: f = x^2 + 1
764
765
the recording dictionary records that the second line depends
766
on the first since the first INSERTS ``x`` into the global
767
namespace and the second line RETRIEVES ``x`` from the global
768
namespace.
769
770
INPUT:
771
772
- ``example`` -- a :class:`doctest.Example` instance.
773
- ``compiled`` -- a code object produced by compiling ``example.source``
774
- ``globs`` -- a dictionary in which to execute ``compiled``.
775
776
OUTPUT:
777
778
- the output of the compiled code snippet.
779
780
EXAMPLES::
781
782
sage: from sage.doctest.parsing import SageOutputChecker
783
sage: from sage.doctest.forker import SageDocTestRunner
784
sage: from sage.doctest.sources import FileDocTestSource
785
sage: from sage.doctest.util import RecordingDict
786
sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
787
sage: from sage.env import SAGE_SRC
788
sage: import doctest, sys, os, hashlib
789
sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
790
sage: DTR.running_doctest_digest = hashlib.md5()
791
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py')
792
sage: FDS = FileDocTestSource(filename,DD)
793
sage: globs = RecordingDict(globals())
794
sage: globs.has_key('doctest_var')
795
False
796
sage: doctests, extras = FDS.create_doctests(globs)
797
sage: ex0 = doctests[0].examples[0]
798
sage: compiled = compile(ex0.source, '<doctest sage.doctest.forker[0]>', 'single', 32768, 1)
799
sage: DTR.execute(ex0, compiled, globs)
800
1764
801
sage: globs['doctest_var']
802
42
803
sage: globs.set
804
set(['doctest_var'])
805
sage: globs.got
806
set(['Integer'])
807
808
Now we can execute some more doctests to see the dependencies. ::
809
810
sage: ex1 = doctests[0].examples[1]
811
sage: compiled = compile(ex1.source, '<doctest sage.doctest.forker[1]>', 'single', 32768, 1)
812
sage: DTR.execute(ex1, compiled, globs)
813
sage: sorted(list(globs.set))
814
['R', 'a']
815
sage: globs.got
816
set(['ZZ'])
817
sage: ex1.predecessors
818
[]
819
820
::
821
822
sage: ex2 = doctests[0].examples[2]
823
sage: compiled = compile(ex2.source, '<doctest sage.doctest.forker[2]>', 'single', 32768, 1)
824
sage: DTR.execute(ex2, compiled, globs)
825
a + 42
826
sage: list(globs.set)
827
[]
828
sage: sorted(list(globs.got))
829
['a', 'doctest_var']
830
sage: set(ex2.predecessors) == set([ex0,ex1])
831
True
832
"""
833
if isinstance(globs, RecordingDict):
834
globs.start()
835
example.sequence_number = len(self.history)
836
self.history.append(example)
837
timer = Timer().start()
838
try:
839
exec compiled in globs
840
finally:
841
timer.stop().annotate(example)
842
if isinstance(globs, RecordingDict):
843
example.predecessors = []
844
for name in globs.got:
845
ref = self.setters.get(name)
846
if ref is not None:
847
example.predecessors.append(ref)
848
for name in globs.set:
849
self.setters[name] = example
850
else:
851
example.predecessors = None
852
self.update_digests(example)
853
example.total_state = self.running_global_digest.hexdigest()
854
example.doctest_state = self.running_doctest_digest.hexdigest()
855
856
def _failure_header(self, test, example):
857
"""
858
We strip out ``sage:`` prompts, so we override
859
:meth:`doctest.DocTestRunner._failure_header` for better
860
reporting.
861
862
INPUT:
863
864
- ``test`` -- a :class:`doctest.DocTest` instance
865
866
- ``example`` -- a :class:`doctest.Example` instance in ``test``.
867
868
OUTPUT:
869
870
- a string used for reporting that the given example failed.
871
872
EXAMPLES::
873
874
sage: from sage.doctest.parsing import SageOutputChecker
875
sage: from sage.doctest.forker import SageDocTestRunner
876
sage: from sage.doctest.sources import FileDocTestSource
877
sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
878
sage: from sage.env import SAGE_SRC
879
sage: import doctest, sys, os
880
sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
881
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py')
882
sage: FDS = FileDocTestSource(filename,DD)
883
sage: doctests, extras = FDS.create_doctests(globals())
884
sage: ex = doctests[0].examples[0]
885
sage: print DTR._failure_header(doctests[0], ex)
886
**********************************************************************
887
File ".../sage/doctest/forker.py", line 11, in sage.doctest.forker
888
Failed example:
889
doctest_var = 42; doctest_var^2
890
<BLANKLINE>
891
892
Without the source swapping::
893
894
sage: import doctest
895
sage: print doctest.DocTestRunner._failure_header(DTR, doctests[0], ex)
896
**********************************************************************
897
File ".../sage/doctest/forker.py", line 11, in sage.doctest.forker
898
Failed example:
899
doctest_var = Integer(42); doctest_var**Integer(2)
900
<BLANKLINE>
901
"""
902
with OriginalSource(example):
903
return doctest.DocTestRunner._failure_header(self, test, example)
904
905
def report_start(self, out, test, example):
906
"""
907
Called when an example starts.
908
909
INPUT:
910
911
- ``out`` -- a function for printing
912
913
- ``test`` -- a :class:`doctest.DocTest` instance
914
915
- ``example`` -- a :class:`doctest.Example` instance in ``test``
916
917
OUTPUT:
918
919
- prints a report to ``out``
920
921
EXAMPLES::
922
923
sage: from sage.doctest.parsing import SageOutputChecker
924
sage: from sage.doctest.forker import SageDocTestRunner
925
sage: from sage.doctest.sources import FileDocTestSource
926
sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
927
sage: from sage.env import SAGE_SRC
928
sage: import doctest, sys, os
929
sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=True, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
930
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py')
931
sage: FDS = FileDocTestSource(filename,DD)
932
sage: doctests, extras = FDS.create_doctests(globals())
933
sage: ex = doctests[0].examples[0]
934
sage: DTR.report_start(sys.stdout.write, doctests[0], ex)
935
Trying (line 11): doctest_var = 42; doctest_var^2
936
Expecting:
937
1764
938
"""
939
# We completely replace doctest.DocTestRunner.report_start so that we can include line numbers
940
with OriginalSource(example):
941
if self._verbose:
942
start_txt = ('Trying (line %s):'%(test.lineno + example.lineno + 1)
943
+ doctest._indent(example.source))
944
if example.want:
945
start_txt += 'Expecting:\n' + doctest._indent(example.want)
946
else:
947
start_txt += 'Expecting nothing\n'
948
out(start_txt)
949
950
def report_success(self, out, test, example, got):
951
"""
952
Called when an example succeeds.
953
954
INPUT:
955
956
- ``out`` -- a function for printing
957
958
- ``test`` -- a :class:`doctest.DocTest` instance
959
960
- ``example`` -- a :class:`doctest.Example` instance in ``test``
961
962
- ``got`` -- a string, the result of running ``example``
963
964
OUTPUT:
965
966
- prints a report to ``out``
967
968
- if in debugging mode, starts an IPython prompt at the point
969
of the failure
970
971
EXAMPLES::
972
973
sage: from sage.doctest.parsing import SageOutputChecker
974
sage: from sage.doctest.forker import SageDocTestRunner
975
sage: from sage.doctest.sources import FileDocTestSource
976
sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
977
sage: from sage.misc.misc import walltime
978
sage: from sage.env import SAGE_SRC
979
sage: import doctest, sys, os
980
sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=True, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
981
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py')
982
sage: FDS = FileDocTestSource(filename,DD)
983
sage: doctests, extras = FDS.create_doctests(globals())
984
sage: ex = doctests[0].examples[0]
985
sage: ex.walltime = 0.0
986
sage: DTR.report_success(sys.stdout.write, doctests[0], ex, '1764')
987
ok [0.00 s]
988
"""
989
# We completely replace doctest.DocTestRunner.report_success so that we can include time taken for the test
990
if self._verbose:
991
out("ok [%.2f s]\n"%example.walltime)
992
993
def report_failure(self, out, test, example, got, globs):
994
r"""
995
Called when a doctest fails.
996
997
INPUT:
998
999
- ``out`` -- a function for printing
1000
1001
- ``test`` -- a :class:`doctest.DocTest` instance
1002
1003
- ``example`` -- a :class:`doctest.Example` instance in ``test``
1004
1005
- ``got`` -- a string, the result of running ``example``
1006
1007
- ``globs`` -- a dictionary of globals, used if in debugging mode
1008
1009
OUTPUT:
1010
1011
- prints a report to ``out``
1012
1013
EXAMPLES::
1014
1015
sage: from sage.doctest.parsing import SageOutputChecker
1016
sage: from sage.doctest.forker import SageDocTestRunner
1017
sage: from sage.doctest.sources import FileDocTestSource
1018
sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
1019
sage: from sage.env import SAGE_SRC
1020
sage: import doctest, sys, os
1021
sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=True, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
1022
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py')
1023
sage: FDS = FileDocTestSource(filename,DD)
1024
sage: doctests, extras = FDS.create_doctests(globals())
1025
sage: ex = doctests[0].examples[0]
1026
sage: DTR.no_failure_yet = True
1027
sage: DTR.report_failure(sys.stdout.write, doctests[0], ex, 'BAD ANSWER\n', {})
1028
**********************************************************************
1029
File ".../sage/doctest/forker.py", line 11, in sage.doctest.forker
1030
Failed example:
1031
doctest_var = 42; doctest_var^2
1032
Expected:
1033
1764
1034
Got:
1035
BAD ANSWER
1036
1037
If debugging is turned on this function starts an IPython
1038
prompt when a test returns an incorrect answer::
1039
1040
sage: import os
1041
sage: os.environ['SAGE_PEXPECT_LOG'] = "1"
1042
sage: sage0.quit()
1043
sage: _ = sage0.eval("import doctest, sys, os, multiprocessing, subprocess")
1044
sage: _ = sage0.eval("from sage.doctest.parsing import SageOutputChecker")
1045
sage: _ = sage0.eval("import sage.doctest.forker as sdf")
1046
sage: _ = sage0.eval("sdf.init_sage()")
1047
sage: _ = sage0.eval("from sage.doctest.control import DocTestDefaults")
1048
sage: _ = sage0.eval("DD = DocTestDefaults(debug=True)")
1049
sage: _ = sage0.eval("ex1 = doctest.Example('a = 17', '')")
1050
sage: _ = sage0.eval("ex2 = doctest.Example('2*a', '1')")
1051
sage: _ = sage0.eval("DT = doctest.DocTest([ex1,ex2], globals(), 'doubling', None, 0, None)")
1052
sage: _ = sage0.eval("DTR = sdf.SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)")
1053
sage: sage0._prompt = r"debug: "
1054
sage: print sage0.eval("DTR.run(DT, clear_globs=False)") # indirect doctest
1055
**********************************************************************
1056
Line 1, in doubling
1057
Failed example:
1058
2*a
1059
Expected:
1060
1
1061
Got:
1062
34
1063
**********************************************************************
1064
Previously executed commands:
1065
...
1066
sage: sage0.eval("a")
1067
'...17'
1068
sage: sage0._prompt = "sage: "
1069
sage: sage0.eval("quit")
1070
'Returning to doctests...TestResults(failed=1, attempted=2)'
1071
"""
1072
if not self.options.initial or self.no_failure_yet:
1073
self.no_failure_yet = False
1074
returnval = doctest.DocTestRunner.report_failure(self, out, test, example, got)
1075
if self.options.debug:
1076
self._fakeout.stop_spoofing()
1077
restore_tcpgrp = None
1078
try:
1079
if os.isatty(0):
1080
# In order to read from the terminal, we need
1081
# to make the current process group the
1082
# foreground group.
1083
restore_tcpgrp = os.tcgetpgrp(0)
1084
signal.signal(signal.SIGTTIN, signal.SIG_IGN)
1085
signal.signal(signal.SIGTTOU, signal.SIG_IGN)
1086
os.tcsetpgrp(0, os.getpgrp())
1087
print("*" * 70)
1088
print("Previously executed commands:")
1089
for ex in test.examples:
1090
if ex is example:
1091
break
1092
if hasattr(ex, 'sage_source'):
1093
src = ' sage: ' + ex.sage_source
1094
else:
1095
src = ' sage: ' + ex.source
1096
if src[-1] == '\n':
1097
src = src[:-1]
1098
src = src.replace('\n', '\n ....: ')
1099
print(src)
1100
if ex.want:
1101
print(doctest._indent(ex.want[:-1]))
1102
from sage.misc.interpreter import DEFAULT_SAGE_CONFIG
1103
from IPython.terminal.embed import InteractiveShellEmbed
1104
cfg = DEFAULT_SAGE_CONFIG.copy()
1105
prompt_config = cfg.PromptManager
1106
prompt_config.in_template = 'debug: '
1107
prompt_config.in2_template = '.....: '
1108
shell = InteractiveShellEmbed(config=cfg, banner1='', user_ns=dict(globs))
1109
shell(header='', stack_depth=2)
1110
except KeyboardInterrupt:
1111
# Assume this is a *real* interrupt. We need to
1112
# escalate this to the master docbuilding process.
1113
if not self.options.serial:
1114
os.kill(os.getppid(), signal.SIGINT)
1115
raise
1116
finally:
1117
# Restore the foreground process group.
1118
if restore_tcpgrp is not None:
1119
os.tcsetpgrp(0, restore_tcpgrp)
1120
signal.signal(signal.SIGTTIN, signal.SIG_DFL)
1121
signal.signal(signal.SIGTTOU, signal.SIG_DFL)
1122
print("Returning to doctests...")
1123
self._fakeout.start_spoofing()
1124
return returnval
1125
1126
def report_overtime(self, out, test, example, got):
1127
r"""
1128
Called when the ``warn_long`` option flag is set and a doctest
1129
runs longer than the specified time.
1130
1131
INPUT:
1132
1133
- ``out`` -- a function for printing
1134
1135
- ``test`` -- a :class:`doctest.DocTest` instance
1136
1137
- ``example`` -- a :class:`doctest.Example` instance in ``test``
1138
1139
- ``got`` -- a string, the result of running ``example``
1140
1141
OUTPUT:
1142
1143
- prints a report to ``out``
1144
1145
EXAMPLES::
1146
1147
sage: from sage.doctest.parsing import SageOutputChecker
1148
sage: from sage.doctest.forker import SageDocTestRunner
1149
sage: from sage.doctest.sources import FileDocTestSource
1150
sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
1151
sage: from sage.misc.misc import walltime
1152
sage: from sage.env import SAGE_SRC
1153
sage: import doctest, sys, os
1154
sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=True, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
1155
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py')
1156
sage: FDS = FileDocTestSource(filename,DD)
1157
sage: doctests, extras = FDS.create_doctests(globals())
1158
sage: ex = doctests[0].examples[0]
1159
sage: ex.walltime = 1.23
1160
sage: DTR.report_overtime(sys.stdout.write, doctests[0], ex, 'BAD ANSWER\n')
1161
**********************************************************************
1162
File ".../sage/doctest/forker.py", line 11, in sage.doctest.forker
1163
Failed example:
1164
doctest_var = 42; doctest_var^2
1165
Test ran for 1.23 s
1166
"""
1167
out(self._failure_header(test, example) +
1168
"Test ran for %.2f s\n"%example.walltime)
1169
1170
def report_unexpected_exception(self, out, test, example, exc_info):
1171
r"""
1172
Called when a doctest raises an exception that's not matched by the expected output.
1173
1174
If debugging has been turned on, starts an interactive debugger.
1175
1176
INPUT:
1177
1178
- ``out`` -- a function for printing
1179
1180
- ``test`` -- a :class:`doctest.DocTest` instance
1181
1182
- ``example`` -- a :class:`doctest.Example` instance in ``test``
1183
1184
- ``exc_info`` -- the result of ``sys.exc_info()``
1185
1186
OUTPUT:
1187
1188
- prints a report to ``out``
1189
1190
- if in debugging mode, starts PDB with the given traceback
1191
1192
EXAMPLES::
1193
1194
sage: import os
1195
sage: os.environ['SAGE_PEXPECT_LOG'] = "1"
1196
sage: sage0.quit()
1197
sage: _ = sage0.eval("import doctest, sys, os, multiprocessing, subprocess")
1198
sage: _ = sage0.eval("from sage.doctest.parsing import SageOutputChecker")
1199
sage: _ = sage0.eval("import sage.doctest.forker as sdf")
1200
sage: _ = sage0.eval("from sage.doctest.control import DocTestDefaults")
1201
sage: _ = sage0.eval("DD = DocTestDefaults(debug=True)")
1202
sage: _ = sage0.eval("ex = doctest.Example('E = EllipticCurve([0,0]); E', 'A singular Elliptic Curve')")
1203
sage: _ = sage0.eval("DT = doctest.DocTest([ex], globals(), 'singular_curve', None, 0, None)")
1204
sage: _ = sage0.eval("DTR = sdf.SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)")
1205
sage: sage0._prompt = r"\(Pdb\) "
1206
sage: sage0.eval("DTR.run(DT, clear_globs=False)") # indirect doctest
1207
'... "Invariants %s define a singular curve."%ainvs'
1208
sage: sage0.eval("l")
1209
'...if self.discriminant() == 0:...raise ArithmeticError...'
1210
sage: sage0.eval("u")
1211
'...EllipticCurve_field.__init__(self, [field(x) for x in ainvs])'
1212
sage: sage0.eval("p ainvs")
1213
'[0, 0]'
1214
sage: sage0._prompt = "sage: "
1215
sage: sage0.eval("quit")
1216
'TestResults(failed=1, attempted=1)'
1217
"""
1218
if not self.options.initial or self.no_failure_yet:
1219
self.no_failure_yet = False
1220
returnval = doctest.DocTestRunner.report_unexpected_exception(self, out, test, example, exc_info)
1221
if self.options.debug:
1222
self._fakeout.stop_spoofing()
1223
restore_tcpgrp = None
1224
try:
1225
if os.isatty(0):
1226
# In order to read from the terminal, we need
1227
# to make the current process group the
1228
# foreground group.
1229
restore_tcpgrp = os.tcgetpgrp(0)
1230
signal.signal(signal.SIGTTIN, signal.SIG_IGN)
1231
signal.signal(signal.SIGTTOU, signal.SIG_IGN)
1232
os.tcsetpgrp(0, os.getpgrp())
1233
1234
exc_type, exc_val, exc_tb = exc_info
1235
self.debugger.reset()
1236
self.debugger.interaction(None, exc_tb)
1237
except KeyboardInterrupt:
1238
# Assume this is a *real* interrupt. We need to
1239
# escalate this to the master docbuilding process.
1240
if not self.options.serial:
1241
os.kill(os.getppid(), signal.SIGINT)
1242
raise
1243
finally:
1244
# Restore the foreground process group.
1245
if restore_tcpgrp is not None:
1246
os.tcsetpgrp(0, restore_tcpgrp)
1247
signal.signal(signal.SIGTTIN, signal.SIG_DFL)
1248
signal.signal(signal.SIGTTOU, signal.SIG_DFL)
1249
self._fakeout.start_spoofing()
1250
return returnval
1251
1252
def update_results(self, D):
1253
"""
1254
When returning results we pick out the results of interest
1255
since many attributes are not pickleable.
1256
1257
INPUT:
1258
1259
- ``D`` -- a dictionary to update with cputime and walltime
1260
1261
OUTPUT:
1262
1263
- the number of failures (or False if there is no failure attribute)
1264
1265
EXAMPLES::
1266
1267
sage: from sage.doctest.parsing import SageOutputChecker
1268
sage: from sage.doctest.forker import SageDocTestRunner
1269
sage: from sage.doctest.sources import FileDocTestSource, DictAsObject
1270
sage: from sage.doctest.control import DocTestDefaults; DD = DocTestDefaults()
1271
sage: from sage.env import SAGE_SRC
1272
sage: import doctest, sys, os
1273
sage: DTR = SageDocTestRunner(SageOutputChecker(), verbose=False, sage_options=DD, optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
1274
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','forker.py')
1275
sage: FDS = FileDocTestSource(filename,DD)
1276
sage: doctests, extras = FDS.create_doctests(globals())
1277
sage: from sage.doctest.util import Timer
1278
sage: T = Timer().start()
1279
sage: DTR.run(doctests[0])
1280
TestResults(failed=0, attempted=4)
1281
sage: T.stop().annotate(DTR)
1282
sage: D = DictAsObject({'cputime':[],'walltime':[],'err':None})
1283
sage: DTR.update_results(D)
1284
0
1285
sage: sorted(list(D.iteritems()))
1286
[('cputime', [...]), ('err', None), ('failures', 0), ('walltime', [...])]
1287
"""
1288
for key in ["cputime","walltime"]:
1289
if not D.has_key(key):
1290
D[key] = []
1291
if hasattr(self, key):
1292
D[key].append(self.__dict__[key])
1293
if hasattr(self, 'failures'):
1294
D['failures'] = self.failures
1295
return self.failures
1296
else:
1297
return False
1298
1299
def dummy_handler(sig, frame):
1300
"""
1301
Dummy signal handler for SIGCHLD (just to ensure the signal
1302
isn't ignored).
1303
1304
TESTS::
1305
1306
sage: import signal
1307
sage: from sage.doctest.forker import dummy_handler
1308
sage: _ = signal.signal(signal.SIGUSR1, dummy_handler)
1309
sage: os.kill(os.getpid(), signal.SIGUSR1)
1310
sage: signal.signal(signal.SIGUSR1, signal.SIG_DFL)
1311
<function dummy_handler at ...>
1312
"""
1313
pass
1314
1315
class DocTestDispatcher(SageObject):
1316
"""
1317
Creates parallel :class:`DocTestWorker` processes and dispatches
1318
doctesting tasks.
1319
"""
1320
def __init__(self, controller):
1321
"""
1322
INPUT:
1323
1324
- ``controller`` -- a :class:`sage.doctest.control.DocTestController` instance
1325
1326
EXAMPLES::
1327
1328
sage: from sage.doctest.control import DocTestController, DocTestDefaults
1329
sage: from sage.doctest.forker import DocTestDispatcher
1330
sage: DocTestDispatcher(DocTestController(DocTestDefaults(), []))
1331
<class 'sage.doctest.forker.DocTestDispatcher'>
1332
"""
1333
self.controller = controller
1334
init_sage()
1335
1336
def serial_dispatch(self):
1337
"""
1338
Run the doctests from the controller's specified sources in series.
1339
1340
There is no graceful handling for signals, no possibility of
1341
interrupting tests and no timeout.
1342
1343
EXAMPLES::
1344
1345
sage: from sage.doctest.control import DocTestController, DocTestDefaults
1346
sage: from sage.doctest.forker import DocTestDispatcher
1347
sage: from sage.doctest.reporting import DocTestReporter
1348
sage: from sage.doctest.util import Timer
1349
sage: from sage.env import SAGE_SRC
1350
sage: import os
1351
sage: homset = os.path.join(SAGE_SRC, 'sage', 'rings', 'homset.py')
1352
sage: ideal = os.path.join(SAGE_SRC, 'sage', 'rings', 'ideal.py')
1353
sage: DC = DocTestController(DocTestDefaults(), [homset, ideal])
1354
sage: DC.expand_files_into_sources()
1355
sage: DD = DocTestDispatcher(DC)
1356
sage: DR = DocTestReporter(DC)
1357
sage: DC.reporter = DR
1358
sage: DC.dispatcher = DD
1359
sage: DC.timer = Timer().start()
1360
sage: DD.serial_dispatch()
1361
sage -t .../rings/homset.py
1362
[... tests, ... s]
1363
sage -t .../rings/ideal.py
1364
[... tests, ... s]
1365
"""
1366
for source in self.controller.sources:
1367
head = self.controller.reporter.report_head(source)
1368
self.controller.log(head)
1369
outtmpfile = os.tmpfile()
1370
result = DocTestTask(source)(self.controller.options, outtmpfile)
1371
outtmpfile.seek(0)
1372
output = outtmpfile.read()
1373
self.controller.reporter.report(source, False, 0, result, output)
1374
1375
def parallel_dispatch(self):
1376
"""
1377
Run the doctests from the controller's specified sources in parallel.
1378
1379
This creates :class:`DocTestWorker` subprocesses, while the master
1380
process checks for timeouts and collects and displays the results.
1381
1382
EXAMPLES::
1383
1384
sage: from sage.doctest.control import DocTestController, DocTestDefaults
1385
sage: from sage.doctest.forker import DocTestDispatcher
1386
sage: from sage.doctest.reporting import DocTestReporter
1387
sage: from sage.doctest.util import Timer
1388
sage: from sage.env import SAGE_SRC
1389
sage: import os
1390
sage: crem = os.path.join(SAGE_SRC, 'sage', 'databases', 'cremona.py')
1391
sage: bigo = os.path.join(SAGE_SRC, 'sage', 'rings', 'big_oh.py')
1392
sage: DC = DocTestController(DocTestDefaults(), [crem, bigo])
1393
sage: DC.expand_files_into_sources()
1394
sage: DD = DocTestDispatcher(DC)
1395
sage: DR = DocTestReporter(DC)
1396
sage: DC.reporter = DR
1397
sage: DC.dispatcher = DD
1398
sage: DC.timer = Timer().start()
1399
sage: DD.parallel_dispatch()
1400
sage -t .../databases/cremona.py
1401
[... tests, ... s]
1402
sage -t .../rings/big_oh.py
1403
[... tests, ... s]
1404
"""
1405
opt = self.controller.options
1406
source_iter = iter(self.controller.sources)
1407
1408
# If timeout was 0, simply set a very long time
1409
if opt.timeout <= 0:
1410
opt.timeout = 2**60
1411
# Timeout we give a process to die (after it received a SIGHUP
1412
# signal). If it doesn't exit by itself in this many seconds, we
1413
# SIGKILL it. This is 5% of doctest timeout, with a maximum of
1414
# 10 minutes and a minimum of 20 seconds.
1415
die_timeout = opt.timeout * 0.05
1416
if die_timeout > 600:
1417
die_timeout = 600
1418
elif die_timeout < 20:
1419
die_timeout = 20
1420
1421
# List of alive DocTestWorkers (child processes). Workers which
1422
# are done but whose messages have not been read are also
1423
# considered alive.
1424
workers = []
1425
1426
# List of DocTestWorkers which have finished running but
1427
# whose results have not been reported yet.
1428
finished = []
1429
1430
# One particular worker that we are "following": we report the
1431
# messages while it's running. For other workers, we report the
1432
# messages if there is no followed worker.
1433
follow = None
1434
1435
# Install signal handler for SIGCHLD
1436
signal.signal(signal.SIGCHLD, dummy_handler)
1437
1438
# Logger
1439
log = self.controller.log
1440
1441
from sage.ext.pselect import PSelecter
1442
try:
1443
# Block SIGCHLD and SIGINT except during the pselect() call
1444
with PSelecter([signal.SIGCHLD, signal.SIGINT]) as sel:
1445
# Function to execute in the child process which exits
1446
# this "with" statement (which restores the signal mask)
1447
# and resets to SIGCHLD handler to default.
1448
# Since multiprocessing.Process is implemented using
1449
# fork(), signals would otherwise remain blocked in the
1450
# child process.
1451
def sel_exit():
1452
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
1453
sel.__exit__(None, None, None)
1454
1455
while True:
1456
# To avoid calling time.time() all the time while
1457
# checking for timeouts, we call it here, once per
1458
# loop. It's not a problem if this isn't very
1459
# precise, doctest timeouts don't need millisecond
1460
# precision.
1461
now = time.time()
1462
1463
# If there were any substantial changes in the state
1464
# (new worker started or finished worker reported),
1465
# restart this while loop instead of calling pselect().
1466
# This ensures internal consistency and a reasonably
1467
# accurate value for "now".
1468
restart = False
1469
1470
# Process all workers. Check for timeouts on active
1471
# workers and move finished/crashed workers to the
1472
# "finished" list.
1473
# Create a new list "new_workers" containing the active
1474
# workers (to avoid updating "workers" in place).
1475
new_workers = []
1476
for w in workers:
1477
if w.rmessages is not None or w.is_alive():
1478
if now >= w.deadline:
1479
# Timeout => (try to) kill the process
1480
# group (which normally includes
1481
# grandchildren) and close the message
1482
# pipe.
1483
# We don't report the timeout yet, we wait
1484
# until the process has actually died.
1485
w.kill()
1486
w.deadline = now + die_timeout
1487
if not w.is_alive():
1488
# Worker is done but we haven't read all
1489
# messages (possibly a grandchild still
1490
# has the messages pipe open).
1491
# Adjust deadline to read all messages:
1492
newdeadline = now + die_timeout
1493
if w.deadline > newdeadline:
1494
w.deadline = newdeadline
1495
new_workers.append(w)
1496
else:
1497
# Save the result and output of the worker
1498
# and close the associated file descriptors.
1499
# It is important to do this now. If we
1500
# would leave them open until we call
1501
# report(), parallel testing can easily fail
1502
# with a "Too many open files" error.
1503
w.save_result_output()
1504
finished.append(w)
1505
workers = new_workers
1506
1507
# Similarly, process finished workers.
1508
new_finished = []
1509
for w in finished:
1510
if follow is not None and follow is not w:
1511
# We are following a different worker, so
1512
# we cannot report now
1513
new_finished.append(w)
1514
else:
1515
# Report the completion of this worker
1516
if w.heading is not None:
1517
log(w.heading)
1518
log(w.messages, end="")
1519
self.controller.reporter.report(
1520
w.source,
1521
w.killed,
1522
w.exitcode,
1523
w.result,
1524
w.output,
1525
pid=w.pid)
1526
restart = True
1527
follow = None
1528
finished = new_finished
1529
1530
# Start new workers if possible
1531
while source_iter is not None and len(workers) < opt.nthreads:
1532
try:
1533
source = source_iter.next()
1534
except StopIteration:
1535
source_iter = None
1536
else:
1537
# Start a new worker.
1538
w = DocTestWorker(source, options=opt, funclist=[sel_exit])
1539
w.heading = self.controller.reporter.report_head(w.source)
1540
if opt.nthreads == 1:
1541
# With 1 process, the child prints
1542
# directly to stdout, so we need to
1543
# log the heading now.
1544
log(w.heading)
1545
w.heading = None
1546
w.start() # This might take some time
1547
w.deadline = time.time() + opt.timeout
1548
workers.append(w)
1549
restart = True
1550
1551
# Recompute state if needed
1552
if restart:
1553
continue
1554
1555
# We are finished if there are no DocTestWorkers left
1556
if len(workers) == 0:
1557
# If there are no active workers, we should have
1558
# reported all finished workers.
1559
assert len(finished) == 0
1560
break
1561
1562
# The master pselect() call
1563
rlist = [w.rmessages for w in workers if w.rmessages is not None]
1564
tmout = min(w.deadline for w in workers) - now
1565
if tmout > 5: # Wait at most 5 seconds
1566
tmout = 5
1567
rlist, _, _, _ = sel.pselect(rlist, timeout=tmout)
1568
1569
# Read messages
1570
for w in workers:
1571
if w.rmessages is not None and w.rmessages in rlist:
1572
w.read_messages()
1573
1574
# Find a worker to follow: if there is only one worker,
1575
# always follow it. Otherwise, take the worker with
1576
# the earliest deadline of all workers with messages.
1577
if follow is None:
1578
if len(workers) == 1:
1579
follow = workers[0]
1580
else:
1581
for w in workers:
1582
if w.messages:
1583
if follow is None or w.deadline < follow.deadline:
1584
follow = w
1585
1586
# Write messages of followed worker
1587
if follow is not None:
1588
if follow.heading is not None:
1589
log(follow.heading)
1590
follow.heading = None
1591
log(follow.messages, end="")
1592
follow.messages = ""
1593
finally:
1594
# Restore SIGCHLD handler (which is to ignore the signal)
1595
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
1596
1597
# Kill all remaining workers (in case we got interrupted)
1598
for w in workers:
1599
try:
1600
w.kill()
1601
except Exception:
1602
pass
1603
else:
1604
log("Killing test %s"%w.source.printpath)
1605
# Fork a child process with the specific purpose of
1606
# killing the remaining workers.
1607
if len(workers) > 0 and os.fork() == 0:
1608
# Block these signals
1609
with PSelecter([signal.SIGHUP, signal.SIGINT]):
1610
try:
1611
from time import sleep
1612
sleep(die_timeout)
1613
for w in workers:
1614
try:
1615
w.kill()
1616
except Exception:
1617
pass
1618
finally:
1619
os._exit(0)
1620
1621
# Hack to ensure multiprocessing leaves these processes
1622
# alone (in particular, it doesn't wait for them when we
1623
# exit).
1624
multiprocessing.current_process()._children = set()
1625
1626
def dispatch(self):
1627
"""
1628
Run the doctests for the controller's specified sources,
1629
by calling :meth:`parallel_dispatch` or :meth:`serial_dispatch`
1630
according to the ``--serial`` option.
1631
1632
EXAMPLES::
1633
1634
sage: from sage.doctest.control import DocTestController, DocTestDefaults
1635
sage: from sage.doctest.forker import DocTestDispatcher
1636
sage: from sage.doctest.reporting import DocTestReporter
1637
sage: from sage.doctest.util import Timer
1638
sage: from sage.env import SAGE_SRC
1639
sage: import os
1640
sage: freehom = os.path.join(SAGE_SRC, 'sage', 'modules', 'free_module_homspace.py')
1641
sage: bigo = os.path.join(SAGE_SRC, 'sage', 'rings', 'big_oh.py')
1642
sage: DC = DocTestController(DocTestDefaults(), [freehom, bigo])
1643
sage: DC.expand_files_into_sources()
1644
sage: DD = DocTestDispatcher(DC)
1645
sage: DR = DocTestReporter(DC)
1646
sage: DC.reporter = DR
1647
sage: DC.dispatcher = DD
1648
sage: DC.timer = Timer().start()
1649
sage: DD.dispatch()
1650
sage -t .../sage/modules/free_module_homspace.py
1651
[... tests, ... s]
1652
sage -t .../sage/rings/big_oh.py
1653
[... tests, ... s]
1654
"""
1655
if self.controller.options.serial:
1656
self.serial_dispatch()
1657
else:
1658
self.parallel_dispatch()
1659
1660
1661
class DocTestWorker(multiprocessing.Process):
1662
"""
1663
The DocTestWorker process runs one :class:`DocTestTask` for a given
1664
source. It returns messages about doctest failures (or all tests if
1665
verbose doctesting) though a pipe and returns results through a
1666
``multiprocessing.Queue`` instance (both these are created in the
1667
:meth:`start` method).
1668
1669
It runs the task in its own process-group, such that killing the
1670
process group kills this process together with its child processes.
1671
1672
The class has additional methods and attributes for bookkeeping
1673
by the master process. Except in :meth:`run`, nothing from this
1674
class should be accessed by the child process.
1675
1676
INPUT:
1677
1678
- ``source`` -- a :class:`DocTestSource` instance
1679
1680
- ``options`` -- an object representing doctest options.
1681
1682
- ``funclist`` -- a list of callables to be called at the start of
1683
the child process.
1684
1685
EXAMPLES::
1686
1687
sage: from sage.doctest.forker import DocTestWorker, DocTestTask
1688
sage: from sage.doctest.sources import FileDocTestSource
1689
sage: from sage.doctest.reporting import DocTestReporter
1690
sage: from sage.doctest.control import DocTestController, DocTestDefaults
1691
sage: from sage.env import SAGE_SRC
1692
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','util.py')
1693
sage: DD = DocTestDefaults()
1694
sage: FDS = FileDocTestSource(filename,DD)
1695
sage: W = DocTestWorker(FDS, DD)
1696
sage: W.start()
1697
sage: DC = DocTestController(DD, filename)
1698
sage: reporter = DocTestReporter(DC)
1699
sage: W.join() # Wait for worker to finish
1700
sage: result = W.result_queue.get()
1701
sage: reporter.report(FDS, False, W.exitcode, result, "")
1702
[... tests, ... s]
1703
"""
1704
def __init__(self, source, options, funclist=[]):
1705
"""
1706
Initialization.
1707
1708
TESTS::
1709
1710
sage: run_doctests(sage.rings.big_oh) # indirect doctest
1711
Running doctests with ID ...
1712
Doctesting 1 file.
1713
sage -t .../sage/rings/big_oh.py
1714
[... tests, ... s]
1715
----------------------------------------------------------------------
1716
All tests passed!
1717
----------------------------------------------------------------------
1718
Total time for all tests: ... seconds
1719
cpu time: ... seconds
1720
cumulative wall time: ... seconds
1721
"""
1722
multiprocessing.Process.__init__(self)
1723
1724
self.source = source
1725
self.options = options
1726
self.funclist = funclist
1727
1728
# Open pipe for messages. These are raw file descriptors,
1729
# not Python file objects!
1730
self.rmessages, self.wmessages = os.pipe()
1731
1732
# Create Queue for the result. Since we're running only one
1733
# doctest, this "queue" will contain only 1 element.
1734
self.result_queue = multiprocessing.Queue(1)
1735
1736
# Temporary file for stdout/stderr of the child process.
1737
# Normally, this isn't used in the master process except to
1738
# debug timeouts/crashes.
1739
self.outtmpfile = os.tmpfile()
1740
1741
# Create string for the master process to store the messages
1742
# (usually these are the doctest failures) of the child.
1743
# These messages are read through the pipe created above.
1744
self.messages = ""
1745
1746
# Has this worker been killed (because of a time out)?
1747
self.killed = False
1748
1749
def run(self):
1750
"""
1751
Runs the :class:`DocTestTask` under its own PGID.
1752
1753
TESTS::
1754
1755
sage: run_doctests(sage.symbolic.units) # indirect doctest
1756
Running doctests with ID ...
1757
Doctesting 1 file.
1758
sage -t .../sage/symbolic/units.py
1759
[... tests, ... s]
1760
----------------------------------------------------------------------
1761
All tests passed!
1762
----------------------------------------------------------------------
1763
Total time for all tests: ... seconds
1764
cpu time: ... seconds
1765
cumulative wall time: ... seconds
1766
"""
1767
os.setpgid(os.getpid(), os.getpid())
1768
1769
# Run functions
1770
for f in self.funclist:
1771
f()
1772
1773
# Write one byte to the pipe to signal to the master process
1774
# that we have started properly.
1775
os.write(self.wmessages, "X")
1776
1777
task = DocTestTask(self.source)
1778
1779
# Ensure the Python stdin is the actual stdin
1780
# (multiprocessing redirects this).
1781
# We will do a more proper redirect of stdin in SageSpoofInOut.
1782
try:
1783
sys.stdin = os.fdopen(0, "r")
1784
except OSError:
1785
# We failed to open stdin for reading, this might happen
1786
# for example when running under "nohup" (Trac #14307).
1787
# Simply redirect stdin from /dev/null and try again.
1788
with open(os.devnull) as f:
1789
os.dup2(f.fileno(), 0)
1790
sys.stdin = os.fdopen(0, "r")
1791
1792
# Close the reading end of the pipe (only the master should
1793
# read from the pipe) and open the writing end.
1794
os.close(self.rmessages)
1795
msgpipe = os.fdopen(self.wmessages, "w")
1796
try:
1797
# If we have only one process, we print directly to stdout
1798
# without using the message pipe. This is mainly needed to
1799
# support the --debug option.
1800
if self.options.nthreads == 1:
1801
msgpipe.close()
1802
msgpipe = None
1803
1804
task(self.options, self.outtmpfile, msgpipe, self.result_queue)
1805
finally:
1806
if msgpipe is not None:
1807
msgpipe.close()
1808
1809
def start(self):
1810
"""
1811
Start the worker and close the writing end of the message pipe.
1812
1813
TESTS::
1814
1815
sage: from sage.doctest.forker import DocTestWorker, DocTestTask
1816
sage: from sage.doctest.sources import FileDocTestSource
1817
sage: from sage.doctest.reporting import DocTestReporter
1818
sage: from sage.doctest.control import DocTestController, DocTestDefaults
1819
sage: from sage.env import SAGE_SRC
1820
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','util.py')
1821
sage: DD = DocTestDefaults()
1822
sage: FDS = FileDocTestSource(filename,DD)
1823
sage: W = DocTestWorker(FDS, DD)
1824
sage: W.start()
1825
sage: try:
1826
....: os.fstat(W.wmessages)
1827
....: except OSError:
1828
....: print "Write end of pipe successfully closed"
1829
Write end of pipe successfully closed
1830
sage: W.join() # Wait for worker to finish
1831
"""
1832
super(DocTestWorker, self).start()
1833
1834
# Close the writing end of the pipe (only the child should
1835
# write to the pipe).
1836
os.close(self.wmessages)
1837
1838
# Read one byte from the pipe as a sign that the child process
1839
# has properly started (to avoid race conditions). In particular,
1840
# it will have its process group changed.
1841
os.read(self.rmessages, 1)
1842
1843
def read_messages(self):
1844
"""
1845
In the master process, read from the pipe and store the data
1846
read in the ``messages`` attribute.
1847
1848
.. NOTE::
1849
1850
This function may need to be called multiple times in
1851
order to read all of the messages.
1852
1853
EXAMPLES::
1854
1855
sage: from sage.doctest.forker import DocTestWorker, DocTestTask
1856
sage: from sage.doctest.sources import FileDocTestSource
1857
sage: from sage.doctest.reporting import DocTestReporter
1858
sage: from sage.doctest.control import DocTestController, DocTestDefaults
1859
sage: from sage.env import SAGE_SRC
1860
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','util.py')
1861
sage: DD = DocTestDefaults(verbose=True,nthreads=2)
1862
sage: FDS = FileDocTestSource(filename,DD)
1863
sage: W = DocTestWorker(FDS, DD)
1864
sage: W.start()
1865
sage: while W.rmessages is not None:
1866
....: W.read_messages()
1867
sage: W.join()
1868
sage: len(W.messages) > 0
1869
True
1870
"""
1871
# It's absolutely important to execute only one read() system
1872
# call, more might block. Assuming that we used pselect()
1873
# correctly, one read() will not block.
1874
if self.rmessages is not None:
1875
s = os.read(self.rmessages, 4096)
1876
self.messages += s
1877
if len(s) == 0: # EOF
1878
os.close(self.rmessages)
1879
self.rmessages = None
1880
1881
def save_result_output(self):
1882
"""
1883
Annotate ``self`` with ``self.result`` (the result read through
1884
the ``result_queue`` and with ``self.output``, the complete
1885
contents of ``self.outtmpfile``. Then close the Queue and
1886
``self.outtmpfile``.
1887
1888
EXAMPLES::
1889
1890
sage: from sage.doctest.forker import DocTestWorker, DocTestTask
1891
sage: from sage.doctest.sources import FileDocTestSource
1892
sage: from sage.doctest.reporting import DocTestReporter
1893
sage: from sage.doctest.control import DocTestController, DocTestDefaults
1894
sage: from sage.env import SAGE_SRC
1895
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','util.py')
1896
sage: DD = DocTestDefaults()
1897
sage: FDS = FileDocTestSource(filename,DD)
1898
sage: W = DocTestWorker(FDS, DD)
1899
sage: W.start()
1900
sage: W.join()
1901
sage: W.save_result_output()
1902
sage: sorted(W.result[1].keys())
1903
['cputime', 'err', 'failures', 'optionals', 'walltime']
1904
sage: len(W.output) > 0
1905
True
1906
"""
1907
from Queue import Empty
1908
try:
1909
self.result = self.result_queue.get(block=False)
1910
except Empty:
1911
self.result = (0, DictAsObject(dict(err='noresult')))
1912
del self.result_queue
1913
1914
self.outtmpfile.seek(0)
1915
self.output = self.outtmpfile.read()
1916
del self.outtmpfile
1917
1918
def kill(self):
1919
"""
1920
Kill this worker. The first time this is called, use
1921
``SIGHUP``. Subsequent times, use ``SIGKILL``. Also close the
1922
message pipe if it was still open.
1923
1924
EXAMPLES::
1925
1926
sage: import time
1927
sage: from sage.doctest.forker import DocTestWorker, DocTestTask
1928
sage: from sage.doctest.sources import FileDocTestSource
1929
sage: from sage.doctest.reporting import DocTestReporter
1930
sage: from sage.doctest.control import DocTestController, DocTestDefaults
1931
sage: from sage.env import SAGE_SRC
1932
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','tests','99seconds.rst')
1933
sage: DD = DocTestDefaults()
1934
sage: FDS = FileDocTestSource(filename,DD)
1935
1936
We set up the worker to start by blocking ``SIGHUP``, such that
1937
killing will fail initially::
1938
1939
sage: from sage.ext.pselect import PSelecter
1940
sage: import signal
1941
sage: def block_hup():
1942
....: # We never __exit__()
1943
....: PSelecter([signal.SIGHUP]).__enter__()
1944
sage: W = DocTestWorker(FDS, DD, [block_hup])
1945
sage: W.start()
1946
sage: W.killed
1947
False
1948
sage: W.kill()
1949
sage: W.killed
1950
True
1951
sage: time.sleep(0.2) # Worker doesn't die
1952
sage: W.kill() # Worker dies now
1953
sage: time.sleep(0.2)
1954
sage: W.is_alive()
1955
False
1956
"""
1957
if self.rmessages is not None:
1958
os.close(self.rmessages)
1959
self.rmessages = None
1960
if not self.killed:
1961
self.killed = True
1962
os.killpg(self.pid, signal.SIGHUP)
1963
else:
1964
os.killpg(self.pid, signal.SIGKILL)
1965
1966
1967
class DocTestTask(object):
1968
"""
1969
This class encapsulates the tests from a single source.
1970
1971
This class does not insulate from problems in the source
1972
(e.g. entering an infinite loop or causing a segfault), that has to
1973
be dealt with at a higher level.
1974
1975
INPUT:
1976
1977
- ``source`` -- a :class:`sage.doctest.sources.DocTestSource` instance.
1978
1979
- ``verbose`` -- boolean, controls reporting of progress by :class:`doctest.DocTestRunner`.
1980
1981
EXAMPLES::
1982
1983
sage: from sage.doctest.forker import DocTestTask
1984
sage: from sage.doctest.sources import FileDocTestSource
1985
sage: from sage.doctest.control import DocTestDefaults, DocTestController
1986
sage: from sage.env import SAGE_SRC
1987
sage: import os
1988
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py')
1989
sage: DD = DocTestDefaults()
1990
sage: FDS = FileDocTestSource(filename,DD)
1991
sage: DTT = DocTestTask(FDS)
1992
sage: DC = DocTestController(DD,[filename])
1993
sage: ntests, results = DTT(options=DD)
1994
sage: ntests >= 300 or ntests
1995
True
1996
sage: sorted(results.keys())
1997
['cputime', 'err', 'failures', 'optionals', 'walltime']
1998
"""
1999
def __init__(self, source):
2000
"""
2001
Initialization.
2002
2003
TESTS::
2004
2005
sage: from sage.doctest.forker import DocTestTask
2006
sage: from sage.doctest.sources import FileDocTestSource
2007
sage: from sage.doctest.control import DocTestDefaults
2008
sage: from sage.env import SAGE_SRC
2009
sage: import os
2010
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','sources.py')
2011
sage: FDS = FileDocTestSource(filename,DocTestDefaults())
2012
sage: DocTestTask(FDS)
2013
<sage.doctest.forker.DocTestTask object at ...>
2014
"""
2015
self.source = source
2016
2017
def __call__(self, options, outtmpfile=None, msgfile=None, result_queue=None):
2018
"""
2019
Calling the task does the actual work of running the doctests.
2020
2021
INPUT:
2022
2023
- ``options`` -- an object representing doctest options.
2024
2025
- ``outtmpfile`` -- a seekable file that's used by the doctest
2026
runner to redirect stdout and stderr.
2027
2028
- ``msgfile`` -- a file or pipe to send doctest messages about
2029
doctest failures (or all tests in verbose mode).
2030
2031
- ``result_queue`` -- an instance of :class:`multiprocessing.Queue`
2032
to store the doctest result. For testing, this can also be None.
2033
2034
OUPUT:
2035
2036
- ``(doctests, result_dict)`` where ``doctests`` is the number of
2037
doctests and and ``result_dict`` is a dictionary annotated with
2038
timings and error information.
2039
2040
- Also put ``(doctests, result_dict)`` onto the ``result_queue``
2041
if the latter isn't None.
2042
2043
EXAMPLES::
2044
2045
sage: from sage.doctest.forker import DocTestTask
2046
sage: from sage.doctest.sources import FileDocTestSource
2047
sage: from sage.doctest.control import DocTestDefaults, DocTestController
2048
sage: from sage.env import SAGE_SRC
2049
sage: import os
2050
sage: filename = os.path.join(SAGE_SRC,'sage','doctest','parsing.py')
2051
sage: DD = DocTestDefaults()
2052
sage: FDS = FileDocTestSource(filename,DD)
2053
sage: DTT = DocTestTask(FDS)
2054
sage: DC = DocTestController(DD, [filename])
2055
sage: ntests, runner = DTT(options=DD)
2056
sage: runner.failures
2057
0
2058
sage: ntests >= 200 or ntests
2059
True
2060
"""
2061
# Store all existing atexit() functions and clear them,
2062
# so we know that all newly registered functions come from
2063
# doctests.
2064
import atexit
2065
saved_exithandlers = atexit._exithandlers
2066
atexit._exithandlers = []
2067
2068
result = None
2069
try:
2070
file = self.source.path
2071
basename = self.source.basename
2072
runner = SageDocTestRunner(
2073
SageOutputChecker(),
2074
verbose=options.verbose,
2075
outtmpfile=outtmpfile,
2076
msgfile=msgfile,
2077
sage_options=options,
2078
optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS)
2079
runner.basename = basename
2080
N = options.file_iterations
2081
results = DictAsObject(dict(walltime=[],cputime=[],err=None))
2082
for it in range(N):
2083
# Make the right set of globals available to doctests
2084
if basename.startswith("sagenb."):
2085
import sage.all_notebook as sage_all
2086
else:
2087
import sage.all_cmdline as sage_all
2088
sage_namespace = RecordingDict(sage_all.__dict__)
2089
sage_namespace['__name__'] = '__main__'
2090
sage_namespace['__package__'] = None
2091
doctests, extras = self.source.create_doctests(sage_namespace)
2092
timer = Timer().start()
2093
2094
for test in doctests:
2095
runner.run(test)
2096
runner.filename = file
2097
failed, tried = runner.summarize(options.verbose)
2098
timer.stop().annotate(runner)
2099
if runner.update_results(results):
2100
break
2101
if extras['tab']:
2102
results.err = 'tab'
2103
results.tab_linenos = extras['tab']
2104
results.optionals = extras['optionals']
2105
# We subtract 1 to remove the sig_on_count() tests
2106
result = (sum([max(0,len(test.examples) - 1) for test in doctests]), results)
2107
2108
# multiprocessing.Process instances don't run exit
2109
# functions, so we run the functions added by doctests
2110
# now manually and restore the old exit functions.
2111
atexit._run_exitfuncs()
2112
atexit._exithandlers = saved_exithandlers
2113
except BaseException:
2114
exc_info = sys.exc_info()
2115
tb = "".join(traceback.format_exception(*exc_info))
2116
result = (0, DictAsObject(dict(err=exc_info[0], tb=tb)))
2117
2118
if result_queue is not None:
2119
result_queue.put(result, False)
2120
return result
2121
2122