Skip to content

Commit

Permalink
Simplify pytest configuration. (#7786)
Browse files Browse the repository at this point in the history
+ Use `--rootdir` to set the rootdir ahead of time and use the buildroot
  for sensible RHS paths in `-v` output (previously we only had sensible
  LHS paths via the `NodeRenamerPlugin`). This option was introduced
  relatively recently in 3.5.0:
    https://docs.pytest.org/en/latest/changelog.html#pytest-3-5-0-2018-03-21
    pytest-dev/pytest#1642
+ Control the `.pytest_cachedir` location with `-o` and move it into the
  task workdir (defaults to the `--rootdir` otherwise).
+ Simplify `pytest.ini` nullification by pointing to os.devnull instead
  of embedding an empty `pytest.ini` in the pytest pex.
  • Loading branch information
jsirois committed May 22, 2019
1 parent 805ef8a commit ead235c
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 39 deletions.
8 changes: 0 additions & 8 deletions src/python/pants/backend/python/tasks/pytest/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,6 @@ def is_conftest(itm):

def pytest_addoption(parser):
group = parser.getgroup('pants', 'Pants testing support')
group.addoption('--pants-rootdir-comm-path',
dest='rootdir_comm_path',
action='store',
metavar='PATH',
help='Path to a file to write the pytest rootdir to.')
group.addoption('--pants-sources-map-path',
dest='sources_map_path',
action='store',
Expand Down Expand Up @@ -105,9 +100,6 @@ def pytest_configure(config):
return

rootdir = str(config.rootdir)
rootdir_comm_path = config.getoption('rootdir_comm_path')
with open(rootdir_comm_path, 'w') as fp:
fp.write(rootdir)

sources_map_path = config.getoption('sources_map_path')
with open(sources_map_path) as fp:
Expand Down
10 changes: 0 additions & 10 deletions src/python/pants/backend/python/tasks/pytest_prep.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

from __future__ import absolute_import, division, print_function, unicode_literals

import os
from builtins import object

import pkg_resources
Expand Down Expand Up @@ -72,14 +71,6 @@ def interpreter(self):
"""
return self._interpreter

@property
def config_path(self):
"""Return the absolute path of the `pytest.ini` config file in this pytest binary.
:rtype: str
"""
return os.path.join(self._pex.path(), 'pytest.ini')

@classmethod
def implementation_version(cls):
return super(PytestPrep, cls).implementation_version() + [('PytestPrep', 2)]
Expand All @@ -98,7 +89,6 @@ def _module_resource(cls, module_name, resource_relpath):
content=pkg_resources.resource_string(__name__, resource_relpath))

def extra_files(self):
yield self.ExtraFile.empty('pytest.ini')
yield self._module_resource(self.PytestBinary.pytest_plugin_module, 'pytest/plugin.py')
yield self._module_resource(self.PytestBinary.coverage_plugin_module, 'coverage/plugin.py')

Expand Down
41 changes: 20 additions & 21 deletions src/python/pants/backend/python/tasks/pytest_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class PytestResult(TestResult):
# This is returned by pytest when no tests are collected (EXIT_NOTESTSCOLLECTED).
# We already short-circuit test runs with no test _targets_ to return 0 emulated exit codes and
# we should do the same for cases when there are test targets but tests themselves have been
# de-selected out of band via `py.test -k`.
# de-selected out of band via `pytest -k`.
5
)

Expand Down Expand Up @@ -378,34 +378,33 @@ def _pants_pytest_plugin_args(self, sources_map):
# uses all arguments that look like paths to compute its rootdir, and we want
# it to pick the buildroot.
with temporary_dir(root_dir=self.workdir) as comm_dir:
rootdir_comm_path = os.path.join(comm_dir, 'pytest_rootdir.path')

def get_pytest_rootdir():
with open(rootdir_comm_path, 'r') as fp:
return fp.read()

sources_map_path = os.path.join(comm_dir, 'sources_map.json')
mode = 'w' if PY3 else 'wb'
with open(sources_map_path, mode) as fp:
json.dump(sources_map, fp)

renaming_args = [
'--pants-rootdir-comm-path', rootdir_comm_path,
'--pants-sources-map-path', sources_map_path
]

yield renaming_args + self._get_sharding_args(), get_pytest_rootdir
yield renaming_args + self._get_sharding_args()

@contextmanager
def _test_runner(self, workdirs, test_targets, sources_map):
pytest_binary = self.context.products.get_data(PytestPrep.PytestBinary)
with self._pants_pytest_plugin_args(sources_map) as (plugin_args, get_pytest_rootdir):
with self._pants_pytest_plugin_args(sources_map) as plugin_args:
with self._maybe_emit_coverage_data(workdirs,
test_targets,
pytest_binary.pex) as coverage_args:
yield (pytest_binary,
['-p', pytest_binary.pytest_plugin_module] + plugin_args + coverage_args,
get_pytest_rootdir)
pytest_rootdir = get_buildroot()
yield (
pytest_binary,
[
'--rootdir', pytest_rootdir,
'-p', pytest_binary.pytest_plugin_module
] + plugin_args + coverage_args,
pytest_rootdir
)

def _constrain_pytest_interpreter_search_path(self):
"""Return an environment for invoking a pex which ensures the use of the selected interpreter.
Expand All @@ -427,11 +426,11 @@ def _do_run_tests_with_args(self, pex, args):
try:
env = dict(os.environ)

# Ensure we don't leak source files or undeclared 3rdparty requirements into the py.test PEX
# Ensure we don't leak source files or undeclared 3rdparty requirements into the pytest PEX
# environment.
pythonpath = env.pop('PYTHONPATH', None)
if pythonpath:
self.context.log.warn('scrubbed PYTHONPATH={} from py.test environment'.format(pythonpath))
self.context.log.warn('scrubbed PYTHONPATH={} from pytest environment'.format(pythonpath))
# But allow this back door for users who do want to force something onto the test pythonpath,
# e.g., modules required during a debugging session.
extra_pythonpath = self.get_options().extra_pythonpath
Expand Down Expand Up @@ -502,9 +501,9 @@ def _get_failed_targets_from_junitxml(self, junitxml, targets, pytest_rootdir):
test_failed = testcase.getElementsByTagName('failure')
test_errored = testcase.getElementsByTagName('error')
if test_failed or test_errored:
# The file attribute is always relative to the py.test rootdir.
# The file attribute is always relative to the pytest rootdir.
pytest_relpath = testcase.getAttribute('file')
relsrc = os.path.join(buildroot_relpath, pytest_relpath)
relsrc = os.path.normpath(os.path.join(buildroot_relpath, pytest_relpath))
failed_target = relsrc_to_target.get(relsrc)
if failed_target:
failed_targets.add(failed_target)
Expand All @@ -521,7 +520,7 @@ def _get_target_from_test(self, test_info, targets, pytest_rootdir):
relsrc_to_target = self._map_relsrc_to_targets(targets)
buildroot_relpath = os.path.relpath(pytest_rootdir, get_buildroot())
pytest_relpath = test_info['file']
relsrc = os.path.join(buildroot_relpath, pytest_relpath)
relsrc = os.path.normpath(os.path.join(buildroot_relpath, pytest_relpath))
return relsrc_to_target.get(relsrc)

@contextmanager
Expand Down Expand Up @@ -610,7 +609,7 @@ def _run_pytest(self, fail_fast, test_targets, workdirs):

with self._test_runner(workdirs, test_targets, sources_map) as (pytest_binary,
test_args,
get_pytest_rootdir):
pytest_rootdir):
# Validate that the user didn't provide any passthru args that conflict
# with those we must set ourselves.
for arg in self.get_passthru_args():
Expand All @@ -622,7 +621,8 @@ def _run_pytest(self, fail_fast, test_targets, workdirs):
# N.B. the `--confcutdir` here instructs pytest to stop scanning for conftest.py files at the
# top of the buildroot. This prevents conftest.py files from outside (e.g. in users home dirs)
# from leaking into pants test runs. See: https://github.com/pantsbuild/pants/issues/2726
args = ['-c', pytest_binary.config_path,
args = ['-c', os.devnull, # Force an empty pytest.ini
'-o' 'cache_dir={}'.format(os.path.join(self.workdir, '.pytest_cache')),
'--junitxml', junitxml_path,
'--confcutdir', get_buildroot(),
'--continue-on-collection-errors']
Expand Down Expand Up @@ -654,7 +654,6 @@ def _run_pytest(self, fail_fast, test_targets, workdirs):
if not os.path.exists(junitxml_path):
return result

pytest_rootdir = get_pytest_rootdir()
failed_targets = self._get_failed_targets_from_junitxml(junitxml_path,
test_targets,
pytest_rootdir)
Expand Down

0 comments on commit ead235c

Please sign in to comment.