From 1b4239d39bbf3b986d986de9b378132555d43807 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Sun, 30 Nov 2014 15:13:58 -0800 Subject: [PATCH] QGCFileDialog UnitTest support --- qgroundcontrol.pro | 8 +- src/QGCFileDialog.cc | 115 +++++++++++++++++++ src/QGCFileDialog.h | 41 +++++-- src/qgcunittest/UnitTest.cc | 220 ++++++++++++++++++++++++++++++------ src/qgcunittest/UnitTest.h | 85 +++++++++++--- 5 files changed, 408 insertions(+), 61 deletions(-) create mode 100644 src/QGCFileDialog.cc diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index cd61aa08b..ed1ea5698 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -615,7 +615,9 @@ SOURCES += \ src/ui/QGCUASFileView.cc \ src/uas/QGCUASWorker.cc \ src/CmdLineOptParser.cc \ - src/uas/QGXPX4UAS.cc + src/uas/QGXPX4UAS.cc \ + src/QGCFileDialog.cc + # # Unit Test specific configuration goes here @@ -636,6 +638,7 @@ INCLUDEPATH += \ HEADERS += \ src/qgcunittest/UnitTest.h \ src/qgcunittest/MessageBoxTest.h \ + src/qgcunittest/FileDialogTest.h \ src/qgcunittest/UASUnitTest.h \ src/qgcunittest/MockLink.h \ src/qgcunittest/MockLinkMissionItemHandler.h \ @@ -655,8 +658,9 @@ HEADERS += \ SOURCES += \ src/qgcunittest/UnitTest.cc \ - src/qgcunittest/UASUnitTest.cc \ src/qgcunittest/MessageBoxTest.cc \ + src/qgcunittest/FileDialogTest.cc \ + src/qgcunittest/UASUnitTest.cc \ src/qgcunittest/MockLink.cc \ src/qgcunittest/MockLinkMissionItemHandler.cc \ src/qgcunittest/MockUASManager.cc \ diff --git a/src/QGCFileDialog.cc b/src/QGCFileDialog.cc new file mode 100644 index 000000000..9db00be5e --- /dev/null +++ b/src/QGCFileDialog.cc @@ -0,0 +1,115 @@ +/*===================================================================== + + QGroundControl Open Source Ground Control Station + + (c) 2009 - 2014 QGROUNDCONTROL PROJECT + + 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 . + + ======================================================================*/ + +#include "QGCFileDialog.h" +#include "QGCApplication.h" +#ifdef QT_DEBUG +#include "UnitTest.h" +#endif + +QString QGCFileDialog::getExistingDirectory(QWidget* parent, + const QString& caption, + const QString& dir, + Options options) +{ + _validate(NULL, options); + +#ifdef QT_DEBUG + if (qgcApp()->runningUnitTests()) { + return UnitTest::_getExistingDirectory(parent, caption, dir, options); + } else +#endif + { + return QFileDialog::getExistingDirectory(parent, caption, dir, options); + } +} + +QString QGCFileDialog::getOpenFileName(QWidget* parent, + const QString& caption, + const QString& dir, + const QString& filter, + QString* selectedFilter, + Options options) +{ + _validate(selectedFilter, options); + +#ifdef QT_DEBUG + if (qgcApp()->runningUnitTests()) { + return UnitTest::_getOpenFileName(parent, caption, dir, filter, selectedFilter, options); + } else +#endif + { + return QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); + } +} + +QStringList QGCFileDialog::getOpenFileNames(QWidget* parent, + const QString& caption, + const QString& dir, + const QString& filter, + QString* selectedFilter, + Options options) +{ + _validate(selectedFilter, options); + +#ifdef QT_DEBUG + if (qgcApp()->runningUnitTests()) { + return UnitTest::_getOpenFileNames(parent, caption, dir, filter, selectedFilter, options); + } else +#endif + { + return QFileDialog::getOpenFileNames(parent, caption, dir, filter, selectedFilter, options); + } +} + +QString QGCFileDialog::getSaveFileName(QWidget* parent, + const QString& caption, + const QString& dir, + const QString& filter, + QString* selectedFilter, + Options options) +{ + _validate(selectedFilter, options); + +#ifdef QT_DEBUG + if (qgcApp()->runningUnitTests()) { + return UnitTest::_getSaveFileName(parent, caption, dir, filter, selectedFilter, options); + } else +#endif + { + return QFileDialog::getSaveFileName(parent, caption, dir, filter, selectedFilter, options); + } +} + +/// @brief Validates and updates the parameters for the file dialog calls +void QGCFileDialog::_validate(QString* selectedFilter, Options& options) +{ + // You can't use QGCFileDialog if QGCApplication is not created yet. + Q_ASSERT(qgcApp()); + + // Support for selectedFilter is not yet implemented through the unit test framework + Q_ASSERT(selectedFilter == NULL); + + // On OSX native dialog can hang so we always use Qt dialogs + options | DontUseNativeDialog; +} diff --git a/src/QGCFileDialog.h b/src/QGCFileDialog.h index 9b3426122..b8ff76fa7 100644 --- a/src/QGCFileDialog.h +++ b/src/QGCFileDialog.h @@ -29,23 +29,46 @@ /// @file /// @brief Subclass of QFileDialog which re-implements the static public functions. The reason for this /// is that the QFileDialog implementations of these use the native os dialogs. On OSX these -/// these can intermittently hang. So instead here we use the native dialogs. +/// these can intermittently hang. So instead here we use the native dialogs. It also allows +/// use to catch these dialogs for unit testing. /// @author Don Gagne class QGCFileDialog : public QFileDialog { public: - static QString getExistingDirectory(QWidget* parent = 0, const QString& caption = QString(), const QString& dir = QString(), Options options = ShowDirsOnly) - { return QFileDialog::getExistingDirectory(parent, caption, dir, options | DontUseNativeDialog); } + static QString getExistingDirectory(QWidget* parent = 0, + const QString& caption = QString(), + const QString& dir = QString(), + Options options = ShowDirsOnly); - static QString getOpenFileName(QWidget* parent = 0, const QString& caption = QString(), const QString& dir = QString(), const QString& filter = QString(), QString* selectedFilter = 0, Options options = 0) - { return QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options | DontUseNativeDialog); } + static QString getOpenFileName(QWidget* parent = 0, + const QString& caption = QString(), + const QString& dir = QString(), + const QString& filter = QString(), + QString* selectedFilter = 0, + Options options = 0); - static QStringList getOpenFileNames(QWidget* parent = 0, const QString& caption = QString(), const QString& dir = QString(), const QString& filter = QString(), QString* selectedFilter = 0, Options options = 0) - { return QFileDialog::getOpenFileNames(parent, caption, dir, filter, selectedFilter, options | DontUseNativeDialog); } + static QStringList getOpenFileNames(QWidget* parent = 0, + const QString& caption = QString(), + const QString& dir = QString(), + const QString& filter = QString(), + QString* selectedFilter = 0, + Options options = 0); - static QString getSaveFileName(QWidget* parent = 0, const QString& caption = QString(), const QString& dir = QString(), const QString& filter = QString(), QString* selectedFilter = 0, Options options = 0) - { return QFileDialog::getSaveFileName(parent, caption, dir, filter, selectedFilter, options | DontUseNativeDialog); } + static QString getSaveFileName(QWidget* parent = 0, + const QString& caption = QString(), + const QString& dir = QString(), + const QString& filter = QString(), + QString* selectedFilter = 0, + Options options = 0); + +private slots: + /// @brief The exec slot is private becasue when only want QGCFileDialog users to use the static methods. Otherwise it will break + /// unit testing. + int exec(void) { return QGCFileDialog::exec(); } + +private: + static void _validate(QString* selectedFilter, Options& options); }; diff --git a/src/qgcunittest/UnitTest.cc b/src/qgcunittest/UnitTest.cc index 1011c99ab..4c2869d25 100644 --- a/src/qgcunittest/UnitTest.cc +++ b/src/qgcunittest/UnitTest.cc @@ -34,10 +34,16 @@ 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) : + _expectMissedFileDialog(false), + _expectMissedMessageBox(false), _unitTestRun(false), - _initTestCaseCalled(false), - _cleanupTestCaseCalled(false), _initCalled(false), _cleanupCalled(false) { @@ -48,8 +54,6 @@ UnitTest::~UnitTest() { if (_unitTestRun) { // Derived classes must call base class implementations - Q_ASSERT(_initTestCaseCalled); - Q_ASSERT(_cleanupTestCaseCalled); Q_ASSERT(_initCalled); Q_ASSERT(_cleanupCalled); } @@ -89,29 +93,6 @@ int UnitTest::run(int argc, char *argv[], QString& singleTest) return ret; } -/// @brief Called at the initialization of the entire unit test. -/// Make sure to call first in your derived class -void UnitTest::initTestCase(void) -{ - _initTestCaseCalled = true; - - _missedMessageBoxCount = 0; - _badResponseButton = false; -} - -/// @brief Called at the end of the entire unit test. -/// Make sure to call first in your derived class -void UnitTest::cleanupTestCase(void) -{ - _cleanupTestCaseCalled = true; - - int missedMessageBoxCount = _missedMessageBoxCount; - _missedMessageBoxCount = 0; - - // Make sure to reset any needed variables since this can fall and cause the rest of the method to be skipped - QCOMPARE(missedMessageBoxCount, 0); -} - /// @brief Called before each test. /// Make sure to call first in your derived class void UnitTest::init(void) @@ -122,7 +103,15 @@ void UnitTest::init(void) _missedMessageBoxCount = 0; _badResponseButton = false; _messageBoxResponseButton = QMessageBox::NoButton; + + _fileDialogRespondedTo = false; + _missedFileDialogCount = 0; + _fileDialogResponseSet = false; + _fileDialogResponse.clear(); + _expectMissedFileDialog = false; + _expectMissedMessageBox = false; + // Each test gets a clean global state qgcApp()->destroySingletonsForUnitTest(); qgcApp()->createSingletonsForUnitTest(); @@ -133,30 +122,52 @@ void UnitTest::init(void) void UnitTest::cleanup(void) { _cleanupCalled = true; - - int missedMessageBoxCount = _missedMessageBoxCount; - _missedMessageBoxCount = 0; - - // Make sure to reset any needed variables since this can fall and cause the rest of the method to be skipped - QCOMPARE(missedMessageBoxCount, 0); + + // 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); - // We use an obsolete StandardButton value to signal that response button has not been set. So you can't use this. 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 @@ -169,10 +180,36 @@ void UnitTest::checkExpectedMessageBox(int expectFailFlags) } QCOMPARE(_badResponseButton, false); - if (expectFailFlags & expectFailNoMessageBox) { + if (expectFailFlags & expectFailNoDialog) { QEXPECT_FAIL("", "Expecting failure due to no message box", Continue); } - QCOMPARE(_messageBoxRespondedTo, true); + + // 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); } QMessageBox::StandardButton UnitTest::_messageBox(QMessageBox::Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) @@ -205,3 +242,112 @@ QMessageBox::StandardButton UnitTest::_messageBox(QMessageBox::Icon icon, const 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, + QString* selectedFilter, + QFileDialog::Options options) +{ + Q_UNUSED(parent); + Q_UNUSED(caption); + Q_UNUSED(dir); + Q_UNUSED(filter); + Q_UNUSED(options); + + // Support for selectedFilter is not yet implemented + Q_ASSERT(selectedFilter == NULL); + + return _fileDialogResponseSingle(getOpenFileName); +} + +QStringList UnitTest::_getOpenFileNames(QWidget* parent, + const QString& caption, + const QString& dir, + const QString& filter, + QString* selectedFilter, + QFileDialog::Options options) +{ + Q_UNUSED(parent); + Q_UNUSED(caption); + Q_UNUSED(dir); + Q_UNUSED(filter); + Q_UNUSED(options); + + // Support for selectedFilter is not yet implemented + Q_ASSERT(selectedFilter == NULL); + + 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, + QString* selectedFilter, + QFileDialog::Options options) +{ + Q_UNUSED(parent); + Q_UNUSED(caption); + Q_UNUSED(dir); + Q_UNUSED(filter); + Q_UNUSED(options); + + // Support for selectedFilter is not yet implemented + Q_ASSERT(selectedFilter == NULL); + + return _fileDialogResponseSingle(getSaveFileName); +} diff --git a/src/qgcunittest/UnitTest.h b/src/qgcunittest/UnitTest.h index 531a9423d..cb27d3a20 100644 --- a/src/qgcunittest/UnitTest.h +++ b/src/qgcunittest/UnitTest.h @@ -32,10 +32,12 @@ #include #include #include +#include #define UT_REGISTER_TEST(className) static UnitTestWrapper t(#className); class QGCMessageBox; +class QGCFileDialog; class UnitTest; class UnitTest : public QObject @@ -56,16 +58,34 @@ public: /// @param response Response to take on message box void setExpectedMessageBox(QMessageBox::StandardButton response); + /// @brief Types for UnitTest::setExpectedFileDialog + enum FileDialogType { + getExistingDirectory, + getOpenFileName, + getOpenFileNames, + getSaveFileName + }; + + /// @brief Sets up for an expected QGCFileDialog + /// @param type Type of expected file dialog + /// @param response Files to return from call. Multiple files only supported by getOpenFileNames + void setExpectedFileDialog(enum FileDialogType type, QStringList response); + enum { expectFailNoFailure = 1 << 0, ///< not expecting any failures - expectFailNoMessageBox = 1 << 1, ///< expecting a failure due to no message box displayed - expectFailBadResponseButton = 1 << 2 ///< expecting a failure due to bad button response + expectFailNoDialog = 1 << 1, ///< expecting a failure due to no dialog displayed + expectFailBadResponseButton = 1 << 2, ///< expecting a failure due to bad button response (QGCMessageBox only) + expectFailWrongFileDialog = 1 << 3 ///< expecting one dialog type, got the wrong type (QGCFileDialog ony) }; /// @brief Check whether a message box was displayed and correctly responded to // @param Expected failure response flags void checkExpectedMessageBox(int expectFailFlags = expectFailNoFailure); + /// @brief Check whether a message box was displayed and correctly responded to + // @param Expected failure response flags + void checkExpectedFileDialog(int expectFailFlags = expectFailNoFailure); + /// @brief Adds a unit test to the list. Should only be called by UnitTestWrapper. static void _addTest(QObject* test); @@ -74,14 +94,6 @@ protected slots: // These are all pure virtuals to force the derived class to implement each one and in turn // call the UnitTest private implementation. - /// @brief Called at the initialization of the entire unit test. - /// Make sure to call _initTestCase first in your derived class. - virtual void initTestCase(void); - - /// @brief Called at the end of the entire unit test. - /// Make sure to call _cleanupTestCase first in your derived class. - virtual void cleanupTestCase(void); - /// @brief Called before each test. /// Make sure to call _init first in your derived class. virtual void init(void); @@ -103,24 +115,71 @@ protected: /// @brief Must be called first by derived class implementation void _cleanup(void); + bool _expectMissedFileDialog; // true: expect a missed file dialog, used for internal testing + bool _expectMissedMessageBox; // true: expect a missed message box, used for internal testing + private: // When the app is running in unit test mode the QGCMessageBox methods are re-routed here. - static QMessageBox::StandardButton _messageBox(QMessageBox::Icon icon, const QString& title, const QString& text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton); + + static QMessageBox::StandardButton _messageBox(QMessageBox::Icon icon, + const QString& title, + const QString& text, + QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton); // This allows the private call to _messageBox friend class QGCMessageBox; + // When the app is running in unit test mode the QGCFileDialog methods are re-routed here. + + static QString _getExistingDirectory(QWidget* parent, + const QString& caption, + const QString& dir, + QFileDialog::Options options); + + static QString _getOpenFileName(QWidget* parent, + const QString& caption, + const QString& dir, + const QString& filter, + QString* selectedFilter, + QFileDialog::Options options); + + static QStringList _getOpenFileNames(QWidget* parent, + const QString& caption, + const QString& dir, + const QString& filter, + QString* selectedFilter, + QFileDialog::Options options); + + static QString _getSaveFileName(QWidget* parent, + const QString& caption, + const QString& dir, + const QString& filter, + QString* selectedFilter, + QFileDialog::Options options); + + static QString _fileDialogResponseSingle(enum FileDialogType type); + + // This allows the private calls to the file dialog methods + friend class QGCFileDialog; + void _unitTestCalled(void); static QList& _testList(void); + // Catch QGCMessageBox calls static bool _messageBoxRespondedTo; ///< Message box was responded to static bool _badResponseButton; ///< Attempt to repond to expected message box with button not being displayed static QMessageBox::StandardButton _messageBoxResponseButton; ///< Response to next message box static int _missedMessageBoxCount; ///< Count of message box not checked with call to messageBoxWasDisplayed + // Catch QGCFileDialog calls + static bool _fileDialogRespondedTo; ///< File dialog was responded to + static bool _fileDialogResponseSet; ///< true: _fileDialogResponse was set by a call to UnitTest::setExpectedFileDialog + static QStringList _fileDialogResponse; ///< Response to next file dialog + static enum FileDialogType _fileDialogExpectedType; ///< type of file dialog expected to show + static int _missedFileDialogCount; ///< Count of file dialogs not checked with call to UnitTest::fileDialogWasDisplayed + bool _unitTestRun; ///< true: Unit Test was run - bool _initTestCaseCalled; ///< true: UnitTest::_initTestCase was called - bool _cleanupTestCaseCalled; ///< true: UnitTest::_cleanupTestCase was called bool _initCalled; ///< true: UnitTest::_init was called bool _cleanupCalled; ///< true: UnitTest::_cleanup was called }; -- 2.22.0