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

Support for Qt Test #23

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ Use `pytest <https://pypi.python.org/pypi/pytest>`_ runner to discover and execu

|python| |version| |downloads| |ci| |coverage|

Supports both `Google Test <https://code.google.com/p/googletest>`_ and
`Boost::Test <http://www.boost.org/doc/libs/release/libs/test>`_:
Supports `Google Test <https://code.google.com/p/googletest>`_ ,
`Boost::Test <http://www.boost.org/doc/libs/release/libs/test>`_ and `QTest <http://doc.qt.io/qt-5/qtest.html>`_:

.. image:: https://raw.githubusercontent.com/pytest-dev/pytest-cpp/master/images/screenshot.png

Expand Down Expand Up @@ -43,7 +43,7 @@ Usage

Once installed, when py.test runs it will search and run tests
founds in executable files, detecting if the suites are
Google or Boost tests automatically.
Google, Boost or Qt tests automatically.

You can configure which files are tested for suites by using the ``cpp_files``
ini configuration::
Expand Down
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
133 changes: 133 additions & 0 deletions pytest_cpp/qt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import os
import subprocess
import tempfile
import io
import shutil
from pytest_cpp.error import CppTestFailure
import xml.etree.ElementTree as ET


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 '-datatags' 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):
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",
"{xml_file},xml".format(xml_file=log_xml),
]
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, _ = p.communicate()

num_reports = len(os.listdir(temp_dir))
if num_reports > 1:
self.merge_xml_report(temp_dir)
log_xml = os.path.join(temp_dir, "result-merged.xml")
log = read_file(log_xml)
elif num_reports == 1:
log_xml = os.path.join(temp_dir, os.listdir(temp_dir)[0])
log = read_file(log_xml)
else:
log_xml = log = None

if p.returncode < 0 and p.returncode not in(-6, ):
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_xml)
shutil.rmtree(temp_dir)

if results:
return results

def merge_xml_report(self, temp_dir):
matches = []
for root, dirnames, filenames in os.walk(temp_dir):
for filename in filenames:
if filename.endswith('.xml'):
matches.append(os.path.join(root, filename))

cases = []
suites = []

for file_name in matches:
tree = ET.parse(file_name)
test_suite = tree.getroot()
cases.append(test_suite.getchildren())
suites.append(test_suite)

new_root = ET.Element('testsuites')

for suite in suites:
new_root.append(suite)

new_tree = ET.ElementTree(new_root)
new_tree.write(os.path.join(temp_dir, "result-merged.xml"),
encoding="UTF-8",
xml_declaration=True)

def _parse_log(self, log):
failed_suites = []
tree = ET.parse(log)
root = tree.getroot()
if log:
for suite in root:
failed_cases = [case for case in root.iter('Incident') if case.get('type') != "pass"]
if failed_cases:
failed_suites = []
for case in failed_cases:
failed_suites.append(QTestFailure(case.attrib['file'], int(case.attrib['line']), case.find('Description').text))
return failed_suites


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
34 changes: 31 additions & 3 deletions tests/SConstruct
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import os
import sys

# In order to build the tests for QT, you need to:
# - Download and install qt from https://www.qt.io/download/
# - Set the QT5DIR variable below
# - Install the Scons Qt5 tools from https://bitbucket.org/dirkbaechle/scons_qt5
# First install option worked for me (installing it directly in the pytest-cpp/tests directory)
ENABLE_QT_TEST = False

if sys.platform.startswith('win'):
CCFLAGS = ['/EHsc', '/nologo']
LIBS = []
Expand All @@ -9,19 +16,40 @@ else:
LIBS = ['pthread']

env = Environment(
CPPPATH=os.environ.get('INCLUDE'),
CPPPATH=os.environ.get('INCLUDE', []),
CCFLAGS=CCFLAGS,
LIBPATH=os.environ.get('LIBPATH'),
LIBPATH=os.environ.get('LIBPATH', []),
LIBS=LIBS,
)

# google test env
genv = env.Clone(LIBS=['gtest'] + LIBS)

if ENABLE_QT_TEST:
# qt5 env
QT5DIR = "/opt/Qt5.7.0/5.7/gcc_64"
qtenv = env.Clone(QT5DIR=QT5DIR,
CCFLAGS="-std=c++11 -fPIC",
tools=['default','qt5'])
qtenv['ENV']['PKG_CONFIG_PATH'] = os.path.join(QT5DIR, 'lib/pkgconfig')
qtenv.EnableQt5Modules([
'QtTest'
])
Export('qtenv')

Export('env genv')

# build google test target
genv.Program('gtest.cpp')

# build boost target
for filename in ('boost_success.cpp', 'boost_failure.cpp', 'boost_error.cpp'):
env.Program(filename)

# build qt5 target
if ENABLE_QT_TEST:
for filename in ('qt_success.cpp', 'qt_failure.cpp', 'qt_error.cpp'):
qtenv.Program(filename)

SConscript('acceptance/googletest-samples/SConscript')
SConscript('acceptance/boosttest-samples/SConscript')
SConscript('acceptance/boosttest-samples/SConscript')
22 changes: 22 additions & 0 deletions tests/qt_error.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include <QtTest/QtTest>

class TestError: public QObject
{
Q_OBJECT
private slots:
void testErrorOne();
void testErrorTwo();
};

void TestError::testErrorOne()
{
throw std::runtime_error("unexpected exception");
}

void TestError::testErrorTwo()
{
throw std::runtime_error("another unexpected exception");
}

QTEST_MAIN(TestError)
#include "qt_error.moc"
22 changes: 22 additions & 0 deletions tests/qt_failure.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include <QtTest/QtTest>

class TestFailure: public QObject
{
Q_OBJECT
private slots:
void TestFailureOne();
void TestFailureTwo();
};

void TestFailure::TestFailureOne()
{
QCOMPARE(2 * 3, 5);
}

void TestFailure::TestFailureTwo()
{
QCOMPARE(2 - 1, 0);
}

QTEST_MAIN(TestFailure)
#include "qt_failure.moc"
22 changes: 22 additions & 0 deletions tests/qt_success.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include <QtTest/QtTest>

class TestSuccess: public QObject
{
Q_OBJECT
private slots:
void testSuccessOne();
void testSuccessTwo();
};

void TestSuccess::testSuccessOne()
{
QCOMPARE(2 * 3, 6);
}

void TestSuccess::testSuccessTwo()
{
QCOMPARE(3 * 4, 12);
}

QTEST_MAIN(TestSuccess)
#include "qt_success.moc"
Loading