Commit a494d27b authored by Don Gagne's avatar Don Gagne

Merge pull request #1025 from DonLakeFlyer/UnitTestV2

New Unit Testing framework
parents 9f6a69b5 89de2ae2
......@@ -130,6 +130,7 @@ MacBuild {
CONFIG += x86_64
CONFIG -= x86
QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.6
QMAKE_MAC_SDK = macosx10.9
ICON = $$BASEDIR/files/images/icons/macx.icns
QT += quickwidgets
}
......@@ -633,7 +634,8 @@ INCLUDEPATH += \
src/qgcunittest
HEADERS += \
src/qgcunittest/AutoTest.h \
src/qgcunittest/UnitTest.h \
src/qgcunittest/MessageBoxTest.h \
src/qgcunittest/UASUnitTest.h \
src/qgcunittest/MockLink.h \
src/qgcunittest/MockLinkMissionItemHandler.h \
......@@ -651,7 +653,9 @@ HEADERS += \
src/qgcunittest/LinkManagerTest.h
SOURCES += \
src/qgcunittest/UnitTest.cc \
src/qgcunittest/UASUnitTest.cc \
src/qgcunittest/MessageBoxTest.cc \
src/qgcunittest/MockLink.cc \
src/qgcunittest/MockLinkMissionItemHandler.cc \
src/qgcunittest/MockUASManager.cc \
......
......@@ -84,8 +84,9 @@ const char* QGCApplication::_savedFileParameterDirectoryName = "SavedParameters"
**/
QGCApplication::QGCApplication(int &argc, char* argv[]) :
QApplication(argc, argv)
QGCApplication::QGCApplication(int &argc, char* argv[], bool unitTesting) :
QApplication(argc, argv),
_runningUnitTests(unitTesting)
{
Q_ASSERT(_app == NULL);
_app = this;
......@@ -120,6 +121,13 @@ QGCApplication::QGCApplication(int &argc, char* argv[]) :
// The setting will delete all settings on this boot
fClearSettingsOptions |= settings.contains(_deleteAllSettingsKey);
// We don't want unit tests to use the same QSettings space as the normal app. So we tweak the app
// name. Also we want to run unit tests with clean settings every time.
if (_runningUnitTests) {
setApplicationName(applicationName().append("UnitTest"));
fClearSettingsOptions = true;
}
if (fClearSettingsOptions) {
// User requested settings to be cleared on command line
settings.clear();
......
......@@ -54,7 +54,7 @@ class QGCApplication : public QApplication
Q_OBJECT
public:
QGCApplication(int &argc, char* argv[]);
QGCApplication(int &argc, char* argv[], bool unitTesting);
~QGCApplication();
/// @brief Sets the persistent flag to delete all settings the next time QGroundControl is started.
......@@ -94,6 +94,9 @@ public:
/// @brief Destroys all singletons. Used by unit test code to reset global state.
void destroySingletonsForUnitTest(void);
/// @brief Returns truee if unit test are being run
bool runningUnitTests(void) { return _runningUnitTests; }
public:
/// @brief Perform initialize which is common to both normal application running and unit tests.
/// Although public should only be called by main.
......@@ -122,6 +125,8 @@ private:
static const char* _savedFileParameterDirectoryName; ///< Name of parameter subdirectory
QList<QGCSingleton*> _singletons; ///< List of registered global singletons
bool _runningUnitTests; ///< true: running unit tests, false: normal app
};
/// @brief Returns the QGCApplication object singleton.
......
......@@ -27,6 +27,10 @@
#include <QMessageBox>
#include "MainWindow.h"
#include "QGCApplication.h"
#ifdef QT_DEBUG
#include "UnitTest.h"
#endif
/// @file
/// @brief Subclass of QMessageBox which re-implements the static public functions. There are two reasons for this:
......@@ -50,30 +54,81 @@ public:
static StandardButton warning(const QString& title, const QString& text, StandardButtons buttons = Ok, StandardButton defaultButton = NoButton, QWidget* parent = NULL)
{ return _messageBox(QMessageBox::Warning, title, text, buttons, defaultButton, parent); }
private slots:
/// @brief The exec slot is private becasue when only want QGCMessageBox users to use the static methods. Otherwise it will break
/// unit testing.
int exec(void) { return QMessageBox::exec(); }
private:
static QWidget* _validateParameters(StandardButtons buttons, StandardButton* defaultButton, QWidget* parent)
{
// This is an obsolete bit which unit tests use for signalling. It should not be used in regular code.
Q_ASSERT(!(buttons & QMessageBox::Escape));
// If there is more than one button displayed, make sure a default button is set. Without this unit test code
// will not be able to respond to unexpected message boxes.
unsigned int bits = static_cast<unsigned int>(buttons);
int buttonCount = 0;
for (size_t i=0; i<sizeof(bits)*8; i++) {
if (bits & (1 << i)) {
buttonCount++;
}
}
Q_ASSERT(buttonCount != 0);
if (buttonCount > 1) {
Q_ASSERT(buttons & *defaultButton);
} else {
// Force default button to be set correctly for single button case to make unit test code simpler
*defaultButton = static_cast<QMessageBox::StandardButton>(static_cast<int>(buttons));
}
return (parent == NULL) ? MainWindow::instance() : parent;
}
#ifdef Q_OS_MAC
static StandardButton _messageBox(Icon icon, const QString& title, const QString& text, StandardButtons buttons, StandardButton defaultButton, QWidget* parent)
{
if (parent == NULL) {
parent = MainWindow::instance();
// You can't use QGCMessageBox if QGCApplication is not created yet.
Q_ASSERT(qgcApp());
parent = _validateParameters(buttons, &defaultButton, parent);
#ifdef QT_DEBUG
if (qgcApp()->runningUnitTests()) {
return UnitTest::_messageBox(icon, title, text, buttons, defaultButton);
} else
#endif // QT_DEBUG
{
QString emptyTitle;
QMessageBox box(icon, emptyTitle, title, buttons, parent);
box.setDefaultButton(defaultButton);
box.setInformativeText(text);
return static_cast<QMessageBox::StandardButton>(box.exec());
}
QString emptyTitle;
QMessageBox box(icon, emptyTitle, title, buttons, parent);
box.setDefaultButton(defaultButton);
box.setInformativeText(text);
return static_cast<QMessageBox::StandardButton>(box.exec());
}
#else
static StandardButton _messageBox(Icon icon, const QString& title, const QString& text, StandardButtons buttons, StandardButton defaultButton, QWidget* parent)
{
if (parent == NULL) {
parent = MainWindow::instance();
// You can't use QGCMessageBox if QGCApplication is not created yet.
Q_ASSERT(qgcApp());
parent = _validateParameters(buttons, &defaultButton, parent);
#ifdef QT_DEBUG
if (qgcApp()->runningUnitTests()) {
return UnitTest::_messageBox(icon, title, text, buttons, defaultButton);
} else
#endif // QT_DEBUG
{
QMessageBox box(icon, title, text, buttons, parent);
box.setDefaultButton(defaultButton);
return static_cast<QMessageBox::StandardButton>(box.exec());
}
QMessageBox box(icon, title, text, buttons, parent);
box.setDefaultButton(defaultButton);
return static_cast<QMessageBox::StandardButton>(box.exec());
}
#endif
#endif // Q_OS_MAC
};
#endif
......@@ -37,7 +37,7 @@ This file is part of the QGROUNDCONTROL project
#include "SerialLink.h"
#include "TCPLink.h"
#ifdef QT_DEBUG
#include "AutoTest.h"
#include "UnitTest.h"
#include "CmdLineOptParser.h"
#ifdef Q_OS_WIN
#include <crtdbg.h>
......@@ -104,11 +104,12 @@ int main(int argc, char *argv[])
qRegisterMetaType<QSerialPort::SerialPortError>();
qRegisterMetaType<QAbstractSocket::SocketError>();
bool runUnitTests = false; // Run unit tests
#ifdef QT_DEBUG
// We parse a small set of command line options here prior to QGCApplication in order to handle the ones
// which need to be handled before a QApplication object is started.
bool runUnitTests = false; // Run unit test
bool quietWindowsAsserts = false; // Don't let asserts pop dialog boxes
CmdLineOpt_t rgCmdLineOptions[] = {
......@@ -126,7 +127,7 @@ int main(int argc, char *argv[])
}
#endif
QGCApplication* app = new QGCApplication(argc, argv);
QGCApplication* app = new QGCApplication(argc, argv, runUnitTests);
Q_CHECK_PTR(app);
app->_initCommon();
......@@ -140,13 +141,10 @@ int main(int argc, char *argv[])
}
// Run the test
int failures = AutoTest::run(argc-1, argv, rgCmdLineOptions[0].optionArg);
if (failures == 0)
{
int failures = UnitTest::run(argc-1, argv, rgCmdLineOptions[0].optionArg);
if (failures == 0) {
qDebug() << "ALL TESTS PASSED";
}
else
{
} else {
qDebug() << failures << " TESTS FAILED!";
}
exitCode = -failures;
......
/**
* @author Rob Caldecott
* @note This was obtained from http://qtcreator.blogspot.com/2010/04/sample-multiple-unit-test-project.html
*
*/
#ifndef AUTOTEST_H
#define AUTOTEST_H
#include <QTest>
#include <QList>
#include <QString>
#include <QSharedPointer>
#include "QGCApplication.h"
namespace AutoTest
{
typedef QList<QObject*> TestList;
inline TestList& testList()
{
static TestList list;
return list;
}
inline bool findObject(QObject* object)
{
TestList& list = testList();
if (list.contains(object))
{
return true;
}
foreach (QObject* test, list)
{
if (test->objectName() == object->objectName())
{
return true;
}
}
return false;
}
inline void addTest(QObject* object)
{
TestList& list = testList();
if (!findObject(object))
{
list.append(object);
}
}
inline int run(int argc, char *argv[], QString& singleTest)
{
int ret = 0;
foreach (QObject* test, testList())
{
if (singleTest.isEmpty() || singleTest == test->objectName()) {
qgcApp()->destroySingletonsForUnitTest();
qgcApp()->createSingletonsForUnitTest();
ret += QTest::qExec(test, argc, argv);
}
}
return ret;
}
}
template <class T>
class Test
{
public:
QSharedPointer<T> child;
Test(const QString& name) : child(new T)
{
child->setObjectName(name);
AutoTest::addTest(child.data());
}
};
#define DECLARE_TEST(className) static Test<className> t(#className);
#endif // AUTOTEST_H
......@@ -29,23 +29,13 @@
///
/// @author Don Gagne <don@thegagnes.com>
UT_REGISTER_TEST(FlightGearUnitTest)
FlightGearUnitTest::FlightGearUnitTest(void)
{
}
// Called before every test
void FlightGearUnitTest::init(void)
{
// Nothing here yet
}
// Called after every test
void FlightGearUnitTest::cleanup(void)
{
// Nothing here yet
}
/// @brief The QGCFlightGearLink::parseUIArguments method is fairly involved so we have a unit
// test for it.
void FlightGearUnitTest::_parseUIArguments_test(void)
......
......@@ -24,11 +24,7 @@
#ifndef TCPLINKTEST_H
#define TCPLINKTEST_H
#include <QObject>
#include <QtTest>
#include <QApplication>
#include "AutoTest.h"
#include "UnitTest.h"
#include "TCPLink.h"
#include "MultiSignalSpy.h"
......@@ -37,7 +33,7 @@
///
/// @author Don Gagne <don@thegagnes.com>
class FlightGearUnitTest : public QObject
class FlightGearUnitTest : public UnitTest
{
Q_OBJECT
......@@ -45,14 +41,12 @@ public:
FlightGearUnitTest(void);
private slots:
void init(void);
void cleanup(void);
UT_DECLARE_DEFAULT_initTestCase
UT_DECLARE_DEFAULT_cleanupTestCase
UT_DECLARE_DEFAULT_init
UT_DECLARE_DEFAULT_cleanup
void _parseUIArguments_test(void);
private:
};
DECLARE_TEST(FlightGearUnitTest)
#endif
......@@ -29,6 +29,8 @@
#include "LinkManagerTest.h"
#include "MockLink.h"
UT_REGISTER_TEST(LinkManagerTest)
LinkManagerTest::LinkManagerTest(void) :
_linkMgr(NULL),
_multiSpy(NULL)
......@@ -38,6 +40,8 @@ LinkManagerTest::LinkManagerTest(void) :
void LinkManagerTest::init(void)
{
UnitTest::init();
Q_ASSERT(_linkMgr == NULL);
Q_ASSERT(_multiSpy == NULL);
......@@ -53,6 +57,8 @@ void LinkManagerTest::init(void)
void LinkManagerTest::cleanup(void)
{
UnitTest::cleanup();
Q_ASSERT(_linkMgr);
Q_ASSERT(_multiSpy);
......
......@@ -24,10 +24,7 @@
#ifndef UASUNITTEST_H
#define UASUNITTEST_H
#include <QObject>
#include <QtTest>
#include "AutoTest.h"
#include "UnitTest.h"
#include "LinkManager.h"
#include "MultiSignalSpy.h"
......@@ -36,7 +33,7 @@
///
/// @author Don Gagne <don@thegagnes.com>
class LinkManagerTest : public QObject
class LinkManagerTest : public UnitTest
{
Q_OBJECT
......@@ -44,6 +41,9 @@ public:
LinkManagerTest(void);
private slots:
UT_DECLARE_DEFAULT_initTestCase
UT_DECLARE_DEFAULT_cleanupTestCase
void init(void);
void cleanup(void);
......@@ -69,9 +69,6 @@ private:
MultiSignalSpy* _multiSpy;
static const size_t _cSignals = maxSignalIndex;
const char* _rgSignals[_cSignals];
};
DECLARE_TEST(LinkManagerTest)
#endif
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009 - 2014 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
This file is part of the QGROUNDCONTROL project
QGROUNDCONTROL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
QGROUNDCONTROL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
======================================================================*/
/// @file
/// @brief The tests the unit test QGCMessageBox catching mechanism.
///
/// @author Don Gagne <don@thegagnes.com>
#include "MessageBoxTest.h"
#include "QGCMessageBox.h"
UT_REGISTER_TEST(MessageBoxTest)
MessageBoxTest::MessageBoxTest(void) :
_expectMissedMessageBox(false)
{
}
void MessageBoxTest::cleanup(void)
{
if (_expectMissedMessageBox) {
_expectMissedMessageBox = false;
QEXPECT_FAIL("", "Supposed to fail in cleanup with a missed message box", Continue);
}
UnitTest::cleanup();
}
void MessageBoxTest::_messageBoxExpected_test(void)
{
setExpectedMessageBox(QMessageBox::Ok);
QCOMPARE(QGCMessageBox::information(QString(), QString()), QMessageBox::Ok);
checkExpectedMessageBox();
}
void MessageBoxTest::_messageBoxUnexpected_test(void)
{
// This should cause the test to fail in the cleanup method
QGCMessageBox::information(QString(), QString());
_expectMissedMessageBox = true;
}
void MessageBoxTest::_previousMessageBox_test(void)
{
// This is the previous unexpected message box
QGCMessageBox::information(QString(), QString());
// Setup for an expected message box.
QEXPECT_FAIL("", "Expecting failure due to previous message boxes", Continue);
setExpectedMessageBox(QMessageBox::Ok);
}
void MessageBoxTest::_noMessageBox_test(void)
{
setExpectedMessageBox(QMessageBox::Ok);
checkExpectedMessageBox(expectFailNoMessageBox);
}
void MessageBoxTest::_badResponseButton_test(void)
{
setExpectedMessageBox(QMessageBox::Cancel);
// Will return Ok even though Cancel was specified, since that was wrong
QCOMPARE(QGCMessageBox::information(QString(), QString()), QMessageBox::Ok);
checkExpectedMessageBox(expectFailBadResponseButton);
}
/*=====================================================================
QGroundControl Open Source Ground Control Station
(c) 2009 - 2014 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
This file is part of the QGROUNDCONTROL project
QGROUNDCONTROL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
QGROUNDCONTROL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QGROUNDCONTROL. If not, see <http://www.gnu.org/licenses/>.
======================================================================*/
/// @file
/// @brief The tests the unit test QGCMessageBox catching mechanism.
///
/// @author Don Gagne <don@thegagnes.com>
#ifndef MESSAGEBOXTEST_H
#define MESSAGEBOXTEST_H
#include "UnitTest.h"
class MessageBoxTest : public UnitTest
{
Q_OBJECT
public:
MessageBoxTest(void);
private slots:
UT_DECLARE_DEFAULT_initTestCase
UT_DECLARE_DEFAULT_cleanupTestCase
UT_DECLARE_DEFAULT_init
void cleanup(void);
void _messageBoxExpected_test(void);
void _messageBoxUnexpected_test(void);
void _previousMessageBox_test(void);
void _noMessageBox_test(void);
void _badResponseButton_test(void);
private:
bool _expectMissedMessageBox;
};
#endif
......@@ -30,6 +30,8 @@
///
/// @author Don Gagne <don@thegagnes.com>
UT_REGISTER_TEST(PX4RCCalibrationTest)
// This will check for the wizard buttons being enabled of disabled according to the mask you pass in.
// We use a macro instead of a method so that we get better line number reporting on failure.
#define CHK_BUTTONS(mask) \
......@@ -137,6 +139,8 @@ PX4RCCalibrationTest::PX4RCCalibrationTest(void) :
/// @brief Called one time before any test cases are run.
void PX4RCCalibrationTest::initTestCase(void)
{
UnitTest::initTestCase();
// Validate that our function to channel mapping is still correct.
for (int function=0; function<PX4RCCalibration::rcCalFunctionMax; function++) {
int chanIndex = _rgFunctionChannelMap[function];
......@@ -149,6 +153,8 @@ void PX4RCCalibrationTest::initTestCase(void)
void PX4RCCalibrationTest::init(void)
{
UnitTest::init();
_mockUASManager = new MockUASManager();
Q_ASSERT(_mockUASManager);
......@@ -196,6 +202,8 @@ void PX4RCCalibrationTest::init(void)
void PX4RCCalibrationTest::cleanup(void)
{
UnitTest::cleanup();
Q_ASSERT(_calWidget);
delete _calWidget;
......
......@@ -24,7 +24,7 @@
#ifndef PX4RCCALIBRATIONTEST_H
#define PX4RCCALIBRATIONTEST_H
#include "AutoTest.h"
#include "UnitTest.h"
#include "MockUASManager.h"
#include "MockUAS.h"
#include "MultiSignalSpy.h"
......@@ -36,7 +36,7 @@
/// @author Don Gagne <don@thegagnes.com>
/// @brief PX4RCCalibration Widget unit test
class PX4RCCalibrationTest : public QObject
class PX4RCCalibrationTest : public UnitTest
{
Q_OBJECT
......@@ -45,6 +45,7 @@ public:
private slots:
void initTestCase(void);
UT_DECLARE_DEFAULT_cleanupTestCase
void init(void);
void cleanup(void);
......@@ -129,6 +130,4 @@ private:
static const int _rgFunctionChannelMap[PX4RCCalibration::rcCalFunctionMax];
};
DECLARE_TEST(PX4RCCalibrationTest)
#endif
......@@ -30,17 +30,20 @@
///
/// @author Don Gagne <don@thegagnes.com>
UT_REGISTER_TEST(QGCUASFileManagerUnitTest)
QGCUASFileManagerUnitTest::QGCUASFileManagerUnitTest(void) :
_mockFileServer(_systemIdQGC, _systemIdServer),
_fileManager(NULL),
_multiSpy(NULL)
{
}
// Called once before all test cases are run
void QGCUASFileManagerUnitTest::initTestCase(void)
{
UnitTest::initTestCase();
_mockUAS.setMockSystemId(_systemIdServer);
_mockUAS.setMockMavlinkPlugin(&_mockFileServer);
}
......@@ -48,6 +51,8 @@ void QGCUASFileManagerUnitTest::initTestCase(void)
// Called before every test case
void QGCUASFileManagerUnitTest::init(void)
{
UnitTest::init();
Q_ASSERT(_multiSpy == NULL);
_fileManager = new QGCUASFileManager(NULL, &_mockUAS, _systemIdQGC);
......@@ -77,6 +82,8 @@ void QGCUASFileManagerUnitTest::init(void)
// Called after every test case
void QGCUASFileManagerUnitTest::cleanup(void)
{
UnitTest::cleanup();
Q_ASSERT(_multiSpy);
Q_ASSERT(_fileManager);
......
......@@ -27,7 +27,7 @@
#include <QObject>
#include <QtTest/QtTest>
#include "AutoTest.h"
#include "UnitTest.h"
#include "MockUAS.h"
#include "MockMavlinkFileServer.h"
#include "QGCUASFileManager.h"
......@@ -38,7 +38,7 @@
///
/// @author Don Gagne <don@thegagnes.com>
class QGCUASFileManagerUnitTest : public QObject
class QGCUASFileManagerUnitTest : public UnitTest
{
Q_OBJECT
......@@ -48,6 +48,7 @@ public:
private slots:
// Test case initialization
void initTestCase(void);
UT_DECLARE_DEFAULT_cleanupTestCase
void init(void);
void cleanup(void);
......@@ -100,6 +101,4 @@ private:
QStringList _fileListReceived;
};
DECLARE_TEST(QGCUASFileManagerUnitTest)
#endif
......@@ -29,18 +29,24 @@
///
/// @author Don Gagne <don@thegagnes.com>
// This unit test has gotten too flaky to run reliably under TeamCity. Removing for now till there is
// time to debug.
//UT_REGISTER_TEST(TCPLinkUnitTest)
TCPLinkUnitTest::TCPLinkUnitTest(void) :