Skip to content

Commit 852fe00

Browse files
committed
unify DataDrivenTestCase classes
1 parent 874d67e commit 852fe00

File tree

2 files changed

+52
-89
lines changed

2 files changed

+52
-89
lines changed

mypy/test/data.py

Lines changed: 52 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,16 @@
2828
FileOperation = Union[UpdateFile, DeleteFile]
2929

3030

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.
3934
4035
NB: this function and DataDrivenTestCase were shared between the
4136
myunit and pytest codepaths -- if something looks redundant,
4237
that's likely the reason.
4338
"""
44-
if native_sep:
39+
base_path = suite.base_path
40+
if suite.native_sep:
4541
join = os.path.join
4642
else:
4743
join = posixpath.join # type: ignore
@@ -51,7 +47,6 @@ def parse_test_cases(
5147
for i in range(len(lst)):
5248
lst[i] = lst[i].rstrip('\n')
5349
p = parse_test_data(lst, path)
54-
out = [] # type: List[DataDrivenTestCase]
5550

5651
# Process the parsed items. Each item has a header of form [id args],
5752
# optionally followed by lines of text.
@@ -143,7 +138,7 @@ def parse_test_cases(
143138
assert passnum > 1
144139
output = p[i].data
145140
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 == '\\':
147142
output = [fix_win_path(line) for line in output]
148143
tcout2[passnum] = output
149144
ok = True
@@ -167,7 +162,7 @@ def parse_test_cases(
167162
('Stale modules after pass {} must be a subset of rechecked '
168163
'modules ({}:{})').format(passnum, path, p[i0].line))
169164

170-
if optional_out:
165+
if suite.optional_out:
171166
ok = True
172167

173168
if ok:
@@ -178,24 +173,24 @@ def parse_test_cases(
178173
lastline = p[i].line if i < len(p) else p[i - 1].line + 9999
179174
arg0 = p[i0].arg
180175
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)
187185
if not ok:
188186
raise ValueError(
189187
'{}, line {}: Error in test case description'.format(
190188
path, p[i0].line))
191189

192-
return out
193-
194190

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."""
197193

198-
# TODO: rename to ParsedTestCase or merge with MypyDataCase (yet avoid multiple inheritance)
199194
# TODO: only create files on setup, not during parsing
200195

201196
input = None # type: List[str]
@@ -215,6 +210,8 @@ class DataDrivenTestCase:
215210

216211
def __init__(self,
217212
name: str,
213+
parent: 'DataSuiteCollector',
214+
skip: bool,
218215
input: List[str],
219216
output: List[str],
220217
output2: Dict[int, List[str]],
@@ -229,7 +226,9 @@ def __init__(self,
229226
native_sep: bool = False,
230227
triggered: Optional[List[str]] = None,
231228
) -> None:
232-
self.name = name
229+
230+
super().__init__(name, parent)
231+
self.skip = skip
233232
self.old_cwd = None # type: Optional[str]
234233
self.tmpdir = None # type: Optional[tempfile.TemporaryDirectory[str]]
235234
self.input = input
@@ -246,6 +245,14 @@ def __init__(self,
246245
self.native_sep = native_sep
247246
self.triggered = triggered or []
248247

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+
249256
def setup(self) -> None:
250257
self.old_cwd = os.getcwd()
251258
self.tmpdir = tempfile.TemporaryDirectory(prefix='mypy-test-')
@@ -336,6 +343,22 @@ def teardown(self) -> None:
336343
self.old_cwd = None
337344
self.tmpdir = None
338345

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+
339362
def find_steps(self) -> List[List[FileOperation]]:
340363
"""Return a list of descriptions of file operations for each incremental step.
341364
@@ -560,7 +583,7 @@ def fix_cobertura_filename(line: str) -> str:
560583

561584

562585
# 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
564587
def pytest_addoption(parser: Any) -> None:
565588
group = parser.getgroup('mypy')
566589
group.addoption('--update-data', action='store_true', default=False,
@@ -580,26 +603,20 @@ def pytest_pycollect_makeitem(collector: Any, name: str,
580603
# Only classes derived from DataSuite contain test cases, not the DataSuite class itself
581604
if issubclass(obj, DataSuite) and obj is not DataSuite:
582605
# 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,
584607
# with self.obj being obj.
585-
return MypyDataSuite(name, parent=collector)
608+
return DataSuiteCollector(name, parent=collector)
586609
return None
587610

588611

589-
class MypyDataSuite(pytest.Class): # type: ignore # inheriting from Any
612+
class DataSuiteCollector(pytest.Class): # type: ignore # inheriting from Any
590613
def collect(self) -> Iterator[pytest.Item]: # type: ignore
591614
"""Called by pytest on each of the object returned from pytest_pycollect_makeitem"""
592615

593616
# obj is the object for which pytest_pycollect_makeitem returned self.
594617
suite = self.obj # type: DataSuite
595618
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))
603620

604621

605622
def add_test_name_suffix(name: str, suffix: str) -> str:
@@ -628,47 +645,6 @@ def has_stable_flags(testcase: DataDrivenTestCase) -> bool:
628645
return True
629646

630647

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-
672648
class DataSuite:
673649
# option fields - class variables
674650
files = None # type: List[str]
@@ -690,7 +666,3 @@ def setup(self) -> None:
690666
@abstractmethod
691667
def run_case(self, testcase: DataDrivenTestCase) -> None:
692668
raise NotImplementedError
693-
694-
@classmethod
695-
def filter(cls, testcase: DataDrivenTestCase) -> bool:
696-
return True

scripts/myunit

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)