/**************************************************************************** * * (c) 2009-2016 QGROUNDCONTROL PROJECT * * QGroundControl is licensed according to the terms in the file * COPYING.md in the root of the source code directory. * ****************************************************************************/ /// @file /// @brief Base class for all unit tests /// /// @author Don Gagne #include "UnitTest.h" #include "QGCApplication.h" #include "MAVLinkProtocol.h" #include "MainWindow.h" #include "Vehicle.h" #include #include bool UnitTest::_messageBoxRespondedTo = false; bool UnitTest::_badResponseButton = false; QMessageBox::StandardButton UnitTest::_messageBoxResponseButton = QMessageBox::NoButton; int UnitTest::_missedMessageBoxCount = 0; bool UnitTest::_fileDialogRespondedTo = false; bool UnitTest::_fileDialogResponseSet = false; QStringList UnitTest::_fileDialogResponse; enum UnitTest::FileDialogType UnitTest::_fileDialogExpectedType = getOpenFileName; int UnitTest::_missedFileDialogCount = 0; UnitTest::UnitTest(void) : _linkManager(NULL) , _mockLink(NULL) , _mainWindow(NULL) , _vehicle(NULL) , _expectMissedFileDialog(false) , _expectMissedMessageBox(false) , _unitTestRun(false) , _initCalled(false) , _cleanupCalled(false) { } UnitTest::~UnitTest() { if (_unitTestRun) { // Derived classes must call base class implementations Q_ASSERT(_initCalled); Q_ASSERT(_cleanupCalled); } } void UnitTest::_addTest(QObject* test) { QList& tests = _testList(); Q_ASSERT(!tests.contains(test)); tests.append(test); } void UnitTest::_unitTestCalled(void) { _unitTestRun = true; } /// @brief Returns the list of unit tests. QList& UnitTest::_testList(void) { static QList tests; return tests; } int UnitTest::run(QString& singleTest) { int ret = 0; for (QObject* test: _testList()) { if (singleTest.isEmpty() || singleTest == test->objectName()) { QStringList args; args << "*" << "-maxwarnings" << "0"; ret += QTest::qExec(test, args); } } return ret; } /// @brief Called before each test. /// Make sure to call first in your derived class void UnitTest::init(void) { _initCalled = true; if (!_linkManager) { _linkManager = qgcApp()->toolbox()->linkManager(); connect(_linkManager, &LinkManager::linkDeleted, this, &UnitTest::_linkDeleted); } _linkManager->restart(); _messageBoxRespondedTo = false; _missedMessageBoxCount = 0; _badResponseButton = false; _messageBoxResponseButton = QMessageBox::NoButton; _fileDialogRespondedTo = false; _missedFileDialogCount = 0; _fileDialogResponseSet = false; _fileDialogResponse.clear(); _expectMissedFileDialog = false; _expectMissedMessageBox = false; MAVLinkProtocol::deleteTempLogFiles(); } /// @brief Called after each test. /// Make sure to call first in your derived class void UnitTest::cleanup(void) { _cleanupCalled = true; _disconnectMockLink(); _closeMainWindow(); // We add a slight delay here to allow for deleteLater and Qml cleanup QTest::qWait(200); // Keep in mind that any code below these QCOMPARE may be skipped if the compare fails if (_expectMissedMessageBox) { QEXPECT_FAIL("", "Expecting failure due internal testing", Continue); } QCOMPARE(_missedMessageBoxCount, 0); if (_expectMissedFileDialog) { QEXPECT_FAIL("", "Expecting failure due internal testing", Continue); } QCOMPARE(_missedFileDialogCount, 0); } void UnitTest::setExpectedMessageBox(QMessageBox::StandardButton response) { // This means that there was an expected message box but no call to checkExpectedMessageBox Q_ASSERT(!_messageBoxRespondedTo); Q_ASSERT(response != QMessageBox::NoButton); Q_ASSERT(_messageBoxResponseButton == QMessageBox::NoButton); // Make sure we haven't missed any previous message boxes int missedMessageBoxCount = _missedMessageBoxCount; _missedMessageBoxCount = 0; QCOMPARE(missedMessageBoxCount, 0); _messageBoxResponseButton = response; } void UnitTest::setExpectedFileDialog(enum FileDialogType type, QStringList response) { // This means that there was an expected file dialog but no call to checkExpectedFileDialog Q_ASSERT(!_fileDialogRespondedTo); // Multiple responses must be expected getOpenFileNames Q_ASSERT(response.count() <= 1 || type == getOpenFileNames); // Make sure we haven't missed any previous file dialogs int missedFileDialogCount = _missedFileDialogCount; _missedFileDialogCount = 0; QCOMPARE(missedFileDialogCount, 0); _fileDialogResponseSet = true; _fileDialogResponse = response; _fileDialogExpectedType = type; } void UnitTest::checkExpectedMessageBox(int expectFailFlags) { // Previous call to setExpectedMessageBox should have already checked this Q_ASSERT(_missedMessageBoxCount == 0); // Check for a valid response if (expectFailFlags & expectFailBadResponseButton) { QEXPECT_FAIL("", "Expecting failure due to bad button response", Continue); } QCOMPARE(_badResponseButton, false); if (expectFailFlags & expectFailNoDialog) { QEXPECT_FAIL("", "Expecting failure due to no message box", Continue); } // Clear this flag before QCOMPARE since anything after QCOMPARE will be skipped on failure bool messageBoxRespondedTo = _messageBoxRespondedTo; _messageBoxRespondedTo = false; QCOMPARE(messageBoxRespondedTo, true); } void UnitTest::checkMultipleExpectedMessageBox(int messageCount) { int missedMessageBoxCount = _missedMessageBoxCount; _missedMessageBoxCount = 0; QCOMPARE(missedMessageBoxCount, messageCount); } void UnitTest::checkExpectedFileDialog(int expectFailFlags) { // Internal testing if (expectFailFlags & expectFailNoDialog) { QEXPECT_FAIL("", "Expecting failure due to no file dialog", Continue); } if (expectFailFlags & expectFailWrongFileDialog) { QEXPECT_FAIL("", "Expecting failure due to incorrect file dialog", Continue); } else { // Previous call to setExpectedFileDialog should have already checked this Q_ASSERT(_missedFileDialogCount == 0); } // Clear this flag before QCOMPARE since anything after QCOMPARE will be skipped on failure bool fileDialogRespondedTo = _fileDialogRespondedTo; _fileDialogRespondedTo = false; QCOMPARE(fileDialogRespondedTo, true); } QMessageBox::StandardButton UnitTest::_messageBox(QMessageBox::Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) { QMessageBox::StandardButton retButton; Q_UNUSED(icon); Q_UNUSED(title); Q_UNUSED(text); if (_messageBoxResponseButton == QMessageBox::NoButton) { // If no response button is set it means we were not expecting this message box. Response with default _missedMessageBoxCount++; retButton = defaultButton; } else { if (_messageBoxResponseButton & buttons) { // Everything is correct, use the specified response retButton = _messageBoxResponseButton; } else { // Trying to respond with a button not in the dialog. This is an error. Respond with default _badResponseButton = true; retButton = defaultButton; } _messageBoxRespondedTo = true; } // Clear response for next message box _messageBoxResponseButton = QMessageBox::NoButton; return retButton; } /// @brief Response to a file dialog which returns a single file QString UnitTest::_fileDialogResponseSingle(enum FileDialogType type) { QString retFile; if (!_fileDialogResponseSet || _fileDialogExpectedType != type) { // If no response is set or the type does not match what we expected it means we were not expecting this file dialog. // Respond with no selection. _missedFileDialogCount++; } else { Q_ASSERT(_fileDialogResponse.count() <= 1); if (_fileDialogResponse.count() == 1) { retFile = _fileDialogResponse[0]; } _fileDialogRespondedTo = true; } // Clear response for next message box _fileDialogResponse.clear(); _fileDialogResponseSet = false; return retFile; } QString UnitTest::_getExistingDirectory( QWidget* parent, const QString& caption, const QString& dir, QFileDialog::Options options) { Q_UNUSED(parent); Q_UNUSED(caption); Q_UNUSED(dir); Q_UNUSED(options); return _fileDialogResponseSingle(getExistingDirectory); } QString UnitTest::_getOpenFileName( QWidget* parent, const QString& caption, const QString& dir, const QString& filter, QFileDialog::Options options) { Q_UNUSED(parent); Q_UNUSED(caption); Q_UNUSED(dir); Q_UNUSED(filter); Q_UNUSED(options); return _fileDialogResponseSingle(getOpenFileName); } QStringList UnitTest::_getOpenFileNames( QWidget* parent, const QString& caption, const QString& dir, const QString& filter, QFileDialog::Options options) { Q_UNUSED(parent); Q_UNUSED(caption); Q_UNUSED(dir); Q_UNUSED(filter); Q_UNUSED(options); QStringList retFiles; if (!_fileDialogResponseSet || _fileDialogExpectedType != getOpenFileNames) { // If no response is set or the type does not match what we expected it means we were not expecting this file dialog. // Respond with no selection. _missedFileDialogCount++; retFiles.clear(); } else { retFiles = _fileDialogResponse; _fileDialogRespondedTo = true; } // Clear response for next message box _fileDialogResponse.clear(); _fileDialogResponseSet = false; return retFiles; } QString UnitTest::_getSaveFileName( QWidget* parent, const QString& caption, const QString& dir, const QString& filter, const QString& defaultSuffix, QFileDialog::Options options) { Q_UNUSED(parent); Q_UNUSED(caption); Q_UNUSED(dir); Q_UNUSED(filter); Q_UNUSED(options); if(!defaultSuffix.isEmpty()) { Q_ASSERT(defaultSuffix.startsWith(".") == false); } return _fileDialogResponseSingle(getSaveFileName); } void UnitTest::_connectMockLink(MAV_AUTOPILOT autopilot) { Q_ASSERT(!_mockLink); switch (autopilot) { case MAV_AUTOPILOT_PX4: _mockLink = MockLink::startPX4MockLink(false); break; case MAV_AUTOPILOT_ARDUPILOTMEGA: _mockLink = MockLink::startAPMArduCopterMockLink(false); break; case MAV_AUTOPILOT_GENERIC: _mockLink = MockLink::startGenericMockLink(false); break; default: qWarning() << "Type not supported"; break; } // Wait for the Vehicle to get created QSignalSpy spyVehicle(qgcApp()->toolbox()->multiVehicleManager(), SIGNAL(parameterReadyVehicleAvailableChanged(bool))); QCOMPARE(spyVehicle.wait(10000), true); QVERIFY(qgcApp()->toolbox()->multiVehicleManager()->parameterReadyVehicleAvailable()); _vehicle = qgcApp()->toolbox()->multiVehicleManager()->activeVehicle(); QVERIFY(_vehicle); // Wait for plan request to complete if (!_vehicle->initialPlanRequestComplete()) { QSignalSpy spyPlan(_vehicle, SIGNAL(initialPlanRequestCompleteChanged(bool))); QCOMPARE(spyPlan.wait(10000), true); } } void UnitTest::_disconnectMockLink(void) { if (_mockLink) { QSignalSpy linkSpy(_linkManager, SIGNAL(linkDeleted(LinkInterface*))); _linkManager->disconnectLink(_mockLink); // Wait for link to go away linkSpy.wait(1000); QCOMPARE(linkSpy.count(), 1); _vehicle = NULL; } } void UnitTest::_linkDeleted(LinkInterface* link) { if (link == _mockLink) { _mockLink = NULL; } } void UnitTest::_createMainWindow(void) { _mainWindow = MainWindow::_create(); Q_CHECK_PTR(_mainWindow); } void UnitTest::_closeMainWindow(bool cancelExpected) { if (_mainWindow) { QSignalSpy mainWindowSpy(_mainWindow, SIGNAL(mainWindowClosed())); _mainWindow->close(); mainWindowSpy.wait(2000); QCOMPARE(mainWindowSpy.count(), cancelExpected ? 0 : 1); // This leaves enough time for any dangling Qml components to get cleaned up. // This prevents qWarning from bad references in Qml QTest::qWait(1000); } } QString UnitTest::createRandomFile(uint32_t byteCount) { QTemporaryFile tempFile; QTime time = QTime::currentTime(); qsrand((uint)time.msec()); tempFile.setAutoRemove(false); if (tempFile.open()) { for (uint32_t bytesWritten=0; bytesWrittentypeIsBool()) { fact->setRawValue(!fact->rawValue().toBool()); } else { if (increment == 0) { increment = 1; } fact->setRawValue(fact->rawValue().toDouble() + increment); } } void UnitTest::_missionItemsEqual(MissionItem& actual, MissionItem& expected) { QCOMPARE(static_cast(actual.command()), static_cast(expected.command())); QCOMPARE(static_cast(actual.frame()), static_cast(expected.frame())); QCOMPARE(actual.autoContinue(), expected.autoContinue()); QVERIFY(UnitTest::doubleNaNCompare(actual.param1(), expected.param1())); QVERIFY(UnitTest::doubleNaNCompare(actual.param2(), expected.param2())); QVERIFY(UnitTest::doubleNaNCompare(actual.param3(), expected.param3())); QVERIFY(UnitTest::doubleNaNCompare(actual.param4(), expected.param4())); QVERIFY(UnitTest::doubleNaNCompare(actual.param5(), expected.param5())); QVERIFY(UnitTest::doubleNaNCompare(actual.param6(), expected.param6())); QVERIFY(UnitTest::doubleNaNCompare(actual.param7(), expected.param7())); } bool UnitTest::fuzzyCompareLatLon(const QGeoCoordinate& coord1, const QGeoCoordinate& coord2) { return coord1.distanceTo(coord2) < 1.0; }