From bd04903f73793e71ad44ea4dc63f0ab9af54e6c6 Mon Sep 17 00:00:00 2001 From: Don Gagne Date: Mon, 4 Aug 2014 20:33:26 -0700 Subject: [PATCH] Added the ability to simulate server errors Also changed the downloaded file contents to not include the file length in the first byte. This makes the code a bit less confusing. --- src/qgcunittest/MockMavlinkFileServer.cc | 62 ++++++++++++++++++++++++-------- src/qgcunittest/MockMavlinkFileServer.h | 35 ++++++++++++++++-- 2 files changed, 79 insertions(+), 18 deletions(-) diff --git a/src/qgcunittest/MockMavlinkFileServer.cc b/src/qgcunittest/MockMavlinkFileServer.cc index 3457a89..bbcbfed 100644 --- a/src/qgcunittest/MockMavlinkFileServer.cc +++ b/src/qgcunittest/MockMavlinkFileServer.cc @@ -23,25 +23,33 @@ #include "MockMavlinkFileServer.h" +const MockMavlinkFileServer::ErrorMode_t MockMavlinkFileServer::rgFailureModes[] = { + MockMavlinkFileServer::errModeNoResponse, + MockMavlinkFileServer::errModeNakResponse, + MockMavlinkFileServer::errModeNoSecondResponse, + MockMavlinkFileServer::errModeNakSecondResponse, + MockMavlinkFileServer::errModeBadCRC, +}; +const size_t MockMavlinkFileServer::cFailureModes = sizeof(MockMavlinkFileServer::rgFailureModes) / sizeof(MockMavlinkFileServer::rgFailureModes[0]); + const MockMavlinkFileServer::FileTestCase MockMavlinkFileServer::rgFileTestCases[MockMavlinkFileServer::cFileTestCases] = { // File fits one Read Ack packet, partially filling data - { "partial.qgc", sizeof(((QGCUASFileManager::Request*)0)->data) - 1 }, + { "partial.qgc", sizeof(((QGCUASFileManager::Request*)0)->data) - 1, false }, // File fits one Read Ack packet, exactly filling all data - { "exact.qgc", sizeof(((QGCUASFileManager::Request*)0)->data) }, + { "exact.qgc", sizeof(((QGCUASFileManager::Request*)0)->data), true }, // File is larger than a single Read Ack packets, requires multiple Reads - { "multi.qgc", sizeof(((QGCUASFileManager::Request*)0)->data) + 1 }, + { "multi.qgc", sizeof(((QGCUASFileManager::Request*)0)->data) + 1, true }, }; // We only support a single fixed session const uint8_t MockMavlinkFileServer::_sessionId = 1; -MockMavlinkFileServer::MockMavlinkFileServer(void) +MockMavlinkFileServer::MockMavlinkFileServer(void) : + _errMode(errModeNone) { } - - /// @brief Handles List command requests. Only supports root folder paths. /// File list returned is set using the setFileList method. void MockMavlinkFileServer::_listCommand(QGCUASFileManager::Request* request) @@ -83,6 +91,13 @@ void MockMavlinkFileServer::_listCommand(QGCUASFileManager::Request* request) } _emitResponse(&ackResponse); + } else if (_errMode == errModeNakSecondResponse) { + // Nak error all subsequent requests + _sendNak(QGCUASFileManager::kErrPerm); + return; + } else if (_errMode == errModeNoSecondResponse) { + // No response for all subsequent requests + return; } else { // FIXME: Does not support directories that span multiple packets _sendNak(QGCUASFileManager::kErrEOF); @@ -136,22 +151,26 @@ void MockMavlinkFileServer::_readCommand(QGCUASFileManager::Request* request) uint32_t readOffset = request->hdr.offset; // offset into file for reading uint8_t cDataBytes = 0; // current number of data bytes used + if (readOffset != 0) { + // If we get here it means the client is requesting additional data past the first request + if (_errMode == errModeNakSecondResponse) { + // Nak error all subsequent requests + _sendNak(QGCUASFileManager::kErrPerm); + return; + } else if (_errMode == errModeNoSecondResponse) { + // No rsponse for all subsequent requests + return; + } + } + if (readOffset >= _readFileLength) { _sendNak(QGCUASFileManager::kErrEOF); return; } - // Write length byte if needed - if (readOffset == 0) { - response.data[0] = _readFileLength; - readOffset++; - cDataBytes++; - } - // Write file bytes. Data is a repeating sequence of 0x00, 0x01, .. 0xFF. for (; cDataBytes < sizeof(response.data) && readOffset < _readFileLength; readOffset++, cDataBytes++) { - // Subtract one from readOffset to take into account length byte and start file data a 0x00 - response.data[cDataBytes] = (readOffset - 1) & 0xFF; + response.data[cDataBytes] = readOffset & 0xFF; } // We should always have written something, otherwise there is something wrong with the code above @@ -187,6 +206,15 @@ void MockMavlinkFileServer::sendMessage(mavlink_message_t message) QGCUASFileManager::Request ackResponse; Q_ASSERT(message.msgid == MAVLINK_MSG_ID_ENCAPSULATED_DATA); + + if (_errMode == errModeNoResponse) { + // Don't respond to any requests, this shold cause the client to eventually timeout waiting for the ack + return; + } else if (_errMode == errModeNakResponse) { + // Nak all requests, the actual error send back doesn't really matter as long as it's an error + _sendNak(QGCUASFileManager::kErrPerm); + return; + } mavlink_encapsulated_data_t requestEncapsulatedData; mavlink_msg_encapsulated_data_decode(&message, &requestEncapsulatedData); @@ -280,6 +308,10 @@ void MockMavlinkFileServer::_emitResponse(QGCUASFileManager::Request* request) mavlink_message_t mavlinkMessage; request->hdr.crc32 = QGCUASFileManager::crc32(request); + if (_errMode == errModeBadCRC) { + // Return a bad CRC + request->hdr.crc32++; + } mavlink_msg_encapsulated_data_pack(250, MAV_COMP_ID_IMU, &mavlinkMessage, 0 /*_encdata_seq*/, (uint8_t*)request); diff --git a/src/qgcunittest/MockMavlinkFileServer.h b/src/qgcunittest/MockMavlinkFileServer.h index 7c581c7..fd00339 100644 --- a/src/qgcunittest/MockMavlinkFileServer.h +++ b/src/qgcunittest/MockMavlinkFileServer.h @@ -46,18 +46,46 @@ public: /// to indicate (F)ile or (D)irectory. void setFileList(QStringList& fileList) { _fileList = fileList; } + /// @brief By calling setErrorMode with one of these modes you can cause the server to simulate an error. + typedef enum { + errModeNone, ///< No error, respond correctly + errModeNoResponse, ///< No response to any request, client should eventually time out with no Ack + errModeNakResponse, ///< Nak all requests + errModeNoSecondResponse, ///< No response to subsequent request to initial command + errModeNakSecondResponse, ///< Nak subsequent request to initial command + errModeBadCRC, ///< Return response with bad CRC + errModeBadSequence ///< Return response with bad sequence number, NYI: Waiting on Firmware sequence # support + } ErrorMode_t; + + /// @brief Sets the error mode for command responses. This allows you to simulate various server errors. + void setErrorMode(ErrorMode_t errMode) { _errMode = errMode; }; + + /// @brief Array of failure modes you can cycle through for testing. By looping through this array you can avoid + /// hardcoding the specific error modes in your unit test. This way when new error modes are added your unit test + /// code may not need to be modified. + static const ErrorMode_t rgFailureModes[]; + + /// @brief The number of ErrorModes in the rgFailureModes array. + static const size_t cFailureModes; + // From MockMavlinkInterface virtual void sendMessage(mavlink_message_t message); + /// @brief Used to represent a single test case for download testing. struct FileTestCase { - const char* filename; - uint8_t length; + const char* filename; ///< Filename to download + uint8_t length; ///< Length of file in bytes + bool fMultiPacketResponse; ///< true: multiple acks required to download, false: single ack contains entire download }; + /// @brief The numbers of test cases in the rgFileTestCases array. static const size_t cFileTestCases = 3; + + /// @brief The set of files supported by the mock server for testing purposes. Each one represents a different edge case for testing. static const FileTestCase rgFileTestCases[cFileTestCases]; signals: + /// @brief You can connect to this signal to be notified when the server receives a Terminate command. void terminateCommandReceived(void); private: @@ -72,7 +100,8 @@ private: QStringList _fileList; ///< List of files returned by List command static const uint8_t _sessionId; - uint8_t _readFileLength; ///< Length of active file being read + uint8_t _readFileLength; ///< Length of active file being read + ErrorMode_t _errMode; ///< Currently set error mode, as specified by setErrorMode }; #endif