Commit 1b4239d3 authored by Don Gagne's avatar Don Gagne

QGCFileDialog UnitTest support

parent c9274137
......@@ -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 \
......
/*=====================================================================
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/>.
======================================================================*/
#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;
}
......@@ -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 <don@thegagnes.com>
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);
};
......
......@@ -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);
}
......@@ -32,10 +32,12 @@
#include <QObject>
#include <QtTest>
#include <QMessageBox>
#include <QFileDialog>
#define UT_REGISTER_TEST(className) static UnitTestWrapper<className> 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<QObject*>& _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
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment