Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

In V2 task output (e.g. Pytest), restore the source root #7714

Closed
Eric-Arellano opened this issue May 14, 2019 · 2 comments
Closed

In V2 task output (e.g. Pytest), restore the source root #7714

Eric-Arellano opened this issue May 14, 2019 · 2 comments

Comments

@Eric-Arellano
Copy link
Contributor

For absolute imports to work, we must strip the source root (e.g. src/python). We do this in #7696 by stripping the source root prefix of all files before passing it to Pytest.

However, this results in Pytest's stdout showing the stripped file names rather than the full name. Pants will still output the full path in the surrounding log, like this, but Pytest won't:

        testprojects/tests/python/pants/dummies:target_with_thirdparty_dep stdout:
        ============================= test session starts ==============================
        platform SOME_TEXT
        rootdir: SOME_TEXT
        plugins: SOME_TEXT
        collected 1 item

        pants/dummies/test_with_thirdparty_dep.py .                              [100%]

        =========================== 1 passed in SOME_TEXT ===========================


        testprojects/tests/python/pants/dummies:target_with_thirdparty_dep              .....   SUCCESS

In V1, we appear to restore the full path by saving the output to a Junit XML file and then injecting the full name:

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)
return relsrc_to_target.get(relsrc)

@stuhood
Copy link
Member

stuhood commented May 14, 2019

@Eric-Arellano : AFAIK, the test file renaming is accomplished by

def _get_conftest_content(self, sources_map, rootdir_comm_path):
# A conftest hook to modify the console output, replacing the chroot-based
# source paths with the source-tree based ones, which are more readable to the end user.
# Note that python stringifies a dict to its source representation, so we can use sources_map
# as a format argument directly.
#
# We'd prefer to hook into pytest_runtest_logstart(), which actually prints the line we
# want to fix, but we can't because we won't have access to any of its state, so
# we can't actually change what it prints.
#
# Alternatively, we could hook into pytest_collect_file() and just set a custom nodeid
# for the entire pytest run. However this interferes with pytest internals, including
# fixture registration, leading to fixtures not running when they should.
# It also requires the generated conftest to be in the root of the source tree, which
# complicates matters when there's already a user conftest.py there.
console_output_conftest_content = dedent("""
### GENERATED BY PANTS ###
import os
import pytest
class NodeRenamerPlugin(object):
# Map from absolute source chroot path -> path of original source relative to the buildroot.
_SOURCES_MAP = {sources_map!r}
def __init__(self, rootdir):
def rootdir_relative(path):
return os.path.relpath(path, rootdir)
self._sources_map = {{rootdir_relative(k): rootdir_relative(v)
for k, v in self._SOURCES_MAP.items()}}
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(self, item, nextitem):
# Temporarily change the nodeid, which pytest uses for display.
real_nodeid = item.nodeid
real_path = real_nodeid.split('::', 1)[0]
fixed_path = self._sources_map.get(real_path, real_path)
fixed_nodeid = fixed_path + real_nodeid[len(real_path):]
try:
item._nodeid = fixed_nodeid
yield
finally:
item._nodeid = real_nodeid
# The path to write out the py.test rootdir to.
_ROOTDIR_COMM_PATH = {rootdir_comm_path!r}
def pytest_configure(config):
rootdir = str(config.rootdir)
with open(_ROOTDIR_COMM_PATH, 'w') as fp:
fp.write(rootdir)
config.pluginmanager.register(NodeRenamerPlugin(rootdir), 'pants_test_renamer')
""".format(sources_map=dict(sources_map), rootdir_comm_path=rootdir_comm_path))
# Add in the sharding conftest, if any.
shard_conftest_content = self._get_shard_conftest_content()
return console_output_conftest_content + shard_conftest_content
rather than by xml parsing (that would be post-run, which is too late).

@Eric-Arellano
Copy link
Contributor Author

No longer relevant with #8063.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants