@@ -40,7 +40,7 @@ import sys
40
40
from argparse import ArgumentParser, FileType
41
41
from pathlib import Path
42
42
43
- from sage.doctest.control import skipfile
43
+ from sage.doctest.control import DocTestDefaults, DocTestController
44
44
from sage.doctest.parsing import parse_file_optional_tags, parse_optional_tags, unparse_optional_tags, update_optional_tags
45
45
from sage.env import SAGE_ROOT
46
46
from sage.features import PythonModule
@@ -307,17 +307,21 @@ def process_block(block, src_in_lines, file_optional_tags):
307
307
308
308
309
309
# set input and output files
310
- if len(args.filename) == 2 and not args.overwrite and not args.no_overwrite:
311
- inputs, outputs = [args.filename[0]], [args.filename[1]]
312
- print("sage-fixdoctests: When passing two filenames, the second one is taken as an output filename; "
313
- "this is deprecated. To pass two input filenames, use the option --overwrite.")
314
- elif args.no_overwrite:
315
- inputs, outputs = args.filename, [input + ".fixed" for input in args.filename]
316
- else:
317
- inputs = outputs = args.filename
310
+ def output_filename(filename):
311
+ if len(args.filename) == 2 and not args.overwrite and not args.no_overwrite:
312
+ if args.filename[0] == filename:
313
+ print("sage-fixdoctests: When passing two filenames, the second one is taken as an output filename; "
314
+ "this is deprecated. To pass two input filenames, use the option --overwrite.")
315
+ return args.filename[1]
316
+ return filename + ".fixed"
317
+ if args.no_overwrite:
318
+ return filename + ".fixed"
319
+ return filename
318
320
319
321
# Test the doctester, putting the output of the test into sage's temporary directory
320
- if not args.no_test:
322
+ if args.no_test:
323
+ doc_out = ''
324
+ else:
321
325
executable = f'{os.path.relpath(args.venv)}/bin/sage' if args.venv else 'sage'
322
326
environment_args = f'--environment {args.environment} ' if args.environment != runtest_default_environment else ''
323
327
long_args = f'--long ' if args.long else ''
@@ -331,38 +335,54 @@ if not args.no_test:
331
335
if status := os.waitstatus_to_exitcode(os.system(f'{cmdline} > {shlex.quote(doc_file)}')):
332
336
print(f'Doctester exited with error status {status}')
333
337
sys.exit(status)
338
+ # Run the doctester, putting the output of the test into sage's temporary directory
339
+ input_args = " ".join(shlex.quote(f) for f in args.filename)
340
+ cmdline = f'{shlex.quote(executable)} -t -p {environment_args}{long_args}{probe_args}{lib_args}{input_args}'
341
+ print(f'Running "{cmdline}"')
342
+ os.system(f'{cmdline} > {shlex.quote(doc_file)}')
334
343
335
- for input, output in zip(inputs, outputs):
336
- if (skipfile_result := skipfile(input, True, log=print)) is True:
337
- continue
338
-
339
- if args.no_test:
340
- doc_out = ''
341
- else:
342
- # Run the doctester, putting the output of the test into sage's temporary directory
343
- cmdline = f'{shlex.quote(executable)} -t {environment_args}{long_args}{probe_args}{lib_args}{shlex.quote(input)}'
344
- print(f'Running "{cmdline}"')
345
- os.system(f'{cmdline} > {shlex.quote(doc_file)}')
346
-
347
- with open(doc_file, 'r') as doc:
348
- doc_out = doc.read()
344
+ with open(doc_file, 'r') as doc:
345
+ doc_out = doc.read()
349
346
350
347
# echo control messages
351
348
for m in re.finditer('^Skipping .*', doc_out, re.MULTILINE):
352
349
print('sage-runtests: ' + m.group(0))
353
- break
354
- else:
355
- sep = "**********************************************************************\n"
356
- doctests = doc_out.split(sep)
350
+
351
+ sep = "**********************************************************************\n"
352
+ doctests = doc_out.split(sep)
353
+
354
+ seen = set()
355
+
356
+ def block_filename(block):
357
+ if not (m := re.match('File "([^"]*)", line ([0-9]+), in ', block)):
358
+ return None
359
+ return m.group(1)
360
+
361
+ def expanded_filename_args():
362
+ DD = DocTestDefaults(optional='all')
363
+ DC = DocTestController(DD, args.filename)
364
+ DC.add_files()
365
+ DC.expand_files_into_sources()
366
+ for source in DC.sources:
367
+ yield source.path
368
+
369
+ def process_grouped_blocks(grouped_iterator):
370
+
371
+ for input, blocks in grouped_iterator:
372
+
373
+ if not input: # Blocks of noise
374
+ continue
375
+ if input in seen:
376
+ continue
377
+ seen.add(input)
357
378
358
379
with open(input, 'r') as test_file:
359
380
src_in = test_file.read()
360
381
src_in_lines = src_in.splitlines()
361
382
shallow_copy_of_src_in_lines = list(src_in_lines)
362
-
363
383
file_optional_tags = set(parse_file_optional_tags(enumerate(src_in_lines)))
364
384
365
- for block in doctests :
385
+ for block in blocks :
366
386
process_block(block, src_in_lines, file_optional_tags)
367
387
368
388
# Now source line numbers do not matter any more, and lines can be real lines again
@@ -394,6 +414,7 @@ for input, output in zip(inputs, outputs):
394
414
persistent_optional_tags = {}
395
415
396
416
if src_in_lines != shallow_copy_of_src_in_lines:
417
+ output = output_filename(input)
397
418
with open(output, 'w') as test_output:
398
419
for line in src_in_lines:
399
420
if line is None:
@@ -411,3 +432,7 @@ for input, output in zip(inputs, outputs):
411
432
subprocess.call(['git', '--no-pager', 'diff', relative], cwd=SAGE_ROOT)
412
433
else:
413
434
print(f"No fixes made in '{input}'")
435
+
436
+ process_grouped_blocks(
437
+ itertools.chain(itertools.groupby(doctests, block_filename),
438
+ ((filename, []) for filename in expanded_filename_args())))
0 commit comments