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 @@
#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);
......
......@@ -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
This diff is collapsed.
......@@ -87,6 +87,10 @@ private:
static const size_t _cSignals = maxSignalIndex;
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;
};
......
......@@ -261,7 +261,13 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me
mavlink_msg_encapsulated_data_decode(&message, &data);
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) {
......@@ -289,7 +295,7 @@ void QGCUASFileManager::receiveMessage(LinkInterface* link, mavlink_message_t me
break;
default:
_emitErrorMessage("Ack received in unexpected state");
_emitErrorMessage(tr("Ack received in unexpected state"));
break;
}
} else if (request->hdr.opcode == kRspNak) {
......@@ -462,7 +468,7 @@ void QGCUASFileManager::_setupAckTimeout(void)
Q_ASSERT(!_ackTimer.isActive());
_ackTimer.setSingleShot(true);
_ackTimer.start(_ackTimerTimeoutMsecs);
_ackTimer.start(ackTimerTimeoutMsecs);
}
/// @brief Clears the ack timeout timer
......
......@@ -39,6 +39,10 @@ public:
bool _sendCmdTestAck(void) { return _sendOpcodeOnlyCmd(kCmdNone, kCOAck); };
bool _sendCmdTestNoAck(void) { return _sendOpcodeOnlyCmd(kCmdTestNoAck, 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:
void statusMessage(const QString& msg);
......@@ -140,9 +144,8 @@ protected:
static quint32 crc32(Request* request, unsigned state = 0);
static QString errorString(uint8_t errorCode);
OperationState _currentOperation; ///> Current operation of state machine
QTimer _ackTimer; ///> Used to signal a timeout waiting for an ack
static const int _ackTimerTimeoutMsecs = 1000; ///> Timeout in msecs for ack timer
OperationState _currentOperation; ///> Current operation of state machine
QTimer _ackTimer; ///> Used to signal a timeout waiting for an ack
UASInterface* _mav;
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