1
1
import sys
2
- import inspect
3
2
import unittest
3
+ from itertools import groupby
4
4
5
5
# Use timeit.default_timer for Python 2 compatibility.
6
6
# default_timer is time.perf_counter on 3.3+
@@ -14,58 +14,72 @@ def __init__(self, stream=None, group_by_module=False):
14
14
if stream is None :
15
15
stream = sys .stdout
16
16
self .stream = _WritelnDecorator (stream )
17
- self .result = CodewarsTestResult (self .stream )
18
17
self .group_by_module = group_by_module
18
+ self .results = []
19
19
20
20
def run (self , test ):
21
21
if isinstance (test , unittest .TestSuite ):
22
- self ._run_each_test_cases (test )
23
- return self .result
22
+ self ._run_modules (_to_tree (_flatten (test )))
24
23
else :
25
- return self ._run_case (test )
26
-
27
- def _run_each_test_cases (self , suite ):
28
- if not isinstance (suite , unittest .TestSuite ):
29
- return
30
-
31
- for test in suite :
32
- if _is_test_module (test ):
33
- name = ""
34
- if self .group_by_module :
35
- case = _get_test_case (test )
36
- name = _get_module_name (case )
37
- if name :
38
- self .stream .writeln (_group (name ))
24
+ self ._run_case (test )
25
+ return self ._make_result ()
26
+
27
+ def _make_result (self ):
28
+ accum = unittest .TestResult ()
29
+ for result in self .results :
30
+ accum .failures .extend (result .failures )
31
+ accum .errors .extend (result .errors )
32
+ accum .testsRun += result .testsRun
33
+ return accum
34
+
35
+ def _run_modules (self , modules ):
36
+ for mod in modules :
37
+ name = ""
38
+ if self .group_by_module :
39
+ name = mod .group_name
40
+ # Don't group on ImportError
41
+ if name == "unittest.loader" :
42
+ name = ""
43
+ if name :
44
+ self .stream .writeln (_group (name ))
39
45
40
- startTime = perf_counter ()
41
- for cases in test :
42
- self ._run_cases (cases )
46
+ startTime = perf_counter ()
47
+ for cases in mod :
48
+ self ._run_cases (cases )
43
49
44
- if name :
45
- self .stream .writeln (_completedin (startTime , perf_counter ()))
46
- else :
47
- self ._run_each_test_cases (test )
50
+ if name :
51
+ self .stream .writeln (_completedin (startTime , perf_counter ()))
48
52
49
53
def _run_cases (self , test ):
50
- case = next (iter (test ), None )
51
- if not case :
52
- return self .result
53
-
54
- self .stream .writeln (_group (_get_class_name (case )))
54
+ name = test .group_name
55
+ # Don't group when errored before running tests, e.g., ImportError
56
+ if name == "_FailedTest" :
57
+ name = ""
58
+ if name :
59
+ self .stream .writeln (_group (name ))
55
60
startTime = perf_counter ()
61
+ result = CodewarsTestResult (self .stream )
56
62
try :
57
- test (self . result )
63
+ test (result )
58
64
finally :
59
65
pass
60
- self .stream .writeln (_completedin (startTime , perf_counter ()))
61
- return self .result
66
+ if name :
67
+ self .stream .writeln (_completedin (startTime , perf_counter ()))
68
+ self .results .append (result )
62
69
63
70
def _run_case (self , test ):
71
+ result = CodewarsTestResult (self .stream )
64
72
try :
65
- test (self . result )
73
+ test (result )
66
74
finally :
67
75
pass
68
- return self .result
76
+ self .results .append (result )
77
+
78
+
79
+ class _NamedTestSuite (unittest .TestSuite ):
80
+ def __init__ (self , tests = (), group_name = None ):
81
+ super (_NamedTestSuite , self ).__init__ (tests )
82
+ self .group_name = group_name
69
83
70
84
71
85
def _group (name ):
@@ -76,46 +90,37 @@ def _completedin(start, end):
76
90
return "\n <COMPLETEDIN::>{:.4f}" .format (1000 * (end - start ))
77
91
78
92
79
- # True if test suite directly contains a test case
80
- def _is_test_cases (suite ):
81
- return isinstance (suite , unittest .TestSuite ) and any (
82
- isinstance (t , unittest .TestCase ) for t in suite
83
- )
84
-
85
-
86
- # True if test suite directly contains test cases
87
- def _is_test_module (suite ):
88
- return isinstance (suite , unittest .TestSuite ) and any (
89
- _is_test_cases (t ) for t in suite
90
- )
93
+ # Flatten nested TestSuite by collecting all test cases.
94
+ def _flatten (suites ):
95
+ tests = []
96
+ for test in suites :
97
+ if isinstance (test , unittest .TestSuite ):
98
+ tests .extend (_flatten (test ))
99
+ else :
100
+ tests .append (test )
101
+ return tests
91
102
92
103
93
- # Get first test case from a TestSuite created from a test module to find module name
94
- def _get_test_case (suite ):
95
- if not isinstance (suite , unittest .TestSuite ):
96
- return None
97
- for test in suite :
98
- if not isinstance (test , unittest .TestSuite ):
99
- continue
100
- for t in test :
101
- if isinstance (t , unittest .TestCase ):
102
- return t
103
- return None
104
+ # Group by module name and then by class name
105
+ def _to_tree (suite ):
106
+ tree = unittest .TestSuite ()
107
+ for k , ms in groupby (suite , _module_name ):
108
+ sub_trees = _NamedTestSuite (group_name = k )
109
+ for c , cs in groupby (ms , _class_name ):
110
+ sub_trees .addTest (_NamedTestSuite (tests = cs , group_name = c ))
111
+ tree .addTest (sub_trees )
112
+ return tree
104
113
105
114
106
- def _get_class_name (x ):
107
- cls = x if inspect .isclass (x ) else x .__class__
108
- return cls .__name__
115
+ def _module_name (x ):
116
+ return x .__class__ .__module__
109
117
110
118
111
- def _get_module_name (x ):
112
- cls = x if inspect .isclass (x ) else x .__class__
113
- mod = cls .__module__
114
- if mod is None or mod == str .__class__ .__module__ :
115
- return ""
116
- return mod
119
+ def _class_name (x ):
120
+ return x .__class__ .__name__
117
121
118
122
123
+ # https://github.com/python/cpython/blob/289f1f80ee87a4baf4567a86b3425fb3bf73291d/Lib/unittest/runner.py#L13
119
124
class _WritelnDecorator (object ):
120
125
"""Used to decorate file-like objects with a handy 'writeln' method"""
121
126
0 commit comments