Skip to content
Next Next commit
New facade QTestLibFacade
  • Loading branch information
Leandre Arseneault authored and Jonathan Roy committed Jul 26, 2016
commit f89d20ac2700229805b94be923f2247cad5774a7
4 changes: 3 additions & 1 deletion pytest_cpp/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

from pytest_cpp.google import GoogleTestFacade

FACADES = [GoogleTestFacade, BoostTestFacade]
from pytest_cpp.qt import QTestLibFacade

FACADES = [GoogleTestFacade, BoostTestFacade, QTestLibFacade]
DEFAULT_MASKS = ('test_*', '*_test')


Expand Down
107 changes: 107 additions & 0 deletions pytest_cpp/qt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import os
import subprocess
import tempfile
from xml.etree import ElementTree
import io
import shutil
from pytest_cpp.error import CppTestFailure


class QTestLibFacade(object):
"""
Facade for QTestLib.
"""

@classmethod
def is_test_suite(cls, executable):
try:
output = subprocess.check_output([executable, '--help'],
stderr=subprocess.STDOUT,
universal_newlines=True)
except (subprocess.CalledProcessError, OSError):
return False
else:
return '-csv' in output

def run_test(self, executable, test_id):

def read_file(name):
try:
with io.open(name) as f:
return f.read()
except IOError:
return None

temp_dir = tempfile.mkdtemp()
log_xml = os.path.join(temp_dir, 'log.xml')
args = [
executable,
'-o log_xml, xml',
]
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, _ = p.communicate()

log = read_file(log_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')
failure = QTestFailure(
'<no source file>',
linenum=0,
contents=msg.format(executable=executable,
test_id=test_id,
stdout=stdout,
log=log,
returncode=p.returncode))
return [failure]

results = self._parse_log(log=log)
shutil.rmtree(temp_dir)
if results:
return results

def _parse_log(self, log):
"""
Parse the "log" section produced by BoostTest.

This is always a XML file, and from this we produce most of the
failures possible when running BoostTest.
"""
# Fatal errors apparently generate invalid xml in the form:
# <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()
fatal_root = ElementTree.fromstring(fatal)
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'))

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


class QTestFailure(CppTestFailure):
def __init__(self, filename, linenum, contents):
self.filename = filename
self.linenum = linenum
self.lines = contents.splitlines()

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

def get_file_reference(self):
return self.filename, self.linenum
12 changes: 7 additions & 5 deletions tests/test_pytest_cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pytest_cpp.boost import BoostTestFacade
from pytest_cpp.error import CppTestFailure, CppFailureRepr
from pytest_cpp.google import GoogleTestFacade
from pytest_cpp.qt import QTestLibFacade


def assert_outcomes(result, expected_outcomes):
Expand Down Expand Up @@ -143,9 +144,10 @@ def test_google_run(testdir, exes):
('FooTest.DISABLED_test_disabled', 'skipped'),
])


def test_unknown_error(testdir, exes, mocker):
mocker.patch.object(GoogleTestFacade, 'run_test',
side_effect=RuntimeError('unknown error'))
side_effect=RuntimeError('unknown error'))
result = testdir.inline_run('-v', exes.get('gtest', 'test_gtest'))
rep = result.matchreport('FooTest.test_success', 'pytest_runtest_logreport')
assert 'unknown error' in str(rep.longrepr)
Expand All @@ -154,9 +156,9 @@ def test_unknown_error(testdir, exes, mocker):
def test_google_internal_errors(mocker, testdir, exes, tmpdir):
mocker.patch.object(GoogleTestFacade, 'is_test_suite', return_value=True)
mocker.patch.object(GoogleTestFacade, 'list_tests',
return_value=['FooTest.test_success'])
return_value=['FooTest.test_success'])
mocked = mocker.patch.object(subprocess, 'check_output', autospec=True,
return_value='')
return_value='')

def raise_error(*args, **kwargs):
raise subprocess.CalledProcessError(returncode=100, cmd='')
Expand All @@ -170,7 +172,7 @@ def raise_error(*args, **kwargs):
xml_file = tmpdir.join('results.xml')
xml_file.write('<empty/>')
mocker.patch.object(GoogleTestFacade, '_get_temp_xml_filename',
return_value=str(xml_file))
return_value=str(xml_file))
result = testdir.inline_run('-v', exes.get('gtest', 'test_gtest'))
rep = result.matchreport(exes.exe_name('test_gtest'),
'pytest_runtest_logreport')
Expand Down Expand Up @@ -220,7 +222,7 @@ def test_cpp_failure_repr(dummy_failure):
def test_cpp_files_option(testdir, exes):
exes.get('boost_success')
exes.get('gtest')

result = testdir.inline_run('--collect-only')
reps = result.getreports()
assert len(reps) == 1
Expand Down