Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit975081b

Browse files
gh-117225: Add color to doctest output (#117583)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
1 parentf6e5cc6 commit975081b

File tree

5 files changed

+92
-15
lines changed

5 files changed

+92
-15
lines changed

‎Lib/doctest.py

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ def _test():
104104
importunittest
105105
fromioimportStringIO,IncrementalNewlineDecoder
106106
fromcollectionsimportnamedtuple
107+
fromtracebackimport_ANSIColors,_can_colorize
107108

108109

109110
classTestResults(namedtuple('TestResults','failed attempted')):
@@ -1179,6 +1180,9 @@ class DocTestRunner:
11791180
The `run` method is used to process a single DocTest case. It
11801181
returns a TestResults instance.
11811182
1183+
>>> save_colorize = traceback._COLORIZE
1184+
>>> traceback._COLORIZE = False
1185+
11821186
>>> tests = DocTestFinder().find(_TestClass)
11831187
>>> runner = DocTestRunner(verbose=False)
11841188
>>> tests.sort(key = lambda test: test.name)
@@ -1229,6 +1233,8 @@ class DocTestRunner:
12291233
can be also customized by subclassing DocTestRunner, and
12301234
overriding the methods `report_start`, `report_success`,
12311235
`report_unexpected_exception`, and `report_failure`.
1236+
1237+
>>> traceback._COLORIZE = save_colorize
12321238
"""
12331239
# This divider string is used to separate failure messages, and to
12341240
# separate sections of the summary.
@@ -1307,7 +1313,10 @@ def report_unexpected_exception(self, out, test, example, exc_info):
13071313
'Exception raised:\n'+_indent(_exception_traceback(exc_info)))
13081314

13091315
def_failure_header(self,test,example):
1310-
out= [self.DIVIDER]
1316+
red,reset= (
1317+
(_ANSIColors.RED,_ANSIColors.RESET)if_can_colorize()else ("","")
1318+
)
1319+
out= [f"{red}{self.DIVIDER}{reset}"]
13111320
iftest.filename:
13121321
iftest.linenoisnotNoneandexample.linenoisnotNone:
13131322
lineno=test.lineno+example.lineno+1
@@ -1592,6 +1601,21 @@ def summarize(self, verbose=None):
15921601
else:
15931602
failed.append((name, (failures,tries,skips)))
15941603

1604+
if_can_colorize():
1605+
bold_green=_ANSIColors.BOLD_GREEN
1606+
bold_red=_ANSIColors.BOLD_RED
1607+
green=_ANSIColors.GREEN
1608+
red=_ANSIColors.RED
1609+
reset=_ANSIColors.RESET
1610+
yellow=_ANSIColors.YELLOW
1611+
else:
1612+
bold_green=""
1613+
bold_red=""
1614+
green=""
1615+
red=""
1616+
reset=""
1617+
yellow=""
1618+
15951619
ifverbose:
15961620
ifnotests:
15971621
print(f"{_n_items(notests)} had no tests:")
@@ -1600,13 +1624,13 @@ def summarize(self, verbose=None):
16001624
print(f"{name}")
16011625

16021626
ifpassed:
1603-
print(f"{_n_items(passed)} passed all tests:")
1627+
print(f"{green}{_n_items(passed)} passed all tests:{reset}")
16041628
forname,countinsorted(passed):
16051629
s=""ifcount==1else"s"
1606-
print(f"{count:3d} test{s} in{name}")
1630+
print(f"{green}{count:3d} test{s} in{name}{reset}")
16071631

16081632
iffailed:
1609-
print(self.DIVIDER)
1633+
print(f"{red}{self.DIVIDER}{reset}")
16101634
print(f"{_n_items(failed)} had failures:")
16111635
forname, (failures,tries,skips)insorted(failed):
16121636
print(f"{failures:3d} of{tries:3d} in{name}")
@@ -1615,18 +1639,21 @@ def summarize(self, verbose=None):
16151639
s=""iftotal_tries==1else"s"
16161640
print(f"{total_tries} test{s} in{_n_items(self._stats)}.")
16171641

1618-
and_f=f" and{total_failures} failed"iftotal_failureselse""
1619-
print(f"{total_tries-total_failures} passed{and_f}.")
1642+
and_f= (
1643+
f" and{red}{total_failures} failed{reset}"
1644+
iftotal_failureselse""
1645+
)
1646+
print(f"{green}{total_tries-total_failures} passed{reset}{and_f}.")
16201647

16211648
iftotal_failures:
16221649
s=""iftotal_failures==1else"s"
1623-
msg=f"***Test Failed***{total_failures} failure{s}"
1650+
msg=f"{bold_red}***Test Failed***{total_failures} failure{s}{reset}"
16241651
iftotal_skips:
16251652
s=""iftotal_skips==1else"s"
1626-
msg=f"{msg} and{total_skips} skipped test{s}"
1653+
msg=f"{msg} and{yellow}{total_skips} skipped test{s}{reset}"
16271654
print(f"{msg}.")
16281655
elifverbose:
1629-
print("Test passed.")
1656+
print(f"{bold_green}Test passed.{reset}")
16301657

16311658
returnTestResults(total_failures,total_tries,skipped=total_skips)
16321659

@@ -1644,7 +1671,7 @@ def merge(self, other):
16441671
d[name]= (failures,tries,skips)
16451672

16461673

1647-
def_n_items(items:list)->str:
1674+
def_n_items(items:list|dict)->str:
16481675
"""
16491676
Helper to pluralise the number of items in a list.
16501677
"""
@@ -1655,7 +1682,7 @@ def _n_items(items: list) -> str:
16551682

16561683
classOutputChecker:
16571684
"""
1658-
A class used to checkthewhether the actual output from a doctest
1685+
A class used to check whether the actual output from a doctest
16591686
example matches the expected output. `OutputChecker` defines two
16601687
methods: `check_output`, which compares a given pair of outputs,
16611688
and returns true if they match; and `output_difference`, which

‎Lib/test/support/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"Error","TestFailed","TestDidNotRun","ResourceDenied",
2727
# io
2828
"record_original_stdout","get_original_stdout","captured_stdout",
29-
"captured_stdin","captured_stderr",
29+
"captured_stdin","captured_stderr","captured_output",
3030
# unittest
3131
"is_resource_enabled","requires","requires_freebsd_version",
3232
"requires_gil_enabled","requires_linux_version","requires_mac_ver",

‎Lib/test/test_doctest/test_doctest.py

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
importtempfile
1717
importtypes
1818
importcontextlib
19+
importtraceback
1920

2021

2122
defdoctest_skip_if(condition):
@@ -470,7 +471,7 @@ def basics(): r"""
470471
>>> tests = finder.find(sample_func)
471472
472473
>>> print(tests) # doctest: +ELLIPSIS
473-
[<DocTest sample_func from test_doctest.py:37 (1 example)>]
474+
[<DocTest sample_func from test_doctest.py:38 (1 example)>]
474475
475476
The exact name depends on how test_doctest was invoked, so allow for
476477
leading path components.
@@ -892,6 +893,9 @@ def basics(): r"""
892893
DocTestRunner is used to run DocTest test cases, and to accumulate
893894
statistics. Here's a simple DocTest case we can use:
894895
896+
>>> save_colorize = traceback._COLORIZE
897+
>>> traceback._COLORIZE = False
898+
895899
>>> def f(x):
896900
... '''
897901
... >>> x = 12
@@ -946,6 +950,8 @@ def basics(): r"""
946950
6
947951
ok
948952
TestResults(failed=1, attempted=3)
953+
954+
>>> traceback._COLORIZE = save_colorize
949955
"""
950956
defverbose_flag():r"""
951957
The `verbose` flag makes the test runner generate more detailed
@@ -1021,6 +1027,9 @@ def exceptions(): r"""
10211027
lines between the first line and the type/value may be omitted or
10221028
replaced with any other string:
10231029
1030+
>>> save_colorize = traceback._COLORIZE
1031+
>>> traceback._COLORIZE = False
1032+
10241033
>>> def f(x):
10251034
... '''
10261035
... >>> x = 12
@@ -1251,6 +1260,8 @@ def exceptions(): r"""
12511260
...
12521261
ZeroDivisionError: integer division or modulo by zero
12531262
TestResults(failed=1, attempted=1)
1263+
1264+
>>> traceback._COLORIZE = save_colorize
12541265
"""
12551266
defdisplayhook():r"""
12561267
Test that changing sys.displayhook doesn't matter for doctest.
@@ -1292,6 +1303,9 @@ def optionflags(): r"""
12921303
The DONT_ACCEPT_TRUE_FOR_1 flag disables matches between True/False
12931304
and 1/0:
12941305
1306+
>>> save_colorize = traceback._COLORIZE
1307+
>>> traceback._COLORIZE = False
1308+
12951309
>>> def f(x):
12961310
... '>>> True\n1\n'
12971311
@@ -1711,6 +1725,7 @@ def optionflags(): r"""
17111725
17121726
Clean up.
17131727
>>> del doctest.OPTIONFLAGS_BY_NAME[unlikely]
1728+
>>> traceback._COLORIZE = save_colorize
17141729
17151730
"""
17161731

@@ -1721,6 +1736,9 @@ def option_directives(): r"""
17211736
single example. To turn an option on for an example, follow that
17221737
example with a comment of the form ``# doctest: +OPTION``:
17231738
1739+
>>> save_colorize = traceback._COLORIZE
1740+
>>> traceback._COLORIZE = False
1741+
17241742
>>> def f(x): r'''
17251743
... >>> print(list(range(10))) # should fail: no ellipsis
17261744
... [0, 1, ..., 9]
@@ -1928,6 +1946,8 @@ def option_directives(): r"""
19281946
>>> test = doctest.DocTestParser().get_doctest(s, {}, 's', 's.py', 0)
19291947
Traceback (most recent call last):
19301948
ValueError: line 0 of the doctest for s has an option directive on a line with no example: '# doctest: +ELLIPSIS'
1949+
1950+
>>> traceback._COLORIZE = save_colorize
19311951
"""
19321952

19331953
deftest_testsource():r"""
@@ -2011,6 +2031,9 @@ def test_pdb_set_trace():
20112031
with a version that restores stdout. This is necessary for you to
20122032
see debugger output.
20132033
2034+
>>> save_colorize = traceback._COLORIZE
2035+
>>> traceback._COLORIZE = False
2036+
20142037
>>> doc = '''
20152038
... >>> x = 42
20162039
... >>> raise Exception('clé')
@@ -2065,7 +2088,7 @@ def test_pdb_set_trace():
20652088
... finally:
20662089
... sys.stdin = real_stdin
20672090
--Return--
2068-
> <doctest test.test_doctest.test_doctest.test_pdb_set_trace[7]>(3)calls_set_trace()->None
2091+
> <doctest test.test_doctest.test_doctest.test_pdb_set_trace[9]>(3)calls_set_trace()->None
20692092
-> import pdb; pdb.set_trace()
20702093
(Pdb) print(y)
20712094
2
@@ -2133,6 +2156,8 @@ def test_pdb_set_trace():
21332156
Got:
21342157
9
21352158
TestResults(failed=1, attempted=3)
2159+
2160+
>>> traceback._COLORIZE = save_colorize
21362161
"""
21372162

21382163
deftest_pdb_set_trace_nested():
@@ -2667,7 +2692,10 @@ def test_testfile(): r"""
26672692
called with the name of a file, which is taken to be relative to the
26682693
calling module. The return value is (#failures, #tests).
26692694
2670-
We don't want `-v` in sys.argv for these tests.
2695+
We don't want color or `-v` in sys.argv for these tests.
2696+
2697+
>>> save_colorize = traceback._COLORIZE
2698+
>>> traceback._COLORIZE = False
26712699
26722700
>>> save_argv = sys.argv
26732701
>>> if '-v' in sys.argv:
@@ -2835,6 +2863,7 @@ def test_testfile(): r"""
28352863
TestResults(failed=0, attempted=2)
28362864
>>> doctest.master = None # Reset master.
28372865
>>> sys.argv = save_argv
2866+
>>> traceback._COLORIZE = save_colorize
28382867
"""
28392868

28402869
classTestImporter(importlib.abc.MetaPathFinder,importlib.abc.ResourceLoader):
@@ -2972,6 +3001,9 @@ def test_testmod(): r"""
29723001
deftest_unicode():"""
29733002
Check doctest with a non-ascii filename:
29743003
3004+
>>> save_colorize = traceback._COLORIZE
3005+
>>> traceback._COLORIZE = False
3006+
29753007
>>> doc = '''
29763008
... >>> raise Exception('clé')
29773009
... '''
@@ -2997,8 +3029,11 @@ def test_unicode(): """
29973029
raise Exception('clé')
29983030
Exception: clé
29993031
TestResults(failed=1, attempted=1)
3032+
3033+
>>> traceback._COLORIZE = save_colorize
30003034
"""
30013035

3036+
30023037
@doctest_skip_if(notsupport.has_subprocess_support)
30033038
deftest_CLI():r"""
30043039
The doctest module can be used to run doctests against an arbitrary file.
@@ -3290,6 +3325,9 @@ def test_run_doctestsuite_multiple_times():
32903325

32913326
deftest_exception_with_note(note):
32923327
"""
3328+
>>> save_colorize = traceback._COLORIZE
3329+
>>> traceback._COLORIZE = False
3330+
32933331
>>> test_exception_with_note('Note')
32943332
Traceback (most recent call last):
32953333
...
@@ -3339,6 +3377,8 @@ def test_exception_with_note(note):
33393377
ValueError: message
33403378
note
33413379
TestResults(failed=1, attempted=...)
3380+
3381+
>>> traceback._COLORIZE = save_colorize
33423382
"""
33433383
exc=ValueError('Text')
33443384
exc.add_note(note)
@@ -3419,6 +3459,9 @@ def test_syntax_error_subclass_from_stdlib():
34193459

34203460
deftest_syntax_error_with_incorrect_expected_note():
34213461
"""
3462+
>>> save_colorize = traceback._COLORIZE
3463+
>>> traceback._COLORIZE = False
3464+
34223465
>>> def f(x):
34233466
... r'''
34243467
... >>> exc = SyntaxError("error", ("x.py", 23, None, "bad syntax"))
@@ -3447,6 +3490,8 @@ def test_syntax_error_with_incorrect_expected_note():
34473490
note1
34483491
note2
34493492
TestResults(failed=1, attempted=...)
3493+
3494+
>>> traceback._COLORIZE = save_colorize
34503495
"""
34513496

34523497

‎Lib/traceback.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,8 +448,12 @@ class _ANSIColors:
448448
BOLD_RED='\x1b[1;31m'
449449
MAGENTA='\x1b[35m'
450450
BOLD_MAGENTA='\x1b[1;35m'
451+
GREEN="\x1b[32m"
452+
BOLD_GREEN="\x1b[1;32m"
451453
GREY='\x1b[90m'
452454
RESET='\x1b[0m'
455+
YELLOW="\x1b[33m"
456+
453457

454458
classStackSummary(list):
455459
"""A list of FrameSummary objects, representing a stack of frames."""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add colour to doctest output. Patch by Hugo van Kemenade.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp