/*===================================================================== 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 "QGCUASFileManagerTest.h" /// @file /// @brief QGCUASFileManager unit test. Note: All code here assumes all work between /// the unit test, mack mavlink file server and file manager is happening on /// the same thread. /// /// @author Don Gagne 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) { _mockUAS = new MockUAS(); Q_CHECK_PTR(_mockUAS); _mockUAS->setMockSystemId(_systemIdServer); _mockUAS->setMockMavlinkPlugin(&_mockFileServer); } void QGCUASFileManagerUnitTest::cleanupTestCase(void) { delete _mockUAS; } // Called before every test case void QGCUASFileManagerUnitTest::init(void) { UnitTest::init(); Q_ASSERT(_multiSpy == NULL); _fileManager = new QGCUASFileManager(NULL, _mockUAS, _systemIdQGC); Q_CHECK_PTR(_fileManager); // Reset any internal state back to normal _mockFileServer.setErrorMode(MockMavlinkFileServer::errModeNone); _fileListReceived.clear(); connect(&_mockFileServer, &MockMavlinkFileServer::messageReceived, _fileManager, &QGCUASFileManager::receiveMessage); connect(_fileManager, &QGCUASFileManager::listEntry, this, &QGCUASFileManagerUnitTest::listEntry); _rgSignals[listEntrySignalIndex] = SIGNAL(listEntry(const QString&)); _rgSignals[listCompleteSignalIndex] = SIGNAL(listComplete(void)); _rgSignals[downloadFileLengthSignalIndex] = SIGNAL(downloadFileLength(unsigned int)); _rgSignals[downloadFileCompleteSignalIndex] = SIGNAL(downloadFileComplete(void)); _rgSignals[errorMessageSignalIndex] = SIGNAL(errorMessage(const QString&)); _multiSpy = new MultiSignalSpy(); Q_CHECK_PTR(_multiSpy); QCOMPARE(_multiSpy->init(_fileManager, _rgSignals, _cSignals), true); } // Called after every test case void QGCUASFileManagerUnitTest::cleanup(void) { Q_ASSERT(_multiSpy); Q_ASSERT(_fileManager); delete _fileManager; delete _multiSpy; _fileManager = NULL; _multiSpy = NULL; UnitTest::cleanup(); } /// @brief Connected to QGCUASFileManager listEntry signal in order to catch list entries void QGCUASFileManagerUnitTest::listEntry(const QString& entry) { // Keep a list of all names received so we can test it for correctness _fileListReceived += entry; } void QGCUASFileManagerUnitTest::_ackTest(void) { Q_ASSERT(_fileManager); Q_ASSERT(_multiSpy); Q_ASSERT(_multiSpy->checkNoSignals() == true); // If the file manager doesn't receive an ack it will timeout and emit an error. So make sure // we don't get any error signals. QVERIFY(_fileManager->_sendCmdTestAck()); QTest::qWait(_ackTimerTimeoutMsecs); // Let the file manager timeout QVERIFY(_multiSpy->checkNoSignals()); // Setup for no response from ack. This should cause a timeout error _mockFileServer.setErrorMode(MockMavlinkFileServer::errModeNoResponse); QVERIFY(_fileManager->_sendCmdTestAck()); QTest::qWait(_ackTimerTimeoutMsecs); // Let the file manager timeout QCOMPARE(_multiSpy->checkOnlySignalByMask(errorMessageSignalMask), true); _multiSpy->clearAllSignals(); // Setup for a bad sequence number in the ack. This should cause an error; _mockFileServer.setErrorMode(MockMavlinkFileServer::errModeBadSequence); QVERIFY(_fileManager->_sendCmdTestAck()); QCOMPARE(_multiSpy->checkOnlySignalByMask(errorMessageSignalMask), true); _multiSpy->clearAllSignals(); } void QGCUASFileManagerUnitTest::_noAckTest(void) { Q_ASSERT(_fileManager); Q_ASSERT(_multiSpy); Q_ASSERT(_multiSpy->checkNoSignals() == true); // This should not get the ack back and timeout. QVERIFY(_fileManager->_sendCmdTestNoAck()); QTest::qWait(_ackTimerTimeoutMsecs); // Let the file manager timeout QCOMPARE(_multiSpy->checkOnlySignalByMask(errorMessageSignalMask), true); } void QGCUASFileManagerUnitTest::_resetTest(void) { Q_ASSERT(_fileManager); Q_ASSERT(_multiSpy); Q_ASSERT(_multiSpy->checkNoSignals() == true); // Send a reset command // We should not get any signals back from this QVERIFY(_fileManager->_sendCmdReset()); QVERIFY(_multiSpy->checkNoSignals()); } void QGCUASFileManagerUnitTest::_listTest(void) { Q_ASSERT(_fileManager); Q_ASSERT(_multiSpy); Q_ASSERT(_multiSpy->checkNoSignals() == true); // QGCUASFileManager::listDirectory signalling as follows: // Emits a listEntry signal for each list entry // Emits an errorMessage signal if: // It gets a Nak back // Sequence number is incorrrect on any response // CRC is incorrect on any responses // List entry is formatted incorrectly // It is possible to get a number of good listEntry signals, followed by an errorMessage signal // Emits listComplete after it receives the final list entry // If an errorMessage signal is signalled no listComplete is signalled // Send a bogus path // We should get a single resetStatusMessages signal // We should get a single errorMessage signal _fileManager->listDirectory("/bogus"); QCOMPARE(_multiSpy->checkOnlySignalByMask(errorMessageSignalMask), true); _multiSpy->clearAllSignals(); // Setup the mock file server with a valid directory list QStringList fileList; fileList << "Ddir" << "Ffoo" << "Fbar"; _mockFileServer.setFileList(fileList); // Run through the various server side failure modes for (size_t i=0; ilistDirectory("/"); QTest::qWait(_ackTimerTimeoutMsecs); // Let the file manager timeout if (errMode == MockMavlinkFileServer::errModeNoSecondResponse || errMode == MockMavlinkFileServer::errModeNakSecondResponse) { // For simulated server errors on subsequent Acks, the first Ack will go through. This means we should have gotten some // partial results. In the case of the directory list test set, all entries fit into the first ack, so we should have // gotten back all of them. QCOMPARE(_multiSpy->getSpyByIndex(listEntrySignalIndex)->count(), fileList.count()); _multiSpy->clearSignalByIndex(listEntrySignalIndex); // And then it should have errored out because the next list Request would have failed. QCOMPARE(_multiSpy->checkOnlySignalByMask(errorMessageSignalMask), true); } else { // For the simulated errors which failed the intial response we should not have gotten any results back at all. // Just an error. QCOMPARE(_multiSpy->checkOnlySignalByMask(errorMessageSignalMask), true); } // Set everything back to initial state _fileListReceived.clear(); _multiSpy->clearAllSignals(); _mockFileServer.setErrorMode(MockMavlinkFileServer::errModeNone); } // Send a list command at the root of the directory tree which should succeed _fileManager->listDirectory("/"); QCOMPARE(_multiSpy->checkSignalByMask(listCompleteSignalMask), true); QCOMPARE(_multiSpy->checkNoSignalByMask(errorMessageSignalMask), true); QCOMPARE(_multiSpy->getSpyByIndex(listEntrySignalIndex)->count(), fileList.count()); QVERIFY(_fileListReceived == fileList); } void QGCUASFileManagerUnitTest::_validateFileContents(const QString& filePath, uint8_t length) { QFile file(filePath); // Make sure file size is correct QCOMPARE(file.size(), (qint64)length); // Read data QVERIFY(file.open(QIODevice::ReadOnly)); QByteArray bytes = file.readAll(); file.close(); // Validate file contents: // Repeating 0x00, 0x01 .. 0xFF until file is full for (uint8_t i=0; icheckNoSignals() == true); // QGCUASFileManager::downloadPath works as follows: // Sends an Open Command to the server // Expects an Ack Response back from the server with the correct sequence numner // Emits an errorMessage signal if it gets a Nak back // Emits an downloadFileLength signal with the file length if it gets back a good Ack // Sends subsequent Read commands to the server until it gets the full file contents back // Emits a downloadFileProgress for each read command ack it gets back // Sends Terminate command to server when download is complete to close Open command // Mock file server will signal terminateCommandReceived when it gets a Terminate command // Sends downloadFileComplete signal to indicate the download is complete // Emits an errorMessage signal if sequence number is incorrrect on any response // Emits an errorMessage signal if CRC is incorrect on any responses // Expected signals if the Open command fails for any reason quint16 signalMaskOpenFailure = errorMessageSignalMask; // Expected signals if the Read command fails for any reason quint16 signalMaskReadFailure = downloadFileLengthSignalMask | errorMessageSignalMask; // Expected signals if the downloadPath command succeeds quint16 signalMaskDownloadSuccess = downloadFileLengthSignalMask | downloadFileCompleteSignalMask; // Send a bogus path // We should get a single resetStatusMessages signal // We should get a single errorMessage signal _fileManager->downloadPath("bogus", QDir::temp()); QCOMPARE(_multiSpy->checkOnlySignalByMask(signalMaskOpenFailure), true); _multiSpy->clearAllSignals(); // Clean previous downloads for (size_t i=0; idownloadPath(testCase->filename, QDir::temp()); QTest::qWait(_ackTimerTimeoutMsecs); // Let the file manager timeout if (errMode == MockMavlinkFileServer::errModeNoSecondResponse || errMode == MockMavlinkFileServer::errModeNakSecondResponse) { // For simulated server errors on subsequent Acks, the first Ack will go through. We must handle things differently depending // on whether the downloaded file requires multiple packets to complete the download. if (testCase->fMultiPacketResponse) { // The downloaded file requires multiple Acks to complete. Hence first Read should have succeeded and sent one downloadFileComplete. // Second Read should have failed. QCOMPARE(_multiSpy->checkOnlySignalByMask(signalMaskReadFailure), true); // Open command succeeded, so we should get a Terminate for the open QCOMPARE(terminateSpy.count(), 1); } else { // The downloaded file fits within a single Ack response, hence there is no second Read issued. // This should result in a successful download. QCOMPARE(_multiSpy->checkOnlySignalByMask(signalMaskDownloadSuccess), true); // We should get a single Terminate command to close the Open session QCOMPARE(terminateSpy.count(), 1); // Validate file contents QString filePath = QDir::temp().absoluteFilePath(testCase->filename); _validateFileContents(filePath, testCase->length); } } else { // For all the other simulated server errors the Open command should have failed. Since the Open failed // there is no session to terminate, hence no Terminate in this case. QCOMPARE(_multiSpy->checkOnlySignalByMask(signalMaskOpenFailure), true); QCOMPARE(terminateSpy.count(), 0); } // Cleanup for next iteration _multiSpy->clearAllSignals(); terminateSpy.clear(); _mockFileServer.setErrorMode(MockMavlinkFileServer::errModeNone); } // Run what should be a successful file download test case. No servers errors are being simulated. _fileManager->downloadPath(testCase->filename, QDir::temp()); // This should be a succesful download QCOMPARE(_multiSpy->checkOnlySignalByMask(signalMaskDownloadSuccess), true); // Make sure the file length coming back through the openFileLength signal is correct QVERIFY(_multiSpy->getSpyByIndex(downloadFileLengthSignalIndex)->takeFirst().at(0).toInt() == testCase->length); _multiSpy->clearAllSignals(); // We should get a single Terminate command to close the session QCOMPARE(terminateSpy.count(), 1); terminateSpy.clear(); // Validate file contents QString filePath = QDir::temp().absoluteFilePath(MockMavlinkFileServer::rgFileTestCases[i].filename); _validateFileContents(filePath, MockMavlinkFileServer::rgFileTestCases[i].length); } }