11import importlib
22import os
33import sys
4- import re
54import tempfile
65import time
76import traceback
87
9- from typing import List , Tuple , Any , Callable , Union , cast , Optional
10- from types import TracebackType
8+ from typing import List , Tuple , Any , Callable , Union , cast , Optional , Iterable
9+ from types import TracebackType , MethodType
1110
1211
1312# TODO remove global state
@@ -105,30 +104,23 @@ def fail() -> None:
105104 raise AssertionFailure ()
106105
107106
108- class TestCase :
109- def __init__ (self , name : str , suite : 'Optional[Suite]' = None ,
110- func : Optional [Callable [[], None ]] = None ) -> None :
111- self .func = func
107+ class BaseTestCase :
108+ """Common base class for _MyUnitTestCase and DataDrivenTestCase.
109+
110+ Handles temporary folder creation and deletion.
111+ """
112+ def __init__ (self , name : str ) -> None :
112113 self .name = name
113- self .suite = suite
114114 self .old_cwd = None # type: Optional[str]
115115 self .tmpdir = None # type: Optional[tempfile.TemporaryDirectory[str]]
116116
117- def run (self ) -> None :
118- if self .func :
119- self .func ()
120-
121- def set_up (self ) -> None :
117+ def setup (self ) -> None :
122118 self .old_cwd = os .getcwd ()
123119 self .tmpdir = tempfile .TemporaryDirectory (prefix = 'mypy-test-' )
124120 os .chdir (self .tmpdir .name )
125121 os .mkdir ('tmp' )
126- if self .suite :
127- self .suite .set_up ()
128122
129- def tear_down (self ) -> None :
130- if self .suite :
131- self .suite .tear_down ()
123+ def teardown (self ) -> None :
132124 assert self .old_cwd is not None and self .tmpdir is not None , \
133125 "test was not properly set up"
134126 os .chdir (self .old_cwd )
@@ -140,35 +132,51 @@ def tear_down(self) -> None:
140132 self .tmpdir = None
141133
142134
135+ class _MyUnitTestCase (BaseTestCase ):
136+ """A concrete, myunit-specific test case, a wrapper around a method to run."""
137+
138+ def __init__ (self , name : str , suite : 'Suite' , run : Callable [[], None ]) -> None :
139+ super ().__init__ (name )
140+ self .run = run
141+ self .suite = suite
142+
143+ def setup (self ) -> None :
144+ super ().setup ()
145+ self .suite .setup ()
146+
147+ def teardown (self ) -> None :
148+ self .suite .teardown () # No-op
149+ super ().teardown ()
150+
151+
143152class Suite :
144- def __init__ (self ) -> None :
145- self .prefix = typename (type (self )) + '.'
146- # Each test case is either a TestCase object or (str, function).
147- self ._test_cases = [] # type: List[Any]
148- self .init ()
153+ """Abstract class for myunit test suites - node in the tree whose leaves are _MyUnitTestCases.
149154
150- def set_up (self ) -> None :
151- pass
155+ The children `cases` are looked up during __init__, looking for attributes named test_*
156+ they are either no-arg methods or of a pair (name, Suite).
157+ """
152158
153- def tear_down (self ) -> None :
154- pass
159+ cases = None # type: Iterable[Union[_MyUnitTestCase, Tuple[str, Suite]]]
155160
156- def init (self ) -> None :
161+ def __init__ (self ) -> None :
162+ self .prefix = typename (type (self )) + '.'
163+ self .cases = []
157164 for m in dir (self ):
158- if m .startswith ('test ' ):
165+ if m .startswith ('test_ ' ):
159166 t = getattr (self , m )
160167 if isinstance (t , Suite ):
161- self .add_test ((m + '.' , t ))
168+ self .cases . append ((m + '.' , t ))
162169 else :
163- self .add_test (TestCase (m , self , getattr (self , m )))
170+ assert isinstance (t , MethodType )
171+ self .cases .append (_MyUnitTestCase (m , self , t ))
164172
165- def add_test (self , test : Union [TestCase ,
166- Tuple [str , Callable [[], None ]],
167- Tuple [str , 'Suite' ]]) -> None :
168- self ._test_cases .append (test )
173+ def setup (self ) -> None :
174+ """Set up fixtures"""
175+ pass
169176
170- def cases (self ) -> List [Any ]:
171- return self ._test_cases [:]
177+ def teardown (self ) -> None :
178+ # This method is not overridden in practice
179+ pass
172180
173181 def skip (self ) -> None :
174182 raise SkipTestCaseException ()
@@ -250,10 +258,11 @@ def main(args: Optional[List[str]] = None) -> None:
250258 sys .exit (1 )
251259
252260
253- def run_test_recursive (test : Any , num_total : int , num_fail : int , num_skip : int ,
261+ def run_test_recursive (test : Union [_MyUnitTestCase , Tuple [str , Suite ], ListSuite ],
262+ num_total : int , num_fail : int , num_skip : int ,
254263 prefix : str , depth : int ) -> Tuple [int , int , int ]:
255- """The first argument may be TestCase , Suite or (str, Suite)."""
256- if isinstance (test , TestCase ):
264+ """The first argument may be _MyUnitTestCase , Suite or (str, Suite)."""
265+ if isinstance (test , _MyUnitTestCase ):
257266 name = prefix + test .name
258267 for pattern in patterns :
259268 if match_pattern (name , pattern ):
@@ -275,7 +284,7 @@ def run_test_recursive(test: Any, num_total: int, num_fail: int, num_skip: int,
275284 suite = test
276285 suite_prefix = test .prefix
277286
278- for stest in suite .cases () :
287+ for stest in suite .cases :
279288 new_prefix = prefix
280289 if depth > 0 :
281290 new_prefix = prefix + suite_prefix
@@ -284,22 +293,22 @@ def run_test_recursive(test: Any, num_total: int, num_fail: int, num_skip: int,
284293 return num_total , num_fail , num_skip
285294
286295
287- def run_single_test (name : str , test : Any ) -> Tuple [bool , bool ]:
296+ def run_single_test (name : str , test : _MyUnitTestCase ) -> Tuple [bool , bool ]:
288297 if is_verbose :
289298 sys .stderr .write (name )
290299 sys .stderr .flush ()
291300
292301 time0 = time .time ()
293- test .set_up () # FIX: check exceptions
294- exc_traceback = None # type: Any
302+ test .setup () # FIX: check exceptions
303+ exc_traceback = None # type: Optional[TracebackType]
295304 try :
296305 test .run ()
297306 except BaseException as e :
298307 if isinstance (e , KeyboardInterrupt ):
299308 raise
300309 exc_type , exc_value , exc_traceback = sys .exc_info ()
301310 finally :
302- test .tear_down ()
311+ test .teardown ()
303312 times .append ((time .time () - time0 , name ))
304313
305314 if exc_traceback :
0 commit comments