Skip to content

Refactor test discovery - #3973

Merged
JukkaL merged 17 commits into
python:masterfrom
elazarg:refactor-test-discovery
Dec 12, 2017
Merged

Refactor test discovery#3973
JukkaL merged 17 commits into
python:masterfrom
elazarg:refactor-test-discovery

Conversation

@elazarg

@elazarg elazarg commented Sep 20, 2017

Copy link
Copy Markdown
Contributor

Test discovery logic is duplicated over all the different test suites. This PR refactors this logic to the plugin method collect(), leaving only the choice of configuration in the classes. This refactoring should enable further optimizations in the discovery of specific tests.

The argument perform to parse_test_cases() is (already) unused, so it is removed. Same goes for include_path.

The core of the changes can be tested by diffing the result of py.test --collect-only -n0 with the main branch. The only differences should be the execution time and the name of the suite PythonCmdlineSuite which is called PythonEvaluationSuite on master.

TestCase is split into BaseTestCase to avoid sharing of fields and methods that's not really shared with myunit tests, such as run(). I believe this change need not last long.

@elazarg
elazarg force-pushed the refactor-test-discovery branch 7 times, most recently from d75d7ee to a107d0c Compare September 20, 2017 09:15
@elazarg
elazarg force-pushed the refactor-test-discovery branch from e76f9ef to 5df8a6c Compare October 21, 2017 19:13
@elazarg
elazarg force-pushed the refactor-test-discovery branch from 5df8a6c to 34120f6 Compare October 21, 2017 20:04

@JukkaL JukkaL left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Negative lines of code is great -- thanks! This removes some pretty pointless boilerplate. I have just a bunch of style/documentation related comments.

Comment thread mypy/myunit/__init__.py Outdated
def __init__(self, name: str, suite: 'Optional[Suite]' = None,
func: Optional[Callable[[], None]] = None) -> None:
self.func = func
class ProtoTestCase:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest renaming this to BaseTestCase, since Proto sounds like a protocol but this is a regular class. Also add a docstring. At least describe how these are run, since this class doesn't seem to have a hint about that.

Comment thread mypy/myunit/__init__.py Outdated
self.tmpdir = None


class TestCase(ProtoTestCase):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add docstring. In particular, how is this different from the base class?

Comment thread mypy/test/data.py Outdated


class DataDrivenTestCase(TestCase):
class DataDrivenTestCase(ProtoTestCase):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of confusing, since there seems to be no longer anything in this class about how an individual test case is run. This at least needs a docstring.

Comment thread mypy/test/data.py Outdated
def pytest_pycollect_makeitem(collector: Any, name: str, obj: Any) -> Any:
if not isinstance(obj, type) or not issubclass(obj, DataSuite):
return None
if obj is DataSuite:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment here -- something like this perhaps: only classes derived from DataSuite contain test cases, not the DataSuite class.

Comment thread mypy/test/data.py Outdated
def collect(self) -> Iterator['MypyDataCase']:
for case in self.obj.cases():
yield MypyDataCase(case.name, self, case)
cls = self.obj

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment -- what is self.obj here and where does it come from? This apparently requires understanding some pytest magic, which many readers likely won't be too familiar with, including me.

Comment thread mypy/test/data.py
return MypyDataSuite(name, parent=collector)


class MypyDataSuite(pytest.Class): # type: ignore # inheriting from Any

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add docstring here as well while you are at it and probably have more context on this than I have?

Comment thread mypy/test/data.py Outdated


class DataSuite:
files = None # type: typing.ClassVar[List[str]]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not have the ClassVar annotations here and elsewhere -- they add noise and distract from the main intent of the code. It would be okay to just add a comment here mentioning that all of these should be treated as class variables.

Comment thread mypy/test/data.py Outdated
optional_out = False # type: typing.ClassVar[bool]
native_sep = False # type: typing.ClassVar[bool]
require_stable = False # type: typing.ClassVar[bool]
require_incremental = False # type: typing.ClassVar[bool]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require_stable and require_incremental are too specialized to be included here as configuration attributes. It's okay to have is_incremental and has_stable_flags as utility functions in this module (I don't think that they need to be associated with any class), but I'd prefer if these would be explicitly handled using asserts in run_case methods, similar to how these were implemented previously.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are also used as filters in the test selection, and the asserts only validate it. I will add a filter method then.

Comment thread mypy/test/testpythoneval.py Outdated
test_python_evaluation, test_temp_dir, True)
return c
files = ['pythoneval.test', 'python2eval.test'] # type: typing.ClassVar[List[str]]
if sys.version_info.major == 3 and sys.version_info.minor >= 4:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3.3 is no longer supported for running mypy so this test can be dropped.

@elazarg

elazarg commented Dec 1, 2017

Copy link
Copy Markdown
Contributor Author

I think I've addressed the comments except the parts about configuration options. I hope to address the rest tomorrow.

@elazarg

elazarg commented Dec 2, 2017

Copy link
Copy Markdown
Contributor Author

@JukkaL I believe this is ready.

@JukkaL JukkaL left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates! Looks mostly good now, just a few minor things left.

Comment thread mypy/myunit/__init__.py Outdated
class BaseTestCase:
"""Common base class for _MyUnitTestCase and DataDrivenTestCase.

Handles temporary folder creation and deletion"""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit: In multi-line docstrings add the trailing """ on a separate line like this:

class BaseTestCase:
    """Common base class for _MyUnitTestCase and DataDrivenTestCase.

    Handles temporary folder creation and deletion.
    """
    ...

Comment thread mypy/myunit/__init__.py Outdated

def setup(self) -> None:
super().setup()
self.suite.set_up()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent naming: setup vs set_up?

Comment thread mypy/test/data.py Outdated
obj: object) -> 'Optional[Any]':
"""Called by pytest on each object in modules configured in conftest.py files.

collector is pytest.Collector, returns Optional[pytest.Class]"""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit: """ should be on a separate line in multi-line docstrings.

Comment thread mypy/test/data.py Outdated
# Only classes derived from DataSuite contain test cases, not the DataSuite class itself
if issubclass(obj, DataSuite) and obj is not DataSuite:
# Non-None result means this obj is a test case
# result.collect will be called, with self.obj being obj

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it's a little unclear what result.collect refers to. Maybe rephrase as "The collect method of the returned MypyDataSuite instance will be called later, with self.obj being obj."

Comment thread mypy/test/testcheck.py Outdated
c += parse_test_cases(os.path.join(test_data_prefix, f),
None, test_temp_dir, True)
return c
files = typecheck_files # type: List[str]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the variable type annotations for in DataSuite subclasses now seem redundant. Can you remove them?

Comment thread mypy/test/testdmypy.py Outdated
def run_case(self, testcase: DataDrivenTestCase) -> None:
assert self.is_incremental(testcase), "Testcase is not incremental"
assert self.has_stable_flags(testcase), "Testcase has varying flags"
assert has_stable_flags(testcase), "Testcase is not incremental"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The message should be "Testcase has varying flags" (the messages seem to have been switched on these two lines).

Comment thread mypy/test/testdmypy.py Outdated
assert self.is_incremental(testcase), "Testcase is not incremental"
assert self.has_stable_flags(testcase), "Testcase has varying flags"
assert has_stable_flags(testcase), "Testcase is not incremental"
assert is_incremental(testcase), "Testcase has varying flags"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the other switched message.

@elazarg

elazarg commented Dec 12, 2017

Copy link
Copy Markdown
Contributor Author

Thanks! Fixed.

@JukkaL
JukkaL merged commit dc52e86 into python:master Dec 12, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants