Commit 334f0187 authored by Lorenz Meier's avatar Lorenz Meier

Merge pull request #790 from DonLakeFlyer/BetterFileMgrUt

More comprehensive testing of FileManager error/edge cases
parents 0b85b0f7 b8594253
...@@ -23,25 +23,33 @@ ...@@ -23,25 +23,33 @@
#include "MockMavlinkFileServer.h" #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] = { const MockMavlinkFileServer::FileTestCase MockMavlinkFileServer::rgFileTestCases[MockMavlinkFileServer::cFileTestCases] = {
// File fits one Read Ack packet, partially filling data // 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 // 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 // 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 // We only support a single fixed session
const uint8_t MockMavlinkFileServer::_sessionId = 1; const uint8_t MockMavlinkFileServer::_sessionId = 1;
MockMavlinkFileServer::MockMavlinkFileServer(void) MockMavlinkFileServer::MockMavlinkFileServer(void) :
_errMode(errModeNone)
{ {
} }
/// @brief Handles List command requests. Only supports root folder paths. /// @brief Handles List command requests. Only supports root folder paths.
/// File list returned is set using the setFileList method. /// File list returned is set using the setFileList method.
void MockMavlinkFileServer::_listCommand(QGCUASFileManager::Request* request) void MockMavlinkFileServer::_listCommand(QGCUASFileManager::Request* request)
...@@ -83,6 +91,13 @@ void MockMavlinkFileServer::_listCommand(QGCUASFileManager::Request* request) ...@@ -83,6 +91,13 @@ void MockMavlinkFileServer::_listCommand(QGCUASFileManager::Request* request)
} }
_emitResponse(&ackResponse); _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 { } else {
// FIXME: Does not support directories that span multiple packets // FIXME: Does not support directories that span multiple packets
_sendNak(QGCUASFileManager::kErrEOF); _sendNak(QGCUASFileManager::kErrEOF);
...@@ -136,22 +151,26 @@ void MockMavlinkFileServer::_readCommand(QGCUASFileManager::Request* request) ...@@ -136,22 +151,26 @@ void MockMavlinkFileServer::_readCommand(QGCUASFileManager::Request* request)
uint32_t readOffset = request->hdr.offset; // offset into file for reading uint32_t readOffset = request->hdr.offset; // offset into file for reading
uint8_t cDataBytes = 0; // current number of data bytes used uint8_t cDataBytes = 0; // current number of data bytes used
if (readOffset >= _readFileLength) { if (readOffset != 0) {
_sendNak(QGCUASFileManager::kErrEOF); // 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; return;
} }
}
// Write length byte if needed if (readOffset >= _readFileLength) {
if (readOffset == 0) { _sendNak(QGCUASFileManager::kErrEOF);
response.data[0] = _readFileLength; return;
readOffset++;
cDataBytes++;
} }
// Write file bytes. Data is a repeating sequence of 0x00, 0x01, .. 0xFF. // Write file bytes. Data is a repeating sequence of 0x00, 0x01, .. 0xFF.
for (; cDataBytes < sizeof(response.data) && readOffset < _readFileLength; readOffset++, cDataBytes++) { 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 & 0xFF;
response.data[cDataBytes] = (readOffset - 1) & 0xFF;
} }
// We should always have written something, otherwise there is something wrong with the code above // We should always have written something, otherwise there is something wrong with the code above
...@@ -188,6 +207,15 @@ void MockMavlinkFileServer::sendMessage(mavlink_message_t message) ...@@ -188,6 +207,15 @@ void MockMavlinkFileServer::sendMessage(mavlink_message_t message)
Q_ASSERT(message.msgid == MAVLINK_MSG_ID_ENCAPSULATED_DATA); 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_encapsulated_data_t requestEncapsulatedData;
mavlink_msg_encapsulated_data_decode(&message, &requestEncapsulatedData); mavlink_msg_encapsulated_data_decode(&message, &requestEncapsulatedData);
QGCUASFileManager::Request* request = (QGCUASFileManager::Request*)&requestEncapsulatedData.data[0]; QGCUASFileManager::Request* request = (QGCUASFileManager::Request*)&requestEncapsulatedData.data[0];
...@@ -280,6 +308,10 @@ void MockMavlinkFileServer::_emitResponse(QGCUASFileManager::Request* request) ...@@ -280,6 +308,10 @@ void MockMavlinkFileServer::_emitResponse(QGCUASFileManager::Request* request)
mavlink_message_t mavlinkMessage; mavlink_message_t mavlinkMessage;
request->hdr.crc32 = QGCUASFileManager::crc32(request); 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); mavlink_msg_encapsulated_data_pack(250, MAV_COMP_ID_IMU, &mavlinkMessage, 0 /*_encdata_seq*/, (uint8_t*)request);
......
...@@ -46,18 +46,46 @@ public: ...@@ -46,18 +46,46 @@ public:
/// to indicate (F)ile or (D)irectory. /// to indicate (F)ile or (D)irectory.
void setFileList(QStringList& fileList) { _fileList = fileList; } 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 // From MockMavlinkInterface
virtual void sendMessage(mavlink_message_t message); virtual void sendMessage(mavlink_message_t message);
/// @brief Used to represent a single test case for download testing.
struct FileTestCase { struct FileTestCase {
const char* filename; const char* filename; ///< Filename to download
uint8_t length; 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; 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]; static const FileTestCase rgFileTestCases[cFileTestCases];
signals: signals:
/// @brief You can connect to this signal to be notified when the server receives a Terminate command.
void terminateCommandReceived(void); void terminateCommandReceived(void);
private: private:
...@@ -73,6 +101,7 @@ private: ...@@ -73,6 +101,7 @@ private:
static const uint8_t _sessionId; 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 #endif
This diff is collapsed.
...@@ -87,6 +87,10 @@ private: ...@@ -87,6 +87,10 @@ private:
static const size_t _cSignals = maxSignalIndex; static const size_t _cSignals = maxSignalIndex;
const char* _rgSignals[_cSignals]; const char* _rgSignals[_cSignals];
/// @brief This is the amount of time to wait to allow the FileManager enough time to timeout waiting for an Ack.
/// As such it must be larger than the Ack Timeout used by the FileManager.
static const int _ackTimerTimeoutMsecs = QGCUASFileManager::ackTimerTimeoutMsecs * 2;
QStringList _fileListReceived; QStringList _fileListReceived;
}; };
......
...@@ -261,7 +261,13 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me ...@@ -261,7 +261,13 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me
mavlink_msg_encapsulated_data_decode(&message, &data); mavlink_msg_encapsulated_data_decode(&message, &data);
Request* request = (Request*)&data.data[0]; Request* request = (Request*)&data.data[0];
// FIXME: Check CRC // Make sure we have a good CRC
if (request->hdr.crc32 != crc32(request)) {
_currentOperation = kCOIdle;
_emitErrorMessage(tr("Bad CRC on received message"));
return;
}
if (request->hdr.opcode == kRspAck) { if (request->hdr.opcode == kRspAck) {
...@@ -289,7 +295,7 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me ...@@ -289,7 +295,7 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me
break; break;
default: default:
_emitErrorMessage("Ack received in unexpected state"); _emitErrorMessage(tr("Ack received in unexpected state"));
break; break;
} }
} else if (request->hdr.opcode == kRspNak) { } else if (request->hdr.opcode == kRspNak) {
...@@ -462,7 +468,7 @@ void QGCUASFileManager::_setupAckTimeout(void) ...@@ -462,7 +468,7 @@ void QGCUASFileManager::_setupAckTimeout(void)
Q_ASSERT(!_ackTimer.isActive()); Q_ASSERT(!_ackTimer.isActive());
_ackTimer.setSingleShot(true); _ackTimer.setSingleShot(true);
_ackTimer.start(_ackTimerTimeoutMsecs); _ackTimer.start(ackTimerTimeoutMsecs);
} }
/// @brief Clears the ack timeout timer /// @brief Clears the ack timeout timer
......
...@@ -40,6 +40,10 @@ public: ...@@ -40,6 +40,10 @@ public:
bool _sendCmdTestNoAck(void) { return _sendOpcodeOnlyCmd(kCmdTestNoAck, kCOAck); }; bool _sendCmdTestNoAck(void) { return _sendOpcodeOnlyCmd(kCmdTestNoAck, kCOAck); };
bool _sendCmdReset(void) { return _sendOpcodeOnlyCmd(kCmdReset, kCOAck); }; bool _sendCmdReset(void) { return _sendOpcodeOnlyCmd(kCmdReset, kCOAck); };
/// @brief Timeout in msecs to wait for an Ack time come back. This is public so we can write unit tests which wait long enough
/// for the FileManager to timeout.
static const int ackTimerTimeoutMsecs = 1000;
signals: signals:
void statusMessage(const QString& msg); void statusMessage(const QString& msg);
void resetStatusMessages(); void resetStatusMessages();
...@@ -142,7 +146,6 @@ protected: ...@@ -142,7 +146,6 @@ protected:
OperationState _currentOperation; ///> Current operation of state machine OperationState _currentOperation; ///> Current operation of state machine
QTimer _ackTimer; ///> Used to signal a timeout waiting for an ack QTimer _ackTimer; ///> Used to signal a timeout waiting for an ack
static const int _ackTimerTimeoutMsecs = 1000; ///> Timeout in msecs for ack timer
UASInterface* _mav; UASInterface* _mav;
quint16 _encdata_seq; quint16 _encdata_seq;
......
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