@@ -105,7 +105,23 @@ def _test():
105105from io import StringIO , IncrementalNewlineDecoder
106106from collections import namedtuple
107107
108- TestResults = namedtuple ('TestResults' , 'failed attempted' )
108+
109+ class TestResults (namedtuple ('TestResults' , 'failed attempted' )):
110+ def __new__ (cls , failed , attempted , * , skipped = 0 ):
111+ results = super ().__new__ (cls , failed , attempted )
112+ results .skipped = skipped
113+ return results
114+
115+ def __repr__ (self ):
116+ if self .skipped :
117+ return (f'TestResults(failed={ self .failed } , '
118+ f'attempted={ self .attempted } , '
119+ f'skipped={ self .skipped } )' )
120+ else :
121+ # Leave the repr() unchanged for backward compatibility
122+ # if skipped is zero
123+ return super ().__repr__ ()
124+
109125
110126# There are 4 basic classes:
111127# - Example: a <source, want> pair, plus an intra-docstring line number.
@@ -1150,8 +1166,7 @@ class DocTestRunner:
11501166 """
11511167 A class used to run DocTest test cases, and accumulate statistics.
11521168 The `run` method is used to process a single DocTest case. It
1153- returns a tuple `(f, t)`, where `t` is the number of test cases
1154- tried, and `f` is the number of test cases that failed.
1169+ returns a TestResults instance.
11551170
11561171 >>> tests = DocTestFinder().find(_TestClass)
11571172 >>> runner = DocTestRunner(verbose=False)
@@ -1164,8 +1179,8 @@ class DocTestRunner:
11641179 _TestClass.square -> TestResults(failed=0, attempted=1)
11651180
11661181 The `summarize` method prints a summary of all the test cases that
1167- have been run by the runner, and returns an aggregated `(f, t)`
1168- tuple :
1182+ have been run by the runner, and returns an aggregated TestResults
1183+ instance :
11691184
11701185 >>> runner.summarize(verbose=1)
11711186 4 items passed all tests:
@@ -1178,13 +1193,15 @@ class DocTestRunner:
11781193 Test passed.
11791194 TestResults(failed=0, attempted=7)
11801195
1181- The aggregated number of tried examples and failed examples is
1182- also available via the `tries` and `failures ` attributes:
1196+ The aggregated number of tried examples and failed examples is also
1197+ available via the `tries`, `failures` and `skips ` attributes:
11831198
11841199 >>> runner.tries
11851200 7
11861201 >>> runner.failures
11871202 0
1203+ >>> runner.skips
1204+ 0
11881205
11891206 The comparison between expected outputs and actual outputs is done
11901207 by an `OutputChecker`. This comparison may be customized with a
@@ -1233,7 +1250,8 @@ def __init__(self, checker=None, verbose=None, optionflags=0):
12331250 # Keep track of the examples we've run.
12341251 self .tries = 0
12351252 self .failures = 0
1236- self ._name2ft = {}
1253+ self .skips = 0
1254+ self ._stats = {}
12371255
12381256 # Create a fake output target for capturing doctest output.
12391257 self ._fakeout = _SpoofOut ()
@@ -1302,13 +1320,11 @@ def __run(self, test, compileflags, out):
13021320 Run the examples in `test`. Write the outcome of each example
13031321 with one of the `DocTestRunner.report_*` methods, using the
13041322 writer function `out`. `compileflags` is the set of compiler
1305- flags that should be used to execute examples. Return a tuple
1306- `(f, t)`, where `t` is the number of examples tried, and `f`
1307- is the number of examples that failed. The examples are run
1308- in the namespace `test.globs`.
1323+ flags that should be used to execute examples. Return a TestResults
1324+ instance. The examples are run in the namespace `test.globs`.
13091325 """
1310- # Keep track of the number of failures and tries .
1311- failures = tries = 0
1326+ # Keep track of the number of failed, attempted, skipped examples .
1327+ failures = attempted = skips = 0
13121328
13131329 # Save the option flags (since option directives can be used
13141330 # to modify them).
@@ -1320,6 +1336,7 @@ def __run(self, test, compileflags, out):
13201336
13211337 # Process each example.
13221338 for examplenum , example in enumerate (test .examples ):
1339+ attempted += 1
13231340
13241341 # If REPORT_ONLY_FIRST_FAILURE is set, then suppress
13251342 # reporting after the first failure.
@@ -1337,10 +1354,10 @@ def __run(self, test, compileflags, out):
13371354
13381355 # If 'SKIP' is set, then skip this example.
13391356 if self .optionflags & SKIP :
1357+ skips += 1
13401358 continue
13411359
13421360 # Record that we started this example.
1343- tries += 1
13441361 if not quiet :
13451362 self .report_start (out , test , example )
13461363
@@ -1418,19 +1435,22 @@ def __run(self, test, compileflags, out):
14181435 # Restore the option flags (in case they were modified)
14191436 self .optionflags = original_optionflags
14201437
1421- # Record and return the number of failures and tries .
1422- self .__record_outcome (test , failures , tries )
1423- return TestResults (failures , tries )
1438+ # Record and return the number of failures and attempted .
1439+ self .__record_outcome (test , failures , attempted , skips )
1440+ return TestResults (failures , attempted , skipped = skips )
14241441
1425- def __record_outcome (self , test , f , t ):
1442+ def __record_outcome (self , test , failures , tries , skips ):
14261443 """
1427- Record the fact that the given DocTest (`test`) generated `f `
1428- failures out of `t ` tried examples.
1444+ Record the fact that the given DocTest (`test`) generated `failures `
1445+ failures out of `tries ` tried examples.
14291446 """
1430- f2 , t2 = self ._name2ft .get (test .name , (0 ,0 ))
1431- self ._name2ft [test .name ] = (f + f2 , t + t2 )
1432- self .failures += f
1433- self .tries += t
1447+ failures2 , tries2 , skips2 = self ._stats .get (test .name , (0 , 0 , 0 ))
1448+ self ._stats [test .name ] = (failures + failures2 ,
1449+ tries + tries2 ,
1450+ skips + skips2 )
1451+ self .failures += failures
1452+ self .tries += tries
1453+ self .skips += skips
14341454
14351455 __LINECACHE_FILENAME_RE = re .compile (r'<doctest '
14361456 r'(?P<name>.+)'
@@ -1519,9 +1539,7 @@ def out(s):
15191539 def summarize (self , verbose = None ):
15201540 """
15211541 Print a summary of all the test cases that have been run by
1522- this DocTestRunner, and return a tuple `(f, t)`, where `f` is
1523- the total number of failed examples, and `t` is the total
1524- number of tried examples.
1542+ this DocTestRunner, and return a TestResults instance.
15251543
15261544 The optional `verbose` argument controls how detailed the
15271545 summary is. If the verbosity is not specified, then the
@@ -1532,59 +1550,61 @@ def summarize(self, verbose=None):
15321550 notests = []
15331551 passed = []
15341552 failed = []
1535- totalt = totalf = 0
1536- for x in self ._name2ft .items ():
1537- name , (f , t ) = x
1538- assert f <= t
1539- totalt += t
1540- totalf += f
1541- if t == 0 :
1553+ total_tries = total_failures = total_skips = 0
1554+ for item in self ._stats .items ():
1555+ name , (failures , tries , skips ) = item
1556+ assert failures <= tries
1557+ total_tries += tries
1558+ total_failures += failures
1559+ total_skips += skips
1560+ if tries == 0 :
15421561 notests .append (name )
1543- elif f == 0 :
1544- passed .append ( (name , t ) )
1562+ elif failures == 0 :
1563+ passed .append ((name , tries ) )
15451564 else :
1546- failed .append (x )
1565+ failed .append (item )
15471566 if verbose :
15481567 if notests :
1549- print (len (notests ), " items had no tests:" )
1568+ print (f" { len (notests )} items had no tests:" )
15501569 notests .sort ()
1551- for thing in notests :
1552- print (" " , thing )
1570+ for name in notests :
1571+ print (f " { name } " )
15531572 if passed :
1554- print (len (passed ), " items passed all tests:" )
1573+ print (f" { len (passed )} items passed all tests:" )
15551574 passed .sort ()
1556- for thing , count in passed :
1557- print (" %3d tests in %s" % ( count , thing ) )
1575+ for name , count in passed :
1576+ print (f" { count :3d } tests in { name } " )
15581577 if failed :
15591578 print (self .DIVIDER )
1560- print (len (failed ), " items had failures:" )
1579+ print (f" { len (failed )} items had failures:" )
15611580 failed .sort ()
1562- for thing , (f , t ) in failed :
1563- print (" %3d of %3d in %s" % ( f , t , thing ) )
1581+ for name , (failures , tries , skips ) in failed :
1582+ print (f" { failures :3d } of { tries :3d } in { name } " )
15641583 if verbose :
1565- print (totalt , "tests in" , len (self ._name2ft ), "items." )
1566- print (totalt - totalf , "passed and" , totalf , "failed." )
1567- if totalf :
1568- print ("***Test Failed***" , totalf , "failures." )
1584+ print (f"{ total_tries } tests in { len (self ._stats )} items." )
1585+ print (f"{ total_tries - total_failures } passed and { total_failures } failed." )
1586+ if total_failures :
1587+ msg = f"***Test Failed*** { total_failures } failures"
1588+ if total_skips :
1589+ msg = f"{ msg } and { total_skips } skipped tests"
1590+ print (f"{ msg } ." )
15691591 elif verbose :
15701592 print ("Test passed." )
1571- return TestResults (totalf , totalt )
1593+ return TestResults (total_failures , total_tries , skipped = total_skips )
15721594
15731595 #/////////////////////////////////////////////////////////////////
15741596 # Backward compatibility cruft to maintain doctest.master.
15751597 #/////////////////////////////////////////////////////////////////
15761598 def merge (self , other ):
1577- d = self ._name2ft
1578- for name , (f , t ) in other ._name2ft .items ():
1599+ d = self ._stats
1600+ for name , (failures , tries , skips ) in other ._stats .items ():
15791601 if name in d :
1580- # Don't print here by default, since doing
1581- # so breaks some of the buildbots
1582- #print("*** DocTestRunner.merge: '" + name + "' in both" \
1583- # " testers; summing outcomes.")
1584- f2 , t2 = d [name ]
1585- f = f + f2
1586- t = t + t2
1587- d [name ] = f , t
1602+ failures2 , tries2 , skips2 = d [name ]
1603+ failures = failures + failures2
1604+ tries = tries + tries2
1605+ skips = skips + skips2
1606+ d [name ] = (failures , tries , skips )
1607+
15881608
15891609class OutputChecker :
15901610 """
@@ -1984,7 +2004,8 @@ class doctest.Tester, then merges the results into (or creates)
19842004 else :
19852005 master .merge (runner )
19862006
1987- return TestResults (runner .failures , runner .tries )
2007+ return TestResults (runner .failures , runner .tries , skipped = runner .skips )
2008+
19882009
19892010def testfile (filename , module_relative = True , name = None , package = None ,
19902011 globs = None , verbose = None , report = True , optionflags = 0 ,
@@ -2107,7 +2128,8 @@ class doctest.Tester, then merges the results into (or creates)
21072128 else :
21082129 master .merge (runner )
21092130
2110- return TestResults (runner .failures , runner .tries )
2131+ return TestResults (runner .failures , runner .tries , skipped = runner .skips )
2132+
21112133
21122134def run_docstring_examples (f , globs , verbose = False , name = "NoName" ,
21132135 compileflags = None , optionflags = 0 ):
0 commit comments