Skip to content

Commit

Permalink
Python test runner uses pytest
Browse files Browse the repository at this point in the history
  • Loading branch information
illicitonion committed Nov 8, 2018
1 parent 31ccf73 commit ddd9ec1
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 23 deletions.
61 changes: 51 additions & 10 deletions src/python/pants/backend/python/rules/python_test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@

from __future__ import absolute_import, division, print_function, unicode_literals

import os.path
from builtins import str

from pants.engine.fs import Digest, MergedDirectories, Snapshot, UrlToFetch
from pants.engine.isolated_process import ExecuteProcessRequest, FallibleExecuteProcessResult
from pants.engine.legacy.graph import HydratedTarget
from pants.engine.rules import rule
from pants.engine.selectors import Select
from pants.engine.selectors import Get, Select
from pants.rules.core.core_test_model import Status, TestResult


Expand All @@ -19,14 +22,52 @@ class PyTestResult(TestResult):
pass


# TODO: Support deps
# TODO: Support resources
@rule(PyTestResult, [Select(HydratedTarget)])
def run_python_test(target):
# TODO: Actually run tests (https://github.com/pantsbuild/pants/issues/6003)

if 'fail' in target.address.reference():
noun = 'failed'
status = Status.FAILURE
else:
noun = 'passed'
status = Status.SUCCESS
return PyTestResult(status=status, stdout=str('I am a python test which {}'.format(noun)))

dirs = set()
for f in target.adaptor.sources.files_relative_to_buildroot:
dir = os.path.dirname(f)
if dir == '':
dir = '.'
dirs.add(dir)
dirs = tuple(sorted(dirs))

# TODO: Inject versions and digests here through some option, rather than hard-coding it.
pex_snapshot = yield Get(Snapshot, UrlToFetch("https://github.com/pantsbuild/pex/releases/download/v1.5.2/pex27",
Digest('8053a79a5e9c2e6e9ace3999666c9df910d6289555853210c1bbbfa799c3ecda', 1757011)))

argv = [
'./{}'.format(pex_snapshot.files[0].path),
'-e', 'pytest:main',
# TODO: This should probably use the running interpreter, rather than hard-coding /usr/local/bin/python2.7
'--python', '/usr/local/bin/python2.7',
# TODO: This is non-hermetic because pytest will be resolved on the fly by pex27, where it should be hermetically provided in some way.
# We should probably also specify a specific version.
'pytest',
]
for dir in dirs:
argv.append('-D')
argv.append(dir)

merged_input_files = yield Get(
Digest,
MergedDirectories,
MergedDirectories(directories=(target.adaptor.sources.snapshot.directory_digest, pex_snapshot.directory_digest)),
)

request = ExecuteProcessRequest(
argv=tuple(argv),
input_files=merged_input_files,
description='Run pytest for {}'.format(target.address.reference()),
# TODO: This should not be necessary
env={'PATH': '/usr/local/bin'}
)

result = yield Get(FallibleExecuteProcessResult, ExecuteProcessRequest, request)
# TODO: Do something with stderr?
status = Status.SUCCESS if result.exit_code == 0 else Status.FAILURE

yield PyTestResult(status=status, stdout=str(result.stdout))
6 changes: 5 additions & 1 deletion src/python/pants/source/wrapped_globs.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,12 @@ def __init__(self, rel_root, filespec, snapshot, include_dirs=False):

@memoized_property
def files(self):
return tuple(fast_relpath(path, self.rel_root) for path in self.files_relative_to_buildroot)

@memoized_property
def files_relative_to_buildroot(self):
fds = self._snapshot.path_stats if self._include_dirs else self._snapshot.files
return tuple(fast_relpath(fd.path, self.rel_root) for fd in fds)
return tuple(fd.path for fd in fds)

@property
def files_hash(self):
Expand Down
93 changes: 81 additions & 12 deletions tests/python/pants_test/rules/test_test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@

class TestIntegrationTest(PantsRunIntegrationTest):

# TODO: Modify flags (or pytest) so that output is hermetic and deterministic, and doesn't require fuzzy matching
def assert_fuzzy_string_match(self, got, want):
want_lines = want.split('\n')
got_lines = got.split('\n')
self.assertEqual(len(want_lines), len(got_lines), 'Wrong number of lines comparing:\nWANT:\n{}\nGOT:\n{}'.format(want, got))

