28
28
FileOperation = Union [UpdateFile , DeleteFile ]
29
29
30
30
31
- def parse_test_cases (
32
- path : str ,
33
- base_path : str = '.' ,
34
- optional_out : bool = False ,
35
- native_sep : bool = False ) -> List ['DataDrivenTestCase' ]:
36
- """Parse a file with test case descriptions.
37
-
38
- Return an array of test cases.
31
+ def parse_test_cases (parent : 'DataSuiteCollector' , suite : 'DataSuite' ,
32
+ path : str ) -> Iterator ['DataDrivenTestCase' ]:
33
+ """Parse a single file from suite with test case descriptions.
39
34
40
35
NB: this function and DataDrivenTestCase were shared between the
41
36
myunit and pytest codepaths -- if something looks redundant,
42
37
that's likely the reason.
43
38
"""
44
- if native_sep :
39
+ base_path = suite .base_path
40
+ if suite .native_sep :
45
41
join = os .path .join
46
42
else :
47
43
join = posixpath .join # type: ignore
@@ -51,7 +47,6 @@ def parse_test_cases(
51
47
for i in range (len (lst )):
52
48
lst [i ] = lst [i ].rstrip ('\n ' )
53
49
p = parse_test_data (lst , path )
54
- out = [] # type: List[DataDrivenTestCase]
55
50
56
51
# Process the parsed items. Each item has a header of form [id args],
57
52
# optionally followed by lines of text.
@@ -143,7 +138,7 @@ def parse_test_cases(
143
138
assert passnum > 1
144
139
output = p [i ].data
145
140
output = [expand_variables (line ) for line in output ]
146
- if native_sep and os .path .sep == '\\ ' :
141
+ if suite . native_sep and os .path .sep == '\\ ' :
147
142
output = [fix_win_path (line ) for line in output ]
148
143
tcout2 [passnum ] = output
149
144
ok = True
@@ -167,7 +162,7 @@ def parse_test_cases(
167
162
('Stale modules after pass {} must be a subset of rechecked '
168
163
'modules ({}:{})' ).format (passnum , path , p [i0 ].line ))
169
164
170
- if optional_out :
165
+ if suite . optional_out :
171
166
ok = True
172
167
173
168
if ok :
@@ -178,24 +173,24 @@ def parse_test_cases(
178
173
lastline = p [i ].line if i < len (p ) else p [i - 1 ].line + 9999
179
174
arg0 = p [i0 ].arg
180
175
assert arg0 is not None
181
- tc = DataDrivenTestCase (arg0 , input , tcout , tcout2 , path ,
182
- p [i0 ].line , lastline ,
183
- files , output_files , stale_modules ,
184
- rechecked_modules , deleted_paths , native_sep ,
185
- triggered )
186
- out .append (tc )
176
+ case_name = add_test_name_suffix (arg0 , suite .test_name_suffix )
177
+ skip = arg0 .endswith ('-skip' )
178
+ if skip :
179
+ case_name = case_name [:- len ('-skip' )]
180
+ yield DataDrivenTestCase (case_name , parent , skip , input , tcout , tcout2 , path ,
181
+ p [i0 ].line , lastline ,
182
+ files , output_files , stale_modules ,
183
+ rechecked_modules , deleted_paths , suite .native_sep ,
184
+ triggered )
187
185
if not ok :
188
186
raise ValueError (
189
187
'{}, line {}: Error in test case description' .format (
190
188
path , p [i0 ].line ))
191
189
192
- return out
193
-
194
190
195
- class DataDrivenTestCase :
196
- """Holds parsed data and handles directory setup and teardown for MypyDataCase ."""
191
+ class DataDrivenTestCase ( pytest . Item ): # type: ignore # inheriting from Any
192
+ """Holds parsed data-driven test cases, and handles directory setup and teardown."""
197
193
198
- # TODO: rename to ParsedTestCase or merge with MypyDataCase (yet avoid multiple inheritance)
199
194
# TODO: only create files on setup, not during parsing
200
195
201
196
input = None # type: List[str]
@@ -215,6 +210,8 @@ class DataDrivenTestCase:
215
210
216
211
def __init__ (self ,
217
212
name : str ,
213
+ parent : 'DataSuiteCollector' ,
214
+ skip : bool ,
218
215
input : List [str ],
219
216
output : List [str ],
220
217
output2 : Dict [int , List [str ]],
@@ -229,7 +226,9 @@ def __init__(self,
229
226
native_sep : bool = False ,
230
227
triggered : Optional [List [str ]] = None ,
231
228
) -> None :
232
- self .name = name
229
+
230
+ super ().__init__ (name , parent )
231
+ self .skip = skip
233
232
self .old_cwd = None # type: Optional[str]
234
233
self .tmpdir = None # type: Optional[tempfile.TemporaryDirectory[str]]
235
234
self .input = input
@@ -246,6 +245,14 @@ def __init__(self,
246
245
self .native_sep = native_sep
247
246
self .triggered = triggered or []
248
247
248
+ def runtest (self ) -> None :
249
+ if self .skip :
250
+ pytest .skip ()
251
+ suite = self .parent .obj ()
252
+ suite .update_data = self .config .getoption ('--update-data' , False )
253
+ suite .setup ()
254
+ suite .run_case (self )
255
+
249
256
def setup (self ) -> None :
250
257
self .old_cwd = os .getcwd ()
251
258
self .tmpdir = tempfile .TemporaryDirectory (prefix = 'mypy-test-' )
@@ -336,6 +343,22 @@ def teardown(self) -> None:
336
343
self .old_cwd = None
337
344
self .tmpdir = None
338
345
346
+ def reportinfo (self ) -> Tuple [str , int , str ]:
347
+ return self .file , self .line , self .name
348
+
349
+ def repr_failure (self , excinfo : Any ) -> str :
350
+ if excinfo .errisinstance (SystemExit ):
351
+ # We assume that before doing exit() (which raises SystemExit) we've printed
352
+ # enough context about what happened so that a stack trace is not useful.
353
+ # In particular, uncaught exceptions during semantic analysis or type checking
354
+ # call exit() and they already print out a stack trace.
355
+ excrepr = excinfo .exconly ()
356
+ else :
357
+ self .parent ._prunetraceback (excinfo )
358
+ excrepr = excinfo .getrepr (style = 'short' )
359
+
360
+ return "data: {}:{}:\n {}" .format (self .file , self .line , excrepr )
361
+
339
362
def find_steps (self ) -> List [List [FileOperation ]]:
340
363
"""Return a list of descriptions of file operations for each incremental step.
341
364
@@ -560,7 +583,7 @@ def fix_cobertura_filename(line: str) -> str:
560
583
561
584
562
585
# This function name is special to pytest. See
563
- # http ://doc .pytest.org/en/latest/writing_plugins .html#initialization-command-line-and-configuration -hooks
586
+ # https ://docs .pytest.org/en/latest/reference .html#initialization-hooks
564
587
def pytest_addoption (parser : Any ) -> None :
565
588
group = parser .getgroup ('mypy' )
566
589
group .addoption ('--update-data' , action = 'store_true' , default = False ,
@@ -580,26 +603,20 @@ def pytest_pycollect_makeitem(collector: Any, name: str,
580
603
# Only classes derived from DataSuite contain test cases, not the DataSuite class itself
581
604
if issubclass (obj , DataSuite ) and obj is not DataSuite :
582
605
# Non-None result means this obj is a test case.
583
- # The collect method of the returned MypyDataSuite instance will be called later,
606
+ # The collect method of the returned DataSuiteCollector instance will be called later,
584
607
# with self.obj being obj.
585
- return MypyDataSuite (name , parent = collector )
608
+ return DataSuiteCollector (name , parent = collector )
586
609
return None
587
610
588
611
589
- class MypyDataSuite (pytest .Class ): # type: ignore # inheriting from Any
612
+ class DataSuiteCollector (pytest .Class ): # type: ignore # inheriting from Any
590
613
def collect (self ) -> Iterator [pytest .Item ]: # type: ignore
591
614
"""Called by pytest on each of the object returned from pytest_pycollect_makeitem"""
592
615
593
616
# obj is the object for which pytest_pycollect_makeitem returned self.
594
617
suite = self .obj # type: DataSuite
595
618
for f in suite .files :
596
- for case in parse_test_cases (os .path .join (suite .data_prefix , f ),
597
- base_path = suite .base_path ,
598
- optional_out = suite .optional_out ,
599
- native_sep = suite .native_sep ):
600
- if suite .filter (case ):
601
- case .name = add_test_name_suffix (case .name , suite .test_name_suffix )
602
- yield MypyDataCase (case .name , self , case )
619
+ yield from parse_test_cases (self , suite , os .path .join (suite .data_prefix , f ))
603
620
604
621
605
622
def add_test_name_suffix (name : str , suffix : str ) -> str :
@@ -628,47 +645,6 @@ def has_stable_flags(testcase: DataDrivenTestCase) -> bool:
628
645
return True
629
646
630
647
631
- class MypyDataCase (pytest .Item ): # type: ignore # inheriting from Any
632
- def __init__ (self , name : str , parent : MypyDataSuite , case : DataDrivenTestCase ) -> None :
633
- self .skip = False
634
- if name .endswith ('-skip' ):
635
- self .skip = True
636
- name = name [:- len ('-skip' )]
637
-
638
- super ().__init__ (name , parent )
639
- self .case = case
640
-
641
- def runtest (self ) -> None :
642
- if self .skip :
643
- pytest .skip ()
644
- suite = self .parent .obj ()
645
- suite .update_data = self .config .getoption ('--update-data' , False )
646
- suite .setup ()
647
- suite .run_case (self .case )
648
-
649
- def setup (self ) -> None :
650
- self .case .setup ()
651
-
652
- def teardown (self ) -> None :
653
- self .case .teardown ()
654
-
655
- def reportinfo (self ) -> Tuple [str , int , str ]:
656
- return self .case .file , self .case .line , self .case .name
657
-
658
- def repr_failure (self , excinfo : Any ) -> str :
659
- if excinfo .errisinstance (SystemExit ):
660
- # We assume that before doing exit() (which raises SystemExit) we've printed
661
- # enough context about what happened so that a stack trace is not useful.
662
- # In particular, uncaught exceptions during semantic analysis or type checking
663
- # call exit() and they already print out a stack trace.
664
- excrepr = excinfo .exconly ()
665
- else :
666
- self .parent ._prunetraceback (excinfo )
667
- excrepr = excinfo .getrepr (style = 'short' )
668
-
669
- return "data: {}:{}:\n {}" .format (self .case .file , self .case .line , excrepr )
670
-
671
-
672
648
class DataSuite :
673
649
# option fields - class variables
674
650
files = None # type: List[str]
@@ -690,7 +666,3 @@ def setup(self) -> None:
690
666
@abstractmethod
691
667
def run_case (self , testcase : DataDrivenTestCase ) -> None :
692
668
raise NotImplementedError
693
-
694
- @classmethod
695
- def filter (cls , testcase : DataDrivenTestCase ) -> bool :
696
- return True
0 commit comments