Closed thomhickey closed 3 years ago
You are quite right, but it might not be straight forward to improve. The main reason it takes a long time is https://pypi.org/project/pytest-mypy-plugins/ since I've had to disable mypy caching when testing because the plugin tests would occasionally crash when trying to read from the mypy cache. Even with caching disabled, this error sometimes occur when running tests in parrallel using pytest xdist, which means that xdist has also been disabled making them even slower. Perhaps this issue is fixed in newer versions of pytest-mypy-plugins
, in which case this would improve the situation by a margin.
Moreover, tox is setup to run tests for both python 3.7 and 3.8, which is probably not necessary to do during local development. You can run a single test environment using tox -e py37
for example which cuts the time in half. You can probably also skip the mypy plugin tests in local development and enable xdist using pytest -p no:pytest-mypy-plugins -n 4
. That command takes about 27 seconds on my machine.
If you are only changing things related to a specific type like Effect
or List
it should be enough to run only the tests for that type locally, and only run the entire test suite on circleci. Hope this helps.
I'll keep the issue open as a reminder to look into upgrading pytest-mypy-plugin
As an update, I tried the following:
pytest-mypy-plugin
to version 1.6.1 (which in turn requires updating mypy
to 0.800 and pytest
to 6.2.2)pytest-xdist
to version 2.2.0The issue with the mypy cache still persists under those versions. Specifically, what you get when using pytest-mypy-plugin
together with xdist
is
!!!!!!!!!!!!!!!!!!!! <ExceptionInfo RuntimeError('\'----------------------------------------------------------------------------------------------------\'.../issues\'\n\'----------------------------------------------------------------------------------------------------\'\n') tblen=14>
INTERNALERROR> Traceback (most recent call last):
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/xdist/workermanage.py", line 338, in process_from_remote
INTERNALERROR> config=self.config, data=kwargs["data"]
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/pluggy/hooks.py", line 286, in __call__
INTERNALERROR> return self._hookexec(self, self.get_hookimpls(), kwargs)
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/pluggy/manager.py", line 93, in _hookexec
INTERNALERROR> return self._inner_hookexec(hook, methods, kwargs)
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/pluggy/manager.py", line 87, in <lambda>
INTERNALERROR> firstresult=hook.spec.opts.get("firstresult") if hook.spec else False,
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/pluggy/callers.py", line 208, in _multicall
INTERNALERROR> return outcome.get_result()
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/pluggy/callers.py", line 80, in get_result
INTERNALERROR> raise ex[1].with_traceback(ex[2])
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/pluggy/callers.py", line 187, in _multicall
INTERNALERROR> res = hook_impl.function(*args)
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/_pytest/reports.py", line 412, in pytest_report_from_serializable
INTERNALERROR> return TestReport._from_json(data)
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/_pytest/reports.py", line 211, in _from_json
INTERNALERROR> kwargs = _report_kwargs_from_json(reportdict)
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/_pytest/reports.py", line 547, in _report_kwargs_from_json
INTERNALERROR> reportdict["longrepr"]["reprtraceback"]
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/_pytest/reports.py", line 530, in deserialize_repr_traceback
INTERNALERROR> deserialize_repr_entry(x) for x in repr_traceback_dict["reprentries"]
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/_pytest/reports.py", line 530, in <listcomp>
INTERNALERROR> deserialize_repr_entry(x) for x in repr_traceback_dict["reprentries"]
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/_pytest/reports.py", line 525, in deserialize_repr_entry
INTERNALERROR> _report_unserialization_failure(entry_type, TestReport, reportdict)
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/_pytest/reports.py", line 226, in _report_unserialization_failure
INTERNALERROR> raise RuntimeError(stream.getvalue())
INTERNALERROR> RuntimeError: '----------------------------------------------------------------------------------------------------'
INTERNALERROR> 'INTERNALERROR: Unknown entry type returned: TraceLastReprEntry'
INTERNALERROR> "report_name: <class '_pytest.reports.TestReport'>"
INTERNALERROR> {'$report_type': 'TestReport',
INTERNALERROR> 'duration': 0.7022934830000018,
INTERNALERROR> 'item_index': 168,
INTERNALERROR> 'keywords': {'immutable_has_correct_constructor': 1,
INTERNALERROR> 'pfun': 1,
INTERNALERROR> 'test_mypy_plugin.yaml': 1,
INTERNALERROR> 'tests/__init__.py': 1},
INTERNALERROR> 'location': ('tests/test_mypy_plugin.yaml',
INTERNALERROR> None,
INTERNALERROR> 'immutable_has_correct_constructor'),
INTERNALERROR> 'longrepr': {'chain': [({'extraline': None,
INTERNALERROR> 'reprentries': [{'data': {'lines': ['E '
INTERNALERROR> 'pytest_mypy_plugins.utils.TypecheckAssertionError: '
INTERNALERROR> 'Invalid '
INTERNALERROR> 'output: ',
INTERNALERROR> 'E Expected:',
INTERNALERROR> 'E main:6: '
INTERNALERROR> 'error: Missing '
INTERNALERROR> 'positional '
INTERNALERROR> 'argument '
INTERNALERROR> '"field" in call '
INTERNALERROR> 'to "C" (diff)',
INTERNALERROR> 'E Actual:',
INTERNALERROR> 'E '
INTERNALERROR> '../../../../../../private/var/folders/6c/nr1b_04d1335cl1f68ksy2q00000gn/T/pytest-mypy-ow52mbzo/Traceback '
INTERNALERROR> '(most recent '
INTERNALERROR> 'call last): '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E File '
INTERNALERROR> '"/Users/sune/pfun/.venv/bin/mypy", '
INTERNALERROR> 'line 10, in '
INTERNALERROR> '<module> (diff)',
INTERNALERROR> 'E '
INTERNALERROR> 'sys.exit(console_entry()) '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E File '
INTERNALERROR> '"/Users/sune/pfun/.venv/lib/python3.7/site-packages/mypy/__main__.py", '
INTERNALERROR> 'line 11, in '
INTERNALERROR> 'console_entry '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E '
INTERNALERROR> 'main(None, '
INTERNALERROR> 'sys.stdout, '
INTERNALERROR> 'sys.stderr) '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E File '
INTERNALERROR> '"mypy/main.py", '
INTERNALERROR> 'line 90, in '
INTERNALERROR> 'main '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E File '
INTERNALERROR> '"mypy/build.py", '
INTERNALERROR> 'line 180, in '
INTERNALERROR> 'build (diff)',
INTERNALERROR> 'E File '
INTERNALERROR> '"mypy/build.py", '
INTERNALERROR> 'line 254, in '
INTERNALERROR> '_build (diff)',
INTERNALERROR> 'E File '
INTERNALERROR> '"mypy/build.py", '
INTERNALERROR> 'line 2638, in '
INTERNALERROR> 'dispatch (diff)',
INTERNALERROR> 'E File '
INTERNALERROR> '"mypy/build.py", '
INTERNALERROR> 'line 2957, in '
INTERNALERROR> 'process_graph '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E File '
INTERNALERROR> '"mypy/build.py", '
INTERNALERROR> 'line 3032, in '
INTERNALERROR> 'process_fresh_modules '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E File '
INTERNALERROR> '"mypy/build.py", '
INTERNALERROR> 'line 1960, in '
INTERNALERROR> 'load_tree '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E File '
INTERNALERROR> '"mypy/metastore.py", '
INTERNALERROR> 'line 98, in '
INTERNALERROR> 'read (diff)',
INTERNALERROR> 'E '
INTERNALERROR> '../../../../../../private/var/folders/6c/nr1b_04d1335cl1f68ksy2q00000gn/T/pytest-mypy-ow52mbzo/FileNotFoundError: '
INTERNALERROR> '[Errno 2] No '
INTERNALERROR> 'such file or '
INTERNALERROR> 'directory: '
INTERNALERROR> "'/var/folders/6c/nr1b_04d1335cl1f68ksy2q00000gn/T/.mypy_cache/3.7/asyncio/runners.data.json' "
INTERNALERROR> '(diff)',
INTERNALERROR> 'E ',
INTERNALERROR> 'E Alignment '
INTERNALERROR> 'of first line '
INTERNALERROR> 'difference:',
INTERNALERROR> 'E E: '
INTERNALERROR> 'main:6: error: '
INTERNALERROR> 'Missing '
INTERNALERROR> 'positional '
INTERNALERROR> 'argument '
INTERNALERROR> '"field" in call '
INTERNALERROR> 'to "C"...',
INTERNALERROR> 'E A: '
INTERNALERROR> '../../../../../../private/var/folders/6c/nr1b_04d1335cl1f68ksy2q00000gn/...',
INTERNALERROR> 'E ^'],
INTERNALERROR> 'reprfileloc': {'lineno': 129,
INTERNALERROR> 'message': '',
INTERNALERROR> 'path': '/Users/sune/pfun/tests/test_mypy_plugin.yaml'},
INTERNALERROR> 'reprfuncargs': None,
INTERNALERROR> 'reprlocals': None,
INTERNALERROR> 'style': 'short'},
INTERNALERROR> 'type': 'TraceLastReprEntry'}],
INTERNALERROR> 'style': 'short'},
INTERNALERROR> {'lineno': 251,
INTERNALERROR> 'message': '',
INTERNALERROR> 'path': '/Users/sune/pfun/.venv/lib/python3.7/site-packages/pytest_mypy_plugins/utils.py'},
INTERNALERROR> None)],
INTERNALERROR> 'reprcrash': {'lineno': 251,
INTERNALERROR> 'message': '',
INTERNALERROR> 'path': '/Users/sune/pfun/.venv/lib/python3.7/site-packages/pytest_mypy_plugins/utils.py'},
INTERNALERROR> 'reprtraceback': {'extraline': None,
INTERNALERROR> 'reprentries': [{'data': {'lines': ['E '
INTERNALERROR> 'pytest_mypy_plugins.utils.TypecheckAssertionError: '
INTERNALERROR> 'Invalid '
INTERNALERROR> 'output: ',
INTERNALERROR> 'E '
INTERNALERROR> 'Expected:',
INTERNALERROR> 'E '
INTERNALERROR> 'main:6: '
INTERNALERROR> 'error: '
INTERNALERROR> 'Missing '
INTERNALERROR> 'positional '
INTERNALERROR> 'argument '
INTERNALERROR> '"field" '
INTERNALERROR> 'in call '
INTERNALERROR> 'to "C" '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E '
INTERNALERROR> 'Actual:',
INTERNALERROR> 'E '
INTERNALERROR> '../../../../../../private/var/folders/6c/nr1b_04d1335cl1f68ksy2q00000gn/T/pytest-mypy-ow52mbzo/Traceback '
INTERNALERROR> '(most '
INTERNALERROR> 'recent '
INTERNALERROR> 'call '
INTERNALERROR> 'last): '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E '
INTERNALERROR> 'File '
INTERNALERROR> '"/Users/sune/pfun/.venv/bin/mypy", '
INTERNALERROR> 'line 10, '
INTERNALERROR> 'in '
INTERNALERROR> '<module> '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E '
INTERNALERROR> 'sys.exit(console_entry()) '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E '
INTERNALERROR> 'File '
INTERNALERROR> '"/Users/sune/pfun/.venv/lib/python3.7/site-packages/mypy/__main__.py", '
INTERNALERROR> 'line 11, '
INTERNALERROR> 'in '
INTERNALERROR> 'console_entry '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E '
INTERNALERROR> 'main(None, '
INTERNALERROR> 'sys.stdout, '
INTERNALERROR> 'sys.stderr) '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E '
INTERNALERROR> 'File '
INTERNALERROR> '"mypy/main.py", '
INTERNALERROR> 'line 90, '
INTERNALERROR> 'in '
INTERNALERROR> 'main '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E '
INTERNALERROR> 'File '
INTERNALERROR> '"mypy/build.py", '
INTERNALERROR> 'line 180, '
INTERNALERROR> 'in '
INTERNALERROR> 'build '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E '
INTERNALERROR> 'File '
INTERNALERROR> '"mypy/build.py", '
INTERNALERROR> 'line 254, '
INTERNALERROR> 'in '
INTERNALERROR> '_build '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E '
INTERNALERROR> 'File '
INTERNALERROR> '"mypy/build.py", '
INTERNALERROR> 'line '
INTERNALERROR> '2638, in '
INTERNALERROR> 'dispatch '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E '
INTERNALERROR> 'File '
INTERNALERROR> '"mypy/build.py", '
INTERNALERROR> 'line '
INTERNALERROR> '2957, in '
INTERNALERROR> 'process_graph '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E '
INTERNALERROR> 'File '
INTERNALERROR> '"mypy/build.py", '
INTERNALERROR> 'line '
INTERNALERROR> '3032, in '
INTERNALERROR> 'process_fresh_modules '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E '
INTERNALERROR> 'File '
INTERNALERROR> '"mypy/build.py", '
INTERNALERROR> 'line '
INTERNALERROR> '1960, in '
INTERNALERROR> 'load_tree '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E '
INTERNALERROR> 'File '
INTERNALERROR> '"mypy/metastore.py", '
INTERNALERROR> 'line 98, '
INTERNALERROR> 'in read '
INTERNALERROR> '(diff)',
INTERNALERROR> 'E '
INTERNALERROR> '../../../../../../private/var/folders/6c/nr1b_04d1335cl1f68ksy2q00000gn/T/pytest-mypy-ow52mbzo/FileNotFoundError: '
INTERNALERROR> '[Errno 2] '
INTERNALERROR> 'No such '
INTERNALERROR> 'file or '
INTERNALERROR> 'directory: '
INTERNALERROR> "'/var/folders/6c/nr1b_04d1335cl1f68ksy2q00000gn/T/.mypy_cache/3.7/asyncio/runners.data.json' "
INTERNALERROR> '(diff)',
INTERNALERROR> 'E ',
INTERNALERROR> 'E '
INTERNALERROR> 'Alignment '
INTERNALERROR> 'of first '
INTERNALERROR> 'line '
INTERNALERROR> 'difference:',
INTERNALERROR> 'E E: '
INTERNALERROR> 'main:6: '
INTERNALERROR> 'error: '
INTERNALERROR> 'Missing '
INTERNALERROR> 'positional '
INTERNALERROR> 'argument '
INTERNALERROR> '"field" '
INTERNALERROR> 'in call '
INTERNALERROR> 'to "C"...',
INTERNALERROR> 'E A: '
INTERNALERROR> '../../../../../../private/var/folders/6c/nr1b_04d1335cl1f68ksy2q00000gn/...',
INTERNALERROR> 'E '
INTERNALERROR> '^'],
INTERNALERROR> 'reprfileloc': {'lineno': 129,
INTERNALERROR> 'message': '',
INTERNALERROR> 'path': '/Users/sune/pfun/tests/test_mypy_plugin.yaml'},
INTERNALERROR> 'reprfuncargs': None,
INTERNALERROR> 'reprlocals': None,
INTERNALERROR> 'style': 'short'},
INTERNALERROR> 'type': 'TraceLastReprEntry'}],
INTERNALERROR> 'style': 'short'},
INTERNALERROR> 'sections': []},
INTERNALERROR> 'nodeid': 'tests/test_mypy_plugin.yaml::immutable_has_correct_constructor',
INTERNALERROR> 'outcome': 'failed',
INTERNALERROR> 'sections': [],
INTERNALERROR> 'testrun_uid': 'a2270c80e91740cfa0b77a59eb58ebf7',
INTERNALERROR> 'user_properties': [],
INTERNALERROR> 'when': 'call',
INTERNALERROR> 'worker_id': 'gw3'}
INTERNALERROR> 'Please report this bug at https://github.com/pytest-dev/pytest/issues'
INTERNALERROR> '----------------------------------------------------------------------------------------------------'
[gw3] node down: <ExceptionInfo RuntimeError('\'----------------------------------------------------------------------------------------------------\'.../issues\'\n\'----------------------------------------------------------------------------------------------------\'\n') tblen=14>
F
replacing crashed worker gw3
gw0 [201] / gw1 [201] / gw2 [201] / gw4 C INTERNALERROR> Traceback (most recent call last):
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/_pytest/main.py", line 269, in wrap_session
INTERNALERROR> session.exitstatus = doit(config, session) or 0
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/_pytest/main.py", line 323, in _main
INTERNALERROR> config.hook.pytest_runtestloop(session=session)
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/pluggy/hooks.py", line 286, in __call__
INTERNALERROR> return self._hookexec(self, self.get_hookimpls(), kwargs)
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/pluggy/manager.py", line 93, in _hookexec
INTERNALERROR> return self._inner_hookexec(hook, methods, kwargs)
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/pluggy/manager.py", line 87, in <lambda>
INTERNALERROR> firstresult=hook.spec.opts.get("firstresult") if hook.spec else False,
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/pluggy/callers.py", line 208, in _multicall
INTERNALERROR> return outcome.get_result()
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/pluggy/callers.py", line 80, in get_result
INTERNALERROR> raise ex[1].with_traceback(ex[2])
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/pluggy/callers.py", line 187, in _multicall
INTERNALERROR> res = hook_impl.function(*args)
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/xdist/dsession.py", line 112, in pytest_runtestloop
INTERNALERROR> self.loop_once()
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/xdist/dsession.py", line 135, in loop_once
INTERNALERROR> call(**kwargs)
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/xdist/dsession.py", line 278, in worker_runtest_protocol_complete
INTERNALERROR> self.sched.mark_test_complete(node, item_index, duration)
INTERNALERROR> File "/Users/sune/pfun/.venv/lib/python3.7/site-packages/xdist/scheduler/load.py", line 151, in mark_test_complete
INTERNALERROR> self.node2pending[node].remove(item_index)
INTERNALERROR> KeyError: <WorkerController gw3>
Which ultimately boils down to the test failing because mypy is crashing with FileNotFoundError
when trying to read e.g /var/folders/6c/nr1b_04d1335cl1f68ksy2q00000gn/T/.mypy_cache/3.7/asyncio/runners.data.json
. Most annoyingly, the error only happens sometimes.
@thomhickey TLDR: The tests should be much faster now. I still think its a good idea to just run the tests for one python environment locally (e.g with poetry run tox -e py38
) and only run both environments on circleci.
I managed to get pytest-mypy-plugin
to play nicely with xdist
which improved the test speeds by a margin. Turns out they are ignoring some configuration in the mypy.ini file regarding caching, but its possible to turn off the cache by adding a configuration option to each test in the yml. But pytest-mypy-plugin
is still the bottleneck for test speed, and I think they could be even faster than they are now. As far as I can tell, they write each test case to a temp file (!) and invoke mypy as a subprocess. I think it might be worth having a look at this alternative pytest-mypy-plugin
, that uses the mypy python api instead, which should be faster.
On a side note, I also improved the circleci test speed quite substantially.
Wonderful, thanks so much @suned ... I will get the latest and try it out. I've been buried in Javascript front-end projects lately, but I'm backing them with FastApi so I can definitely start to use pfun in these projects.
The test suite takes so long to run that I probably wouldn't run it very frequently. I'm not sure if this is because of tox or if it's the tests themselves, I haven't looked into it in detail but it makes writing tests quite tedious.