for line_number, (want_line, got_line) in enumerate(zip(want_lines, got_lines)):
want_parts = want_line.split('SOME_TEXT')
if len(want_parts) == 1:
self.assertEqual(want_line, got_line, "Line {} wrong".format(line_number))
elif len(want_parts) == 2:
self.assertTrue(got_line.startswith(want_parts[0]), 'Line {} Want "{}" to start with "{}"'.format(line_number, got_line, want_parts[0]))
self.assertTrue(got_line.endswith(want_parts[1]), 'Line {} Want "{}" to end with "{}"'.format(line_number, got_line, want_parts[1]))

def test_passing_python_test(self):
args = [
'--no-v1',
Expand All @@ -18,13 +32,25 @@ def test_passing_python_test(self):
]
pants_run = self.run_pants(args)
self.assert_success(pants_run)
self.assertEqual("""I am a python test which passed

testprojects/tests/python/pants/dummies:passing_target ..... SUCCESS
""", pants_run.stdout_data)
self.assertEqual("", pants_run.stderr_data)
self.assertEqual(pants_run.returncode, 0)

self.assert_fuzzy_string_match(
pants_run.stdout_data,
"""============================= test session starts ==============================
platform SOME_TEXT
rootdir: SOME_TEXT
collected 1 item
testprojects/tests/python/pants/dummies/test_pass.py . [100%]
=========================== 1 passed in SOME_TEXT ===========================
testprojects/tests/python/pants/dummies:passing_target ..... SUCCESS
""",
)

def test_failing_python_test(self):
args = [
'--no-v1',
Expand All @@ -34,12 +60,30 @@ def test_failing_python_test(self):
]
pants_run = self.run_pants(args)
self.assert_failure(pants_run)
self.assertEqual("""I am a python test which failed
testprojects/tests/python/pants/dummies:failing_target ..... FAILURE
""", pants_run.stdout_data)
self.assertEqual("", pants_run.stderr_data)
self.assertEqual(pants_run.returncode, 1)
self.assert_fuzzy_string_match(
pants_run.stdout_data,
"""============================= test session starts ==============================
platform SOME_TEXT
rootdir: SOME_TEXT
collected 1 item
testprojects/tests/python/pants/dummies/test_fail.py F [100%]
=================================== FAILURES ===================================
__________________________________ test_fail ___________________________________
def test_fail():
> assert False
E assert False
testprojects/tests/python/pants/dummies/test_fail.py:2: AssertionError
=========================== 1 failed in SOME_TEXT ===========================
testprojects/tests/python/pants/dummies:failing_target ..... FAILURE
""",
)

def test_mixed_python_tests(self):
args = [
Expand All @@ -51,11 +95,36 @@ def test_mixed_python_tests(self):
]
pants_run = self.run_pants(args)
self.assert_failure(pants_run)
self.assertEqual("""I am a python test which failed
I am a python test which passed
self.assertEqual("", pants_run.stderr_data)
self.assertEqual(pants_run.returncode, 1)
self.assert_fuzzy_string_match(
pants_run.stdout_data,
"""============================= test session starts ==============================
platform SOME_TEXT
rootdir: SOME_TEXT
collected 1 item
testprojects/tests/python/pants/dummies/test_fail.py F [100%]
=================================== FAILURES ===================================
__________________________________ test_fail ___________________________________
def test_fail():
> assert False
E assert False
testprojects/tests/python/pants/dummies/test_fail.py:2: AssertionError
=========================== 1 failed in SOME_TEXT ===========================
============================= test session starts ==============================
platform SOME_TEXT
rootdir: SOME_TEXT
collected 1 item
testprojects/tests/python/pants/dummies/test_pass.py . [100%]
=========================== 1 passed in SOME_TEXT ===========================
testprojects/tests/python/pants/dummies:failing_target ..... FAILURE
testprojects/tests/python/pants/dummies:passing_target ..... SUCCESS
""", pants_run.stdout_data)
self.assertEqual("", pants_run.stderr_data)
self.assertEqual(pants_run.returncode, 1)
""",
)

0 comments on commit ddd9ec1

Please sign in to comment.