Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
exclude: '^($|.*\.bin)'
repos:
- repo: https://github.com/ambv/black
rev: 19.10b0
hooks:
- id: black
args: [--safe, --quiet]
language_version: python3.7
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- repo: local
hooks:
- id: rst
name: rst
entry: rst-lint --encoding utf-8
files: README.rst
language: python
additional_dependencies: [pygments, restructuredtext_lint]
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ jobs:
python: '3.8'
- env: TOXENV=py38-pytest53
python: '3.8'
- env: TOXENV=linting
python: '3.7'
- stage: deploy
python: '3.8'
install: pip install -U setuptools setuptools_scm
Expand Down
24 changes: 12 additions & 12 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

# 1.1.0

- New `cpp_arguments` ini option allows customization of the command-line
- New `cpp_arguments` ini option allows customization of the command-line
used to run tests, for example to enable logging and verbosity.
Thanks @elkin for the PR.

Expand Down Expand Up @@ -50,22 +50,22 @@
# 0.4.0 #

- Integrated most of the examples found in boost-1.55 for Boost Test into the
`pytest-cpp` test suite. This uncovered a problem when Fatal Errors were
produced and which would break py.test.
`pytest-cpp` test suite. This uncovered a problem when Fatal Errors were
produced and which would break py.test.

- Integrated all the official Google Test examples into the `pytest-cpp` test
suite, ensuring that Google Test is fully covered.
- Fixed #17: supporting Type-Parametrized tests in Google Test. Thanks
@joarbe for the report.
suite, ensuring that Google Test is fully covered.

- Fixed #17: supporting Type-Parametrized tests in Google Test. Thanks
@joarbe for the report.

- Now any executable passed explicitly to py.test in the
- Now any executable passed explicitly to py.test in the
command-line will run as a test, regardless of the `cpp_files` option.

# 0.3.1 #

- Now capturing standard error while collecting tests, otherwise
executable files that are not test suites could write into `stderr`,
- Now capturing standard error while collecting tests, otherwise
executable files that are not test suites could write into `stderr`,
which would mess with `py.test`'s output to console.

# 0.3.0 #
Expand All @@ -76,7 +76,7 @@ which would mess with `py.test`'s output to console.
# 0.2.0 #

- `cpp_files` option to `pytest.ini` files.
- `Boost::Test` suites now writes reports to a file, which makes
- `Boost::Test` suites now writes reports to a file, which makes
it behave correctly if a test writes in `std::cout` or `std::cerr`.

# 0.1.0 #
Expand Down
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
include LICENSE
include README.md
include README.md
5 changes: 4 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pytest-cpp

Use `pytest <https://pypi.python.org/pypi/pytest>`_ runner to discover and execute C++ tests.

|python| |version| |anaconda| |ci| |coverage|
|python| |version| |anaconda| |ci| |coverage| |black|

Supports both `Google Test <https://code.google.com/p/googletest>`_ and
`Boost::Test <http://www.boost.org/doc/libs/release/libs/test>`_:
Expand All @@ -27,6 +27,9 @@ Supports both `Google Test <https://code.google.com/p/googletest>`_ and
:target: https://pypi.python.org/pypi/pytest-cpp/
:alt: Supported Python versions

.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/psf/black

This brings several benefits:

* Allows you to run all your tests in multi-language projects with a single
Expand Down
77 changes: 42 additions & 35 deletions pytest_cpp/boost.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,22 @@ class BoostTestFacade(object):
@classmethod
def is_test_suite(cls, executable):
try:
output = subprocess.check_output([executable, '--help'],
stderr=subprocess.STDOUT,
universal_newlines=True)
output = subprocess.check_output(
[executable, "--help"],
stderr=subprocess.STDOUT,
universal_newlines=True,
)
except (subprocess.CalledProcessError, OSError):
return False
else:
return '--output_format' in output and 'log_format' in output
return "--output_format" in output and "log_format" in output

def list_tests(self, executable):
# unfortunately boost doesn't provide us with a way to list the tests
# inside the executable, so the test_id is a dummy placeholder :(
return [os.path.basename(os.path.splitext(executable)[0])]

def run_test(self, executable, test_id, test_args=()):

def read_file(name):
try:
with io.open(name) as f:
Expand All @@ -38,13 +39,13 @@ def read_file(name):
return None

temp_dir = tempfile.mkdtemp()
log_xml = os.path.join(temp_dir, 'log.xml')
report_xml = os.path.join(temp_dir, 'report.xml')
log_xml = os.path.join(temp_dir, "log.xml")
report_xml = os.path.join(temp_dir, "report.xml")
args = [
executable,
'--output_format=XML',
'--log_sink=%s' % log_xml,
'--report_sink=%s' % report_xml,
"--output_format=XML",
"--log_sink=%s" % log_xml,
"--report_sink=%s" % report_xml,
]
args.extend(test_args)
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
Expand All @@ -54,27 +55,33 @@ def read_file(name):
report = read_file(report_xml)

