Skip to content
Snippets Groups Projects
UnitTest.cc 12.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • Don Gagne's avatar
    Don Gagne committed
    /*=====================================================================
     
     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 Base class for all unit tests
    ///
    ///     @author Don Gagne <don@thegagnes.com>
    
    #include "UnitTest.h"
    #include "QGCApplication.h"
    
    #include "MAVLinkProtocol.h"
    
    #include "MainWindow.h"
    
    #include "Vehicle.h"
    
    Don Gagne's avatar
    Don Gagne committed
    
    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)
    {    
    
    
    Don Gagne's avatar
    Don Gagne committed
    }
    
    UnitTest::~UnitTest()
    {
        if (_unitTestRun) {
            // Derived classes must call base class implementations
            Q_ASSERT(_initCalled);
            Q_ASSERT(_cleanupCalled);
        }
    }
    
    void UnitTest::_addTest(QObject* test)
    {
    
    Don Gagne's avatar
    Don Gagne committed
    	QList<QObject*>& tests = _testList();
    
        Q_ASSERT(!tests.contains(test));
    
    Don Gagne's avatar
    Don Gagne committed
        tests.append(test);
    
    Don Gagne's avatar
    Don Gagne committed
    }
    
    void UnitTest::_unitTestCalled(void)
    {
        _unitTestRun = true;
    }
    
    
    Don Gagne's avatar
    Don Gagne committed
    /// @brief Returns the list of unit tests.
    QList<QObject*>& UnitTest::_testList(void)
    {
    	static QList<QObject*> tests;
    	return tests;
    }
    
    
    int UnitTest::run(QString& singleTest)
    
    Don Gagne's avatar
    Don Gagne committed
    {
        int ret = 0;
        
    
    Don Gagne's avatar
    Don Gagne committed
        foreach (QObject* test, _testList()) {
    
    Don Gagne's avatar
    Don Gagne committed
            if (singleTest.isEmpty() || singleTest == test->objectName()) {
    
                QStringList args;
                args << "*" << "-maxwarnings" << "0";
                ret += QTest::qExec(test, args);
    
    Don Gagne's avatar
    Don Gagne committed
            }
        }
        
        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();
    
    Don Gagne's avatar
    Don Gagne committed
        
        _messageBoxRespondedTo = false;
        _missedMessageBoxCount = 0;
        _badResponseButton = false;
        _messageBoxResponseButton = QMessageBox::NoButton;
    
        
        _fileDialogRespondedTo = false;
        _missedFileDialogCount = 0;
        _fileDialogResponseSet = false;
        _fileDialogResponse.clear();
    
        _expectMissedFileDialog = false;
        _expectMissedMessageBox = false;
        
    
        MAVLinkProtocol::deleteTempLogFiles();
    
    Don Gagne's avatar
    Don Gagne committed
    }
    
    /// @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);
    
    Don Gagne's avatar
    Don Gagne committed
    }
    
    void UnitTest::setExpectedMessageBox(QMessageBox::StandardButton response)
    {
    
        // This means that there was an expected message box but no call to checkExpectedMessageBox
    
    Don Gagne's avatar
    Don Gagne committed
        Q_ASSERT(!_messageBoxRespondedTo);
        
        Q_ASSERT(response != QMessageBox::NoButton);
        Q_ASSERT(_messageBoxResponseButton == QMessageBox::NoButton);
        
    
        // Make sure we haven't missed any previous message boxes
    
    Don Gagne's avatar
    Don Gagne committed
        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;
    }
    
    
    Don Gagne's avatar
    Don Gagne committed
    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) {
    
    Don Gagne's avatar
    Don Gagne committed
            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::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);
    
    Don Gagne's avatar
    Don Gagne committed
    }
    
    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);
    
    dogmaphobic's avatar
    dogmaphobic committed
        if(!defaultSuffix.isEmpty()) {
    
            Q_ASSERT(defaultSuffix.startsWith(".") == false);
    
    dogmaphobic's avatar
    dogmaphobic committed
        }
    
        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(5000), true);
        QVERIFY(qgcApp()->toolbox()->multiVehicleManager()->parameterReadyVehicleAvailable());
    
        _vehicle = qgcApp()->toolbox()->multiVehicleManager()->activeVehicle();
        QVERIFY(_vehicle);
    
    }
    
    void UnitTest::_disconnectMockLink(void)
    {
        if (_mockLink) {
            QSignalSpy  linkSpy(_linkManager, SIGNAL(linkDeleted(LinkInterface*)));
    
    
    Don Gagne's avatar
    Don Gagne committed
            _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);