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-' )
@@ -343,6 +350,22 @@ def teardown(self) -> None:
343
350
self .old_cwd = None
344
351
self .tmpdir = None
345
352
353
+ def reportinfo (self ) -> Tuple [str , int , str ]:
354
+ return self .file , self .line , self .name
355
+
356
+ def repr_failure (self , excinfo : Any ) -> str :
357
+ if excinfo .errisinstance (SystemExit ):
358
+ # We assume that before doing exit() (which raises SystemExit) we've printed
359
+ # enough context about what happened so that a stack trace is not useful.
360
+ # In particular, uncaught exceptions during semantic analysis or type checking
361
+ # call exit() and they already print out a stack trace.
362
+ excrepr = excinfo .exconly ()
363
+ else :
364
+ self .parent ._prunetraceback (excinfo )
365
+ excrepr = excinfo .getrepr (style = 'short' )
366
+
367
+ return "data: {}:{}:\n {}" .format (self .file , self .line , excrepr )
368
+
346
369
def find_steps (self ) -> List [List [FileOperation ]]:
347
370
"""Return a list of descriptions of file operations for each incremental step.
348
371
@@ -569,7 +592,7 @@ def fix_cobertura_filename(line: str) -> str:
569
592
570
593
571
594
# This function name is special to pytest. See
572
- # http ://doc .pytest.org/en/latest/writing_plugins .html#initialization-command-line-and-configuration -hooks
595
+ # https ://docs .pytest.org/en/latest/reference .html#initialization-hooks
573
596
def pytest_addoption (parser : Any ) -> None :
574
597
group = parser .getgroup ('mypy' )
575
598
group .addoption ('--update-data' , action = 'store_true' , default = False ,
@@ -589,26 +612,20 @@ def pytest_pycollect_makeitem(collector: Any, name: str,
589
612
# Only classes derived from DataSuite contain test cases, not the DataSuite class itself
590
613
if issubclass (obj , DataSuite ) and obj is not DataSuite :
591
614
# Non-None result means this obj is a test case.
592
- # The collect method of the returned MypyDataSuite instance will be called later,
615
+ # The collect method of the returned DataSuiteCollector instance will be called later,
593
616
# with self.obj being obj.
594
- return MypyDataSuite (name , parent = collector )
617
+ return DataSuiteCollector (name , parent = collector )
595
618
return None
596
619
597
620
598
- class MypyDataSuite (pytest .Class ): # type: ignore # inheriting from Any
621
+ class DataSuiteCollector (pytest .Class ): # type: ignore # inheriting from Any
599
622
def collect (self ) -> Iterator [pytest .Item ]: # type: ignore
600
623
"""Called by pytest on each of the object returned from pytest_pycollect_makeitem"""
601
624
602
625
# obj is the object for which pytest_pycollect_makeitem returned self.
603
626
suite = self .obj # type: DataSuite
604
627
for f in suite .files :
605
- for case in parse_test_cases (os .path .join (suite .data_prefix , f ),
606
- base_path = suite .base_path ,
607
- optional_out = suite .optional_out ,
608
- native_sep = suite .native_sep ):
609
- if suite .filter (case ):
610
- case .name = add_test_name_suffix (case .name , suite .test_name_suffix )
611
- yield MypyDataCase (case .name , self , case )
628
+ yield from parse_test_cases (self , suite , os .path .join (suite .data_prefix , f ))
612
629
613
630
614
631
def add_test_name_suffix (name : str , suffix : str ) -> str :
@@ -637,47 +654,6 @@ def has_stable_flags(testcase: DataDrivenTestCase) -> bool:
637
654
return True
638
655
639
656
640
- class MypyDataCase (pytest .Item ): # type: ignore # inheriting from Any
641
- def __init__ (self , name : str , parent : MypyDataSuite , case : DataDrivenTestCase ) -> None :
642
- self .skip = False
643
- if name .endswith ('-skip' ):
644
- self .skip = True
645
- name = name [:- len ('-skip' )]
646
-
647
- super ().__init__ (name , parent )
648
- self .case = case
649
-
650
- def runtest (self ) -> None :
651
- if self .skip :
652
- pytest .skip ()
653
- suite = self .parent .obj ()
654
- suite .update_data = self .config .getoption ('--update-data' , False )
655
- suite .setup ()
656
- suite .run_case (self .case )
657
-
658
- def setup (self ) -> None :
659
- self .case .setup ()
660
-
661
- def teardown (self ) -> None :
662
- self .case .teardown ()
663
-
664
- def reportinfo (self ) -> Tuple [str , int , str ]:
665
- return self .case .file , self .case .line , self .case .name
666
-
667
- def repr_failure (self , excinfo : Any ) -> str :
668
- if excinfo .errisinstance (SystemExit ):
669
- # We assume that before doing exit() (which raises SystemExit) we've printed
670
- # enough context about what happened so that a stack trace is not useful.
671
- # In particular, uncaught exceptions during semantic analysis or type checking
672
- # call exit() and they already print out a stack trace.
673
- excrepr = excinfo .exconly ()
674
- else :
675
- self .parent ._prunetraceback (excinfo )
676
- excrepr = excinfo .getrepr (style = 'short' )
677
-
678
- return "data: {}:{}:\n {}" .format (self .case .file , self .case .line , excrepr )
679
-
680
-
681
657
class DataSuite :
682
658
# option fields - class variables
683
659
files = None # type: List[str]
@@ -699,7 +675,3 @@ def setup(self) -> None:
699
675
@abstractmethod
700
676
def run_case (self , testcase : DataDrivenTestCase ) -> None :
701
677
raise NotImplementedError
702
-
703
- @classmethod
704
- def filter (cls , testcase : DataDrivenTestCase ) -> bool :
705
- return True
0 commit comments