if p.returncode not in (0, 200, 201):
msg = ('Internal Error: calling {executable} '
'for test {test_id} failed (returncode={returncode}):\n'
'output:{stdout}\n'
'log:{log}\n'
'report:{report}')
msg = (
"Internal Error: calling {executable} "
"for test {test_id} failed (returncode={returncode}):\n"
"output:{stdout}\n"
"log:{log}\n"
"report:{report}"
)
failure = BoostTestFailure(
'<no source file>',
"<no source file>",
linenum=0,
contents=msg.format(executable=executable,
test_id=test_id,
stdout=stdout,
log=log,
report=report,
returncode=p.returncode))
contents=msg.format(
executable=executable,
test_id=test_id,
stdout=stdout,
log=log,
report=report,
returncode=p.returncode,
),
)
return [failure]

if report is not None and (
report.startswith('Boost.Test framework internal error: ') or
report.startswith('Test setup error: ')):
report.startswith("Boost.Test framework internal error: ")
or report.startswith("Test setup error: ")
):
# boost.test doesn't do XML output on fatal-enough errors.
failure = BoostTestFailure('unknown location', 0, report)
failure = BoostTestFailure("unknown location", 0, report)
return [failure]

results = self._parse_log(log=log)
Expand All @@ -93,22 +100,22 @@ def _parse_log(self, log):
# <FatalError>...</FatalError><TestLog>...</TestLog>
# so we have to manually split it into two xmls if that's the case.
parsed_elements = []
if log.startswith('<FatalError'):
fatal, log = log.split('</FatalError>')
fatal += '</FatalError>' # put it back, removed by split()
if log.startswith("<FatalError"):
fatal, log = log.split("</FatalError>")
fatal += "</FatalError>" # put it back, removed by split()
fatal_root = ElementTree.fromstring(fatal)
fatal_root.text = 'Fatal Error: %s' % fatal_root.text
fatal_root.text = "Fatal Error: %s" % fatal_root.text
parsed_elements.append(fatal_root)

log_root = ElementTree.fromstring(log)
parsed_elements.extend(log_root.findall('Exception'))
parsed_elements.extend(log_root.findall('Error'))
parsed_elements.extend(log_root.findall('FatalError'))
parsed_elements.extend(log_root.findall("Exception"))
parsed_elements.extend(log_root.findall("Error"))
parsed_elements.extend(log_root.findall("FatalError"))

result = []
for elem in parsed_elements:
filename = elem.attrib['file']
linenum = int(elem.attrib['line'])
filename = elem.attrib["file"]
linenum = int(elem.attrib["line"])
result.append(BoostTestFailure(filename, linenum, elem.text))
return result

Expand All @@ -120,7 +127,7 @@ def __init__(self, filename, linenum, contents):
self.lines = contents.splitlines()

def get_lines(self):
m = ('red', 'bold')
m = ("red", "bold")
return [(x, m) for x in self.lines]

def get_file_reference(self):
Expand Down
17 changes: 8 additions & 9 deletions pytest_cpp/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class CppFailureError(Exception):

Contains a list of `CppFailure` instances.
"""

def __init__(self, failures):
self.failures = failures

Expand Down Expand Up @@ -45,22 +46,23 @@ class CppFailureRepr(object):
"repr" object for pytest that knows how to print a CppFailure instance
into both terminal and files.
"""
failure_sep = '---'

failure_sep = "---"

def __init__(self, failures):
self.failures = failures

def __str__(self):
reprs = []
for failure in self.failures:
pure_lines = '\n'.join(x[0] for x in failure.get_lines())
pure_lines = "\n".join(x[0] for x in failure.get_lines())
repr_loc = self._get_repr_file_location(failure)
reprs.append("%s\n%s" % (pure_lines, repr_loc))
return self.failure_sep.join(reprs)

def _get_repr_file_location(self, failure):
filename, linenum = failure.get_file_reference()
return ReprFileLocation(filename, linenum, 'C++ failure')
return ReprFileLocation(filename, linenum, "C++ failure")

def toterminal(self, tw):
for index, failure in enumerate(self.failures):
Expand All @@ -69,7 +71,7 @@ def toterminal(self, tw):
for line in code_lines:
tw.line(line, white=True, bold=True) # pragma: no cover

indent = get_left_whitespace(code_lines[-1]) if code_lines else ''
indent = get_left_whitespace(code_lines[-1]) if code_lines else ""

for line, markup in failure.get_lines():
markup_params = {m: True for m in markup}
Expand All @@ -92,18 +94,15 @@ def get_code_context_around_line(filename, linenum):
with open(filename) as f:
index_above = index - 2
index_above = index_above if index_above >= 0 else 0
return [x.rstrip() for x in f.readlines()[index_above:index + 1]]
return [x.rstrip() for x in f.readlines()[index_above : index + 1]]
return []


def get_left_whitespace(line):
result = ''
result = ""
for c in line:
if c in string.whitespace:
result += c
else:
break
return result



Loading