in src/python/pants/backend/python/rules/python_test_runner.py [0:0]
def run_python_test(test_target, pytest, python_setup, source_root_config, pex_build_environment, subprocess_encoding_environment):
"""Runs pytest for one target."""
# TODO: Inject versions and digests here through some option, rather than hard-coding it.
url = 'https://github.com/pantsbuild/pex/releases/download/v1.6.6/pex'
digest = Digest('61bb79384db0da8c844678440bd368bcbfac17bbdb865721ad3f9cb0ab29b629', 1826945)
pex_snapshot = yield Get(Snapshot, UrlToFetch(url, digest))
# TODO(7726): replace this with a proper API to get the `closure` for a
# TransitiveHydratedTarget.
transitive_hydrated_targets = yield Get(
TransitiveHydratedTargets, BuildFileAddresses((test_target.address,))
)
all_targets = [t.adaptor for t in transitive_hydrated_targets.closure]
# Produce a pex containing pytest and all transitive 3rdparty requirements.
all_target_requirements = []
for maybe_python_req_lib in all_targets:
# This is a python_requirement()-like target.
if hasattr(maybe_python_req_lib, 'requirement'):
all_target_requirements.append(str(maybe_python_req_lib.requirement))
# This is a python_requirement_library()-like target.
if hasattr(maybe_python_req_lib, 'requirements'):
for py_req in maybe_python_req_lib.requirements:
all_target_requirements.append(str(py_req.requirement))
# Sort all user requirement strings to increase the chance of cache hits across invocations.
all_requirements = sorted(all_target_requirements + list(pytest.get_requirement_strings()))
# TODO(#7061): This str() can be removed after we drop py2!
python_binary = text_type(sys.executable)
interpreter_constraint_args = parse_interpreter_constraints(
python_setup, python_target_adaptors=all_targets
)
interpreter_search_paths = text_type(create_path_env_var(python_setup.interpreter_search_paths))
# TODO: This is non-hermetic because the requirements will be resolved on the fly by
# pex27, where it should be hermetically provided in some way.
output_pytest_requirements_pex_filename = 'pytest-with-requirements.pex'
requirements_pex_argv = [
python_binary,
'./{}'.format(pex_snapshot.files[0]),
'-e', 'pytest:main',
'-o', output_pytest_requirements_pex_filename,
] + interpreter_constraint_args + [
# TODO(#7061): This text_type() wrapping can be removed after we drop py2!
text_type(req) for req in all_requirements
]
pex_resolve_env = {'PATH': interpreter_search_paths}
# TODO(#6071): merge the two dicts via ** unpacking once we drop Py2.
pex_resolve_env.update(pex_build_environment.invocation_environment_dict)
requirements_pex_request = ExecuteProcessRequest(
argv=tuple(requirements_pex_argv),
env=pex_resolve_env,
input_files=pex_snapshot.directory_digest,
description='Resolve requirements: {}'.format(", ".join(all_requirements)),
output_files=(output_pytest_requirements_pex_filename,),
)
requirements_pex_response = yield Get(
ExecuteProcessResult, ExecuteProcessRequest, requirements_pex_request)
source_roots = source_root_config.get_source_roots()
# Gather sources and adjust for the source root.
# TODO: make TargetAdaptor return a 'sources' field with an empty snapshot instead of raising to
# simplify the hasattr() checks here!
# TODO(7714): restore the full source name for the stdout of the Pytest run.
sources_snapshots_and_source_roots = []
for maybe_source_target in all_targets:
if hasattr(maybe_source_target, 'sources'):
tgt_snapshot = maybe_source_target.sources.snapshot
tgt_source_root = source_roots.find_by_path(maybe_source_target.address.spec_path)
sources_snapshots_and_source_roots.append((tgt_snapshot, tgt_source_root))
all_sources_digests = yield [
Get(
Digest,
DirectoryWithPrefixToStrip(
directory_digest=snapshot.directory_digest,
prefix=source_root.path
)
)
for snapshot, source_root
in sources_snapshots_and_source_roots
]
sources_digest = yield Get(
Digest, DirectoriesToMerge(directories=tuple(all_sources_digests)),
)
inits_digest = yield Get(InjectedInitDigest, Digest, sources_digest)
all_input_digests = [
sources_digest,
inits_digest.directory_digest,
requirements_pex_response.output_directory_digest,
]
merged_input_files = yield Get(
Digest,
DirectoriesToMerge,
DirectoriesToMerge(directories=tuple(all_input_digests)),
)
pex_exe_env = {'PATH': interpreter_search_paths}
# TODO(#6071): merge the two dicts via ** unpacking once we drop Py2.
pex_exe_env.update(subprocess_encoding_environment.invocation_environment_dict)
request = ExecuteProcessRequest(
argv=(python_binary, './{}'.format(output_pytest_requirements_pex_filename)),
env=pex_exe_env,
input_files=merged_input_files,
description='Run pytest for {}'.format(test_target.address.reference()),
)
result = yield Get(FallibleExecuteProcessResult, ExecuteProcessRequest, request)
status = Status.SUCCESS if result.exit_code == 0 else Status.FAILURE
yield TestResult(
status=status,
stdout=result.stdout.decode('utf-8'),
stderr=result.stderr.decode('utf-8'),
)