first version of embedded test stand done

This commit is contained in:
2026-05-18 15:21:18 +03:00
commit 6179eb954a
35 changed files with 2042 additions and 0 deletions

38
.gitignore vendored Normal file
View File

@@ -0,0 +1,38 @@
*.exe
*.pdb
*.pro
*.user
*.a
*.o
*.eww
*.tmp
*.dep
*.ewd
*.ewp
*.ewt
*.pbi
*.hex
*.lst
*.pbd
*.browse
*.dni
*.dbgdt
*.cout
*.log
*.txt
*.debug
/cmd*
/bin*
/logs*
/iar_files*
/python
/build*
/configs/test_cases*
*.Debug
*.Release
/src/apis
/src/inc
/bin
/compiler
*.Makefile.Debug
*.Makefile.Release

21
config.json Normal file
View File

@@ -0,0 +1,21 @@
{
"UART": {
"RS485_address": 1,
"COM_port": "COM14",
"COM_baudRate": 921600,
"COM_bits": 8,
"COM_parity": 3,
"COM_stopBits": 1,
"COM_flowControl": false
},
"Path": {
"MK_elf_file": "C:/Danila/work/sputnik_test/src/sputnik/Debug/c.out",
"MK_map_file": "C:/Danila/work/sputnik_test/src/sputnik/Debug/sputnik.map",
"test_log_path": "C:/Danila/work/embedded_test_stand/logs/",
"stand_report_html_path": "C:/Danila/work/embedded_test_stand/logs/report.html",
"map_parser": "C:/Danila/work/embedded_test_stand/python/iar_parser/map_parser.py",
"elf_parser": "C:/Danila/work/embedded_test_stand/python/iar_parser/elf_parser.py",
"iar_json_out": "C:/Danila/work/embedded_test_stand/iar_files/",
"local_python": "C:/Danila/work/embedded_test_stand/python/iar_parser/venv/Scripts/python.exe"
}
}

View File

@@ -0,0 +1,34 @@
{
"meta": {
"name": "endurance_50_60_marchFTE_5",
"description": "start endurance test in NAND Flash",
"parameters": {
"cmd_code": 22,
"block_check_cnt": 2,
"blocks": [50, 60]
}
},
"binary": "C:/Danila/work/embedded_test_stand/cmd/endurance_50_60_marchFTE_5.bin",
"expectations": {
"start": {
"type": "json",
"fields": {
"msgType": 2,
"cmd": 22
}
},
"sequence": [
{
"type": "contains",
"value": "Current state of cycle counters:"
}
],
"end": null
},
"timeout_msec": 9000
}

View File

@@ -0,0 +1,29 @@
{
"meta": {
"name": "get_savedBadBlockMap_fromNAND",
"description": "get bad block map from NAND Flash into RAM",
"parameters": {
"cmd_code": 4,
"badBlockMapOperMode": 2
}
},
"binary": "C:/Danila/work/embedded_test_stand/cmd/get_savedBadBlockMap_fromNAND.bin",
"expectations": {
"start": {
"type": "json",
"fields": {
"msgType": 2,
"cmd": 4
}
},
"end": {
"type": "contains",
"value": "NAND Flash test stand ready to receive new commands"
}
},
"timeout_msec": 200
}

View File

@@ -0,0 +1,32 @@
{
"meta": {
"name": "randomDataTest_100_110",
"description": "start random data test in all target of NAND Flash",
"parameters": {
"cmd_code": 19,
"mode_random": 2,
"seed":1,
"begin_block": 100,
"end_block": 110
}
},
"binary": "C:/Danila/work/embedded_test_stand/cmd/randomDataTest_blocks_100_110_seed_1.bin",
"expectations": {
"start": {
"type": "json",
"fields": {
"msgType": 2,
"cmd": 19
}
},
"end": {
"type": "contains",
"value": "NAND Flash test stand ready to receive new commands"
}
},
"timeout_msec": 5000
}

View File

@@ -0,0 +1,37 @@
{
"meta": {
"name": "set_micron_MT29F16G08AJADAWP",
"description": "check settings for micron_MT29F16G08AJADAWP",
"parameters": {
"cmd_code": 5,
"endurance": 100000,
"timerPeriod": 100,
"timeoutProgPageDMA": 600,
"timeoutReadPageDMA":600,
"extMemTurnCycles": 1,
"extMemeWriteCycles": 1,
"extMemReadCycles": 1,
"DMAincr": 0,
"repeatOper": 1
}
},
"binary": "C:/Danila/work/embedded_test_stand/cmd/set_settings_NAND_micron_MT29F16G08AJADAWP.bin",
"expectations": {
"start": {
"type": "json",
"fields": {
"msgType": 2,
"cmd": 5
}
},
"end": {
"type": "contains",
"value": "NAND Flash test stand ready to receive new commands"
}
},
"timeout_msec": 200
}

View File

@@ -0,0 +1,34 @@
{
"meta": {
"name": "start_March-FTE_all_targ0_repeat2",
"description": "start March-FTE for all target",
"parameters": {
"cmd_code": 20,
"target": 0,
"repeat": 2
}
},
"binary": "C:/Danila/work/embedded_test_stand/cmd/start_March-FTE_all_targ0_repeat2.bin",
"expectations": {
"start": {
"type": "json",
"fields": {
"msgType": 2,
"cmd": 20
}
},
"sequence": [
{
"type": "contains",
"value": "increment block address, erase blocks"
}
],
"end": null
},
"timeout_msec": 10000
}

View File

@@ -0,0 +1,54 @@
{
"meta": {
"name": "start_March-FTE_by_blocks",
"description": "start March-FTE by specified blocks",
"parameters": {
"cmd_code": 21,
"mode_March_FTE": 2,
"begin_block": 20,
"end_block": 25
}
},
"binary": "C:/Danila/work/embedded_test_stand/cmd/start_March-FTE_by_blocks_20_25.bin",
"expectations": {
"start": {
"type": "json",
"fields": {
"msgType": 2,
"cmd": 21
}
},
"sequence": [
{
"type": "regex",
"value": "start march-FTE in block \\d+(, LUN 0, Target 0)?"
},
{
"type": "regex",
"value": "start march-FTE in block \\d+(, LUN 0, Target 0)?"
},
{
"type": "regex",
"value": "start march-FTE in block \\d+(, LUN 0, Target 0)?"
},
{
"type": "regex",
"value": "start march-FTE in block \\d+(, LUN 0, Target 0)?"
},
{
"type": "regex",
"value": "start march-FTE in block \\d+(, LUN 0, Target 0)?"
}
],
"end": {
"type": "contains",
"value": "NAND Flash test stand ready to receive new commands"
}
},
"timeout_msec": 27000
}

25
configs/test_plan.json Normal file
View File

@@ -0,0 +1,25 @@
{
"groups": {
"smoke": [
"test_UART_cmdSetDataInterface.json",
"test_set_micron_MT29F16G08AJADAWP.json",
"test_get_badBlockMap_fromNAND.json"
],
"full": [
"test_UART_cmdSetDataInterface.json",
"test_set_micron_MT29F16G08AJADAWP.json",
"test_get_badBlockMap_fromNAND.json",
"test_start_March_FTE_by_blocks.json",
"test_randomDataTest_100_110.json",
"test_endurance_50_60_marchFTE_5.json",
"test_abort.json",
"#test_start_March_FTE_all_targ0_repeat2.json",
"#test_abort.json"
],
"abort": [
"test_abort.json"
]
}
}

42
src/core/cli_parser.cpp Normal file
View File

@@ -0,0 +1,42 @@
#include "cli_parser.h"
#include <QStringList>
TestOptions CLIParser::parse(int argc, char* argv[])
{
TestOptions opt;
QStringList args;
for (int i = 0; i < argc; ++i)
{
args << argv[i];
}
for (int i = 0; i < args.size(); ++i)
{
QString a = args[i];
if (a == "--group" && i + 1 < args.size())
{
opt.group = args[i + 1];
}
else if (a == "--case" && i + 1 < args.size())
{
opt.caseFilter = args[i + 1];
}
else if (a == "--repeat" && i + 1 < args.size())
{
opt.repeat = args[i + 1].toInt();
}
else if (a == "--delay" && i + 1 < args.size())
{
opt.delayMs = args[i + 1].toInt();
}
}
return opt;
}

12
src/core/cli_parser.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef CLI_PARSER_H
#define CLI_PARSER_H
#include "test_options.h"
class CLIParser
{
public:
static TestOptions parse(int argc, char* argv[]);
};
#endif // CLI_PARSER_H

68
src/core/declarations.h Normal file
View File

@@ -0,0 +1,68 @@
#ifndef DECLARATIONS_H
#define DECLARATIONS_H
#include <QByteArray>
#include <QDateTime>
#include <QFile>
#include <QJsonObject>
#include <QJsonDocument>
#include <QJsonArray>
#include <QHash>
#include <QHashIterator>
#include "QLoggingCategory"
#include "QProcess"
#include <QRegularExpression>
#include <QString>
#include <QStringList>
#include <QSettings>
#include <QTextCodec>
#include <QtCore>
#include <QTextStream>
#include <QtSerialPort/QSerialPort>
#include <QVariant>
#include <QVariantList>
#include <QVector>
#include <gtest/gtest.h>
#include "loggingCategories.h"
#include "html_report.h"
struct comSet_t {
int addrRS485;
QString name;
qint32 baudRate;
QSerialPort::DataBits dataBits;
QSerialPort::Parity parity;
QSerialPort::StopBits stopBits;
QSerialPort::FlowControl flowControl;
comSet_t()
{
addrRS485 = 1;
name = "";
baudRate = QSerialPort::Baud9600;
dataBits = QSerialPort::Data8;
parity = QSerialPort::NoParity;
stopBits = QSerialPort::OneStop;
flowControl = QSerialPort::NoFlowControl;
}
};
typedef comSet_t comSettings_t;
typedef struct {
QString MK_elf_file;
QString MK_map_file;
QString test_log_path;
QString stand_report_html_path;
QString map_parser;
QString elf_parser;
QString iar_json_out;
QString local_python;
} path_t;
#endif // DECLARATIONS_H

View File

@@ -0,0 +1,94 @@
#ifndef EXCEPTIONS_HANDLE_H
#define EXCEPTIONS_HANDLE_H
#include "declarations.h"
using namespace std;
/*!
* \brief the ErrOpenFile class for handling exceptions when opening file
*/
class ErrOpenFile: public exception
{
public:
ErrOpenFile(QString message, QString filePath)
{
this->message = message;
this->filePath = filePath;
}
QString getFilePath()
{ return filePath; }
QString getMessage()
{ return message; }
virtual ~ErrOpenFile() throw() {}
private:
QString message;
QString filePath;
};
//class ErrOpenFile: public exception
//{
//public:
// ErrOpenFile(QString fname, QString errMsg)
// {
// this->fname = fname;
// this->intro = "ошибка чтения файла";
// this->errMsg = errMsg;
// }
// QString getFname() { return fname; }
// QString getIntro() { return intro; }
// QString getErrMsg() { return errMsg; }
// virtual ~ErrOpenFile() throw() {}
//private:
// QString fname;
// QString intro;
// QString errMsg;
//};
class ErrInJsonSet: public exception
{
public:
ErrInJsonSet(QString fname, QString jsonObjectsList, QString param, QString errMsg)
{
this->fname = fname;
this->jsonObjectsList = jsonObjectsList;
this->param = param;
this->intro = "ошибка чтения файла настроек";
this->errMsg = errMsg;
this->errFromJsonFlag = false;
}
ErrInJsonSet(QString fname, QString errFromJson)
{
this->fname = fname;
this->errFromJsonFlag = true;
this->intro = "ошибка чтения файла настроек";
this->errFromJson = errFromJson;
}
QString getFname() { return fname; }
QString getJsonObj() { return jsonObjectsList; }
QString getParam() { return param; }
QString getIntro() { return intro; }
QString getErrMsg() { return errMsg; }
QString getErrFromJson() { return errFromJson; }
bool checkErrFromJson() { return errFromJsonFlag; }
virtual ~ErrInJsonSet() throw() {}
private:
QString fname;
QString intro;
QString jsonObjectsList;
QString param;
QString errMsg;
QString errFromJson;
bool errFromJsonFlag = false;
};
#endif // EXCEPTIONS_HANDLE_H

158
src/core/html_report.cpp Normal file
View File

@@ -0,0 +1,158 @@
#include "html_report.h"
#include <QFile>
#include <QStringList>
#include <QTextStream>
static QMap<QString,int> extractSummary(
const QString& content)
{
QMap<QString,int> s;
QStringList lines =
content.split("\n");
for (QString line : lines)
{
if (line.startsWith("TOTAL:"))
s["total"] =
line.split(":")[1].trimmed().toInt();
if (line.startsWith("PASSED:"))
s["passed"] =
line.split(":")[1].trimmed().toInt();
if (line.startsWith("FAILED:"))
s["failed"] =
line.split(":")[1].trimmed().toInt();
}
return s;
}
void HtmlReport::generate(
const QString& logFile,
const QString& htmlFile,
QString curTime)
{
QFile f(logFile);
f.open(QIODevice::ReadOnly);
QString content =
f.readAll();
f.close();
auto summary =
extractSummary(content);
QString html;
html += R"(
<html>
<head>
<style>
body {
font-family: monospace;
background:#111;
color:#eee;
}
.pass {
color:lightgreen;
}
.fail {
color:red;
}
details {
margin-bottom:10px;
}
summary {
cursor:pointer;
font-weight:bold;
}
pre {
white-space:pre-wrap;
}
</style>
</head>
<body>
)";
html += QString("<h1>Tests Report (%1)</h1>").arg(curTime);
html += QString(R"(
<h2>SUMMARY</h2>
<div>
<b>Total:</b> %1<br>
<b style='color:lightgreen;'>Passed:</b> %2<br>
<b style='color:red;'>Failed:</b> %3<br>
</div>
)")
.arg(summary["total"])
.arg(summary["passed"])
.arg(summary["failed"]);
QStringList tests =
content.split("<<<TEST_START>>>");
for (QString t : tests)
{
if (!t.contains("TEST:"))
continue;
QString cls =
t.contains("STATUS: FAIL")
? "fail"
: "pass";
QString title =
t.split("\n")[1];
html += QString(R"(
<details>
<summary class="%1">%2</summary>
<pre>%3</pre>
</details>
)")
.arg(cls)
.arg(title)
.arg(t.toHtmlEscaped());
}
int passRate = 0;
if (summary["total"] > 0)
{
passRate =
(100 * summary["passed"])
/ summary["total"];
}
html += QString(R"(
<div style="background:#333;width:300px;">
<div style="
background:green;
width:%1%;
color:white;">
%1%
</div>
</div>
)").arg(passRate);
html += "</body></html>";
QFile out(htmlFile);
out.open(QIODevice::WriteOnly);
out.write(html.toUtf8());
out.close();
}

14
src/core/html_report.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef HTML_REPORT_H
#define HTML_REPORT_H
#include <QString>
class HtmlReport
{
public:
static void generate(const QString& logFile,
const QString& htmlFile, QString curTime);
};
#endif // HTML_REPORT_H

182
src/core/json_processor.cpp Normal file
View File

@@ -0,0 +1,182 @@
#include "json_processor.h"
JsonProcessor::JsonProcessor()
{
}
void JsonProcessor::setJsonFile(QString jsonPath_)
{
jsonPath = jsonPath_;
}
void JsonProcessor::openJsonFile(QString jsonPath, QJsonObject &jsonObj)
{
QByteArray bytes;
QFile file(jsonPath);
if (file.open( QIODevice::ReadOnly))
{
bytes = file.readAll();
file.close();
}
else
{ throw(ErrOpenFile(jsonPath, "файл не найден")); }
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(bytes, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{ throw(ErrInJsonSet(jsonPath, jsonError.errorString())); return; }
else
{
if (doc.isObject())
{ jsonObj = doc.object(); }
else
{ throw(ErrInJsonSet(jsonPath, "неверный формат json: " + jsonPath)); }
}
}
void JsonProcessor::saveJsonDataInFile(QString jsonPath, QJsonObject jsonObj)
{
QJsonDocument document;
document.setObject(jsonObj);
QByteArray bytes = document.toJson( QJsonDocument::Indented );
QFile file(jsonPath);
if( file.open( QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate ) )
{
QTextStream iStream( &file );
iStream.setCodec( "utf-8" );
iStream << bytes;
file.close();
}
else
{ throw(ErrOpenFile(jsonPath, "файл не найден")); }
}
/* common functions */
void JsonProcessor::jsonConvertStruct(QString jsonObjName, QJsonObject obj,
QString jsonParamName, QVector<QString> *setToStructArr, int arrSize)
{
setToStructArr->clear();
QJsonArray *param = new QJsonArray;
*param = obj[jsonParamName].toArray();
if (param->size() != arrSize)
{ throw(ErrInJsonSet(jsonPath, jsonObjName, jsonParamName, "параметр отсутствует или кол-во элементов в параметре должно быть равно " + QString::number(arrSize))); }
else
{
for (int i = 0; i < arrSize; i++)
{ setToStructArr->push_back(param->at(i).toString()); }
}
}
void JsonProcessor::jsonConvertStruct(QJsonObject obj, QString jsonParamName,
QVector<QString> *setToStructArr)
{
setToStructArr->clear();
QJsonArray *param = new QJsonArray;
*param = obj[jsonParamName].toArray();
for (int i = 0; i < param->size(); i++)
{ setToStructArr->push_back(param->at(i).toString()); }
}
void JsonProcessor::jsonConvertStruct(QJsonObject obj,
QString jsonParamName, QVector<double> *setToStructArr)
{
setToStructArr->clear();
QJsonArray *param = new QJsonArray;
*param = obj[jsonParamName].toArray();
for (int i = 0; i < param->size(); i++)
{ setToStructArr->push_back(param->at(i).toDouble()); }
}
void JsonProcessor::jsonConvertStruct(QString jsonObjName, QJsonObject obj,
QString jsonParamName, double *setToStructArr, int arrSize)
{
QJsonArray param = obj[jsonParamName].toArray();
if (param.size() != arrSize)
{ throw(ErrInJsonSet(jsonPath, jsonObjName, jsonParamName,
"параметр отсутствует или кол-во элементов в параметре должно быть равно " + QString::number(arrSize))); }
else
{
for (int i = 0; i < arrSize; i++)
{ setToStructArr[i] = param[i].toDouble(); }
}
}
void JsonProcessor::jsonConvertStruct(QString jsonObjName, QJsonObject obj,
QString jsonParamName, bool *setToStructArr, int arrSize)
{
QJsonArray param = obj[jsonParamName].toArray();
if (param.size() != arrSize)
{ throw(ErrInJsonSet(jsonPath, jsonObjName, jsonParamName,
"параметр отсутствует или кол-во элементов в параметре должно быть равно " + QString::number(arrSize))); }
else
{
for (int i = 0; i < arrSize; i++)
{ setToStructArr[i] = param[i].toBool(); }
}
}
bool JsonProcessor::jsonGetBoolValue(QJsonObject obj, QString param, QString jsonGeneral)
{
QJsonValue val = obj[param];
if (val == QJsonValue::Null)
{ throw(ErrInJsonSet(jsonPath, jsonGeneral, param, "отсутствует параметр")); }
return val.toBool(false);
}
void JsonProcessor::jsonGetStrValue(QJsonObject obj, QString paramName, QString &paramValue, QString jsonObjName)
{
QString val = obj[paramName].toString();
if (val == NULL)
{ throw(ErrInJsonSet(jsonPath, jsonObjName, paramName, "parameter missing")); }
else
{ paramValue = val; }
}
void JsonProcessor::jsonSetComPortSettings(QString jsonObjName, QJsonObject obj, comSettings_t &com)
{
com.addrRS485 = obj["RS485_address"].toInt();
if (com.addrRS485 < 1 ||
com.addrRS485 > 254)
{ throw(ErrInJsonSet(jsonPath, jsonObjName, "RS485_address", "отсутствует параметр")); }
com.name = obj["COM_port"].toString();
if (com.name == NULL) { throw(ErrInJsonSet(jsonPath, jsonObjName, "COM_port", "отсутствует параметр")); }
com.baudRate = (quint32)obj["COM_baudRate"].toInt();
if (com.baudRate == 0) { throw(ErrInJsonSet(jsonPath, jsonObjName, "COM_baudRate", "отсутствует параметр")); }
com.dataBits = (QSerialPort::DataBits)obj["COM_bits"].toInt();
if (com.dataBits < QSerialPort::Data5 ||
com.dataBits > QSerialPort::Data8)
{ throw(ErrInJsonSet(jsonPath, jsonObjName, "COM_bits", "отсутствует параметр")); }
com.parity = (QSerialPort::Parity)obj["COM_parity"].toInt();
if (com.parity < QSerialPort::UnknownParity ||
com.parity == 1 ||
com.parity > QSerialPort::MarkParity)
{ throw(ErrInJsonSet(jsonPath, jsonObjName, "COM_parity", "отсутствует параметр")); }
com.stopBits = (QSerialPort::StopBits)obj["COM_stopBits"].toInt();
if (com.stopBits < QSerialPort::OneStop ||
com.stopBits > QSerialPort::OneAndHalfStop)
{ throw(ErrInJsonSet(jsonPath, jsonObjName, "COM_stopBits", "отсутствует параметр")); }
com.flowControl = (QSerialPort::FlowControl)obj["COM_flowCotrol"].toInt();
if (com.flowControl < QSerialPort::NoFlowControl ||
com.flowControl > QSerialPort::SoftwareControl)
{ throw(ErrInJsonSet(jsonPath, jsonObjName, "COM_flowControl", "отсутствует параметр")); }
}
void JsonProcessor::jsonSaveComPortSettings(QJsonObject &obj, comSettings_t &com)
{
obj.insert("RS485_address", com.addrRS485);
obj.insert("COM_port", com.name);
obj.insert("COM_baudRate", com.baudRate);
obj.insert("COM_bits", com.dataBits);
obj.insert("COM_parity", com.parity);
obj.insert("COM_stopBits", com.stopBits);
obj.insert("COM_flowControl", com.flowControl);
}

39
src/core/json_processor.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef JSONPROCESSOR_H
#define JSONPROCESSOR_H
#include "exceptions_handle.h"
class JsonProcessor
{
public:
JsonProcessor();
virtual void setJsonFile(QString jsonPath_);
virtual void openJsonFile(QString jsonPath, QJsonObject &jsonObj);
virtual void saveJsonDataInFile(QString jsonPath, QJsonObject jsonObj);
virtual void jsonConvertStruct(QString jsonObjName, QJsonObject obj,
QString jsonParamName, QVector<QString> *setToStructArr, int arrSize);
virtual void jsonConvertStruct(QJsonObject obj,
QString jsonParamName, QVector<QString> *setToStructArr);
virtual void jsonConvertStruct(QJsonObject obj,
QString jsonParamName, QVector<double> *setToStructArr);
virtual void jsonConvertStruct(QString jsonObjName, QJsonObject obj,
QString jsonParamName, double *setToStructArr, int arrSize);
virtual void jsonConvertStruct(QString jsonObjName, QJsonObject obj,
QString jsonParamName, bool *setToStructArr, int arrSize);
virtual bool jsonGetBoolValue(QJsonObject obj, QString param, QString jsonPSgeneral);
virtual void jsonSetComPortSettings(QString jsonObjName, QJsonObject obj, comSettings_t &com);
virtual void jsonSaveComPortSettings(QJsonObject &obj, comSettings_t &com);
virtual void jsonGetStrValue(QJsonObject obj, QString paramName, QString &paramValue, QString jsonObjName);
private:
QString jsonPath = "";
};
#endif // JSONPROCESSOR_H

127
src/core/logger.cpp Normal file
View File

@@ -0,0 +1,127 @@
#include "logger.h"
// Умный указатель на файл логирования
QScopedPointer<QFile> m_logFile;
void Logger::write(const QString& file, const QString& text)
{
QFile f(file);
f.open(QIODevice::Append | QIODevice::Text);
QTextStream out(&f);
out << text;
f.close();
}
void Logger::saveTestLog(
const QString& filename,
const ProtocolHandler& handler,
const QJsonObject& cfg,
bool passed,
const QString& error)
{
QString log;
log += "\n<<<TEST_START>>>\n";
log += "TEST: ";
log += cfg["meta"].toObject()["name"].toString();
log += "\n";
log += "TIME: ";
log += QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm:ss");
log += "\n";
log += passed ? "STATUS: PASS\n" : "STATUS: FAIL\n";
if (!error.isEmpty())
{
log += "\n--- ERROR ---\n";
log += error;
log += "\n";
}
if (!handler.mismatches.isEmpty())
{
log += "\n--- MISMATCHES ---\n";
QJsonDocument doc(QJsonArray::fromVariantList(handler.mismatches));
log += doc.toJson();
log += "\n";
}
log += "\n--- FULL LOG ---\n";
for (const QString& s : handler.messages)
{
log += s;
log += "\n";
}
log += "<<<TEST_END>>>\n";
write(filename, log);
}
void Logger::appendSummary(const QString& filename, int total,
int passed, int failed)
{
QString s;
s += "\n";
s += "############################################################\n";
s += "SUMMARY\n";
s += "############################################################\n";
s += QString("TOTAL: %1\n").arg(total);
s += QString("PASSED: %1\n").arg(passed);
s += QString("FAILED: %1\n").arg(failed);
write(filename, s);
}
void Logger::setupLog()
{
// Устанавливаем файл логирования
m_logFile.reset(new QFile("C:/Danila/work/embedded_test_stand/logs/test_stand.log"));
// Открываем файл логирования
if (!m_logFile.data()->open(QFile::Append | QFile::Text))
{ printf("\nlog file is not opened!\n"); }
// Устанавливаем обработчик
qInstallMessageHandler(writeStandLog);
}
void Logger::writeToConsol(QString msg)
{
QTextStream out(stdout);
out.setCodec("IBM 866");
out << msg << Qt::endl;
}
void Logger::writeStandLog(QtMsgType type,
const QMessageLogContext &context,
const QString &msg)
{
// Открываем поток записи в файл
QTextStream out(m_logFile.data());
// Записываем дату записи
out << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz ");
// По типу определяем, к какому уровню относится сообщение
switch (type)
{
case QtInfoMsg: out << "INF "; break;
case QtDebugMsg: out << "DBG "; break;
case QtWarningMsg: out << "WRN "; break;
case QtCriticalMsg: out << "CRT "; break;
case QtFatalMsg: out << "FTL "; break;
}
// Записываем в вывод категорию сообщения и само сообщение
out << context.category << ": " << msg << Qt::endl;
out.flush(); // Очищаем буферизированные данные
}

39
src/core/logger.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef LOGGER_H
#define LOGGER_H
#include "protocol_handler.h"
class ProtocolHandler;
class QJsonObject;
class Logger
{
public:
static void saveTestLog(
const QString& filename,
const ProtocolHandler& handler,
const QJsonObject& cfg,
bool passed,
const QString& error = "");
static void appendSummary(
const QString& filename,
int total,
int passed,
int failed);
static void setupLog();
static void writeStandLog(QtMsgType type, const QMessageLogContext &context,
const QString &msg);
static void writeToConsol(QString msg);
private:
static void write(
const QString& file,
const QString& text);
};
#endif // LOGGER_H

View File

@@ -0,0 +1,6 @@
#include "loggingCategories.h"
Q_LOGGING_CATEGORY(logDebug, "Debug")
Q_LOGGING_CATEGORY(logInfo, "Info")
Q_LOGGING_CATEGORY(logWarning, "Warning")
Q_LOGGING_CATEGORY(logCritical, "Critical")

View File

@@ -0,0 +1,11 @@
#ifndef LOGGINGCATEGORIES_H
#define LOGGINGCATEGORIES_H
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(logDebug)
Q_DECLARE_LOGGING_CATEGORY(logInfo)
Q_DECLARE_LOGGING_CATEGORY(logWarning)
Q_DECLARE_LOGGING_CATEGORY(logCritical)
#endif // LOGGINGCATEGORIES_H

View File

@@ -0,0 +1,194 @@
#include "protocol_handler.h"
ProtocolHandler::ProtocolHandler(const QJsonObject& expectations)
{
exp = expectations;
}
void ProtocolHandler::feed(const QByteArray& data)
{
QString text = QString::fromUtf8(data);
buffer += text;
messages.push_back(text);
QJsonValue startExp = exp["start"];
QJsonValue endExp = exp["end"];
if (startExp.isNull())
{ startReceived = true; }
if (!startReceived)
{ checkStart(); }
if (startReceived)
{ checkSequence(); }
if (endExp.isNull())
{ endReceived = true; }
if (!endReceived)
{ checkEnd(); }
}
void ProtocolHandler::checkStart()
{
if (!buffer.contains("{"))
return;
int start = buffer.indexOf("{");
int end = buffer.indexOf("}");
if (end < 0)
return;
QString jsonText = buffer.mid(start, end - start + 1);
QJsonDocument doc = QJsonDocument::fromJson(jsonText.toUtf8());
if (!doc.isObject()) return;
QJsonObject msg = doc.object();
QJsonObject expected = exp["start"]
.toObject()["fields"]
.toObject();
bool ok = true;
for (QString key : expected.keys())
{
if (msg[key] != expected[key])
{
ok = false;
break;
}
}
if (ok)
{ startReceived = true; }
else
{
QVariantMap m;
m["stage"] = "start";
m["expected"] = expected;
m["actual"] = msg.toVariantMap();
mismatches.append(m);
}
buffer = buffer.mid(end + 1);
}
void ProtocolHandler::checkSequence()
{
QJsonArray seq = exp["sequence"].toArray();
while (sequenceIndex < seq.size())
{
QJsonObject current =
seq[sequenceIndex].toObject();
QString type = current["type"].toString();
QString value = current["value"].toString();
bool matched = false;
if (type == "contains")
{
matched = buffer.contains(value);
}
else if (type == "regex")
{
QRegularExpression re(value);
matched = re.match(buffer).hasMatch();
}
if (matched)
{
sequenceIndex++;
}
}
}
void ProtocolHandler::checkEnd()
{
QJsonObject endExp = exp["end"].toObject();
if (endExp.isEmpty()) return;
QString type = endExp["type"].toString();
QString value = endExp["value"].toString();
if (type == "contains")
{
if (buffer.contains(value))
{
endReceived = true;
// sequence incomplete
QJsonArray seq = exp["sequence"].toArray();
if (sequenceIndex < seq.size())
{
QVariantMap m;
m["stage"] = "sequence";
m["step"] = sequenceIndex;
m["expected"] =
seq[sequenceIndex].toObject()
.toVariantMap()["value"];
m["actual_buffer"] = buffer.right(200);
mismatches.append(m);
}
}
}
}
bool ProtocolHandler::isDone(QString &msg) const
{
bool startOk = true;
bool endOk = true;
QJsonValue startExp = exp["start"];
QJsonValue endExp = exp["end"];
if (!startExp.isNull())
{
startOk = startReceived;
}
if (!endExp.isNull())
{
endOk = endReceived;
}
if (!startOk)
{
msg.append("\nstart sequence did not receive");
}
if (!endOk)
{
msg.append("\nend sequence did not receive");
}
int seqSize =
exp["sequence"]
.toArray()
.size();
return (
startOk
&& sequenceIndex >= seqSize
&& endOk
);
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include "declarations.h"
class ProtocolHandler
{
public:
ProtocolHandler(const QJsonObject& expectations);
void feed(const QByteArray& data);
bool isDone(QString &msg) const;
QStringList messages;
QVariantList mismatches;
private:
void checkStart();
void checkSequence();
void checkEnd();
private:
QJsonObject exp;
QString buffer;
bool startReceived = false;
bool endReceived = false;
int sequenceIndex = 0;
};

47
src/core/test_loader.cpp Normal file
View File

@@ -0,0 +1,47 @@
#include "test_loader.h"
QStringList TestLoader::loadCases(const QString& group,
const QString& caseFilter,
int repeat)
{
QString fname = "C:/Danila/work/embedded_test_stand/configs/test_plan.json";
QFile f(fname);
QStringList result;
if (!f.open(QIODevice::ReadOnly))
{
throw(ErrOpenFile("cannot open json file", fname));
}
QJsonObject root = QJsonDocument::fromJson(f.readAll()).object();
const QJsonArray arr = root["groups"].toObject()[group].toArray();
if (arr.count() == 0)
{
throw(ErrOpenFile("invalid format in json file!", fname));
}
for (auto v : arr)
{
QString name = v.toString();
// skip test
if (name.startsWith("#"))
continue;
// filter
if (!caseFilter.isEmpty())
{
if (!name.contains(caseFilter))
continue;
}
for (int i = 0; i < repeat; ++i)
{ result << QString("C:/Danila/work/embedded_test_stand/configs/test_cases/%1").arg(name); }
}
return result;
}

11
src/core/test_loader.h Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
#include "exceptions_handle.h"
class TestLoader
{
public:
static QStringList loadCases(const QString& group,
const QString& caseFilter = QString(),
int repeat = 1);
};

View File

@@ -0,0 +1 @@
#include "test_options.h"

17
src/core/test_options.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef TEST_OPTIONS_H
#define TEST_OPTIONS_H
#include <QString>
struct TestOptions
{
QString group = "full";
QString caseFilter;
int repeat = 1;
int delayMs = 0;
};
#endif // TEST_OPTIONS_H

12
src/core/test_stats.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef TEST_STATS_H
#define TEST_STATS_H
struct TestStats
{
int total = 0;
int passed = 0;
int failed = 0;
};
#endif // TEST_STATS_H

59
src/core/uart.cpp Normal file
View File

@@ -0,0 +1,59 @@
#include "uart.h"
UART::UART()
{
}
UART::~UART()
{
}
void UART::connect(comSettings_t set)
{
serial.setPortName(set.name);
serial.setBaudRate(set.baudRate);
serial.setDataBits(set.dataBits);
serial.setParity(set.parity);
serial.setStopBits(set.stopBits);
serial.setFlowControl(set.flowControl);
if (!serial.open(QIODevice::ReadWrite))
{
throw(ErrOpenFile(QString("error opening com-port: %1").
arg(serial.errorString()), set.name));
}
}
bool UART::send(const QByteArray& data, QString &err)
{
bool st = false;
int status = serial.write(data);
serial.flush();
if (status == -1)
{ return false; }
else
{ st = serial.waitForBytesWritten(3000); }
err = serial.errorString();
return st;
}
QByteArray UART::receive(int timeoutMs)
{
QByteArray result;
while (serial.waitForReadyRead(timeoutMs))
{
result += serial.readAll();
}
return result;
}
void UART::close()
{
serial.close();
}

22
src/core/uart.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include "exceptions_handle.h"
class UART
{
public:
UART();
~UART();
public:
void connect(comSettings_t set);
bool send(const QByteArray& data, QString &err);
QByteArray receive(int timeoutMs);
void close();
private:
QSerialPort serial;
};

39
src/gtest_dependency.pri Normal file
View File

@@ -0,0 +1,39 @@
isEmpty(GOOGLETEST_DIR):GOOGLETEST_DIR=$$(GOOGLETEST_DIR)
isEmpty(GOOGLETEST_DIR) {
GOOGLETEST_DIR = C:/Danila/soft/googletest
!isEmpty(GOOGLETEST_DIR) {
warning("Using googletest src dir specified at Qt Creator wizard")
message("set GOOGLETEST_DIR as environment variable or qmake variable to get rid of this message")
}
}
!isEmpty(GOOGLETEST_DIR): {
GTEST_SRCDIR = $$GOOGLETEST_DIR/googletest
GMOCK_SRCDIR = $$GOOGLETEST_DIR/googlemock
} else: unix {
exists(/usr/src/gtest):GTEST_SRCDIR=/usr/src/gtest
exists(/usr/src/gmock):GMOCK_SRCDIR=/usr/src/gmock
!isEmpty(GTEST_SRCDIR): message("Using gtest from system")
}
requires(exists($$GTEST_SRCDIR):exists($$GMOCK_SRCDIR))
!isEmpty(GTEST_SRCDIR) {
INCLUDEPATH *= \
$$GTEST_SRCDIR \
$$GTEST_SRCDIR/include
SOURCES += \
$$GTEST_SRCDIR/src/gtest-all.cc
}
!isEmpty(GMOCK_SRCDIR) {
INCLUDEPATH *= \
$$GMOCK_SRCDIR \
$$GMOCK_SRCDIR/include
SOURCES += \
$$GMOCK_SRCDIR/src/gmock-all.cc
}

22
src/main.cpp Normal file
View File

@@ -0,0 +1,22 @@
#include <QCoreApplication>
#include "../core/cli_parser.h"
#include <gtest/gtest.h>
TestOptions g_options;
//test_runner.exe --group smoke
//test_runner.exe --group full --repeat 5
//test_runner.exe --group nand_flash --case test_abort
//test_runner.exe --group smoke --delay 1000
int main(int argc, char** argv)
{
QCoreApplication app(argc, argv);
g_options = CLIParser::parse(argc, argv);
::testing::InitGoogleTest(&argc, argv);
auto status = RUN_ALL_TESTS();
return status;
}

157
src/tests/test_runner.cpp Normal file
View File

@@ -0,0 +1,157 @@
#include "uart_fixture.h"
#include "../core/cli_parser.h"
extern TestOptions g_options;
std::vector<QString> getJsonTests()
{
QStringList list;
std::vector<QString> result;
try
{
list = TestLoader::loadCases(
g_options.group,
g_options.caseFilter,
g_options.repeat);
}
catch (ErrOpenFile &errOpen)
{
qDebug(logCritical()) << QString("%1, path: %2")
.arg(errOpen.getMessage(),
errOpen.getFilePath());
Logger::writeToConsol(QString("\nERROR: %1, path: %2\n").arg(
errOpen.getMessage(),errOpen.getFilePath()));
return result;
}
for (const auto& s : list)
{
result.push_back(s);
}
return result;
}
INSTANTIATE_TEST_SUITE_P(
JsonTestSuite,
UARTFixture,
::testing::ValuesIn(
getJsonTests()
),
[](const testing::TestParamInfo<QString>& info)
{
QFileInfo fi(info.param);
return fi.baseName()
.toStdString();
}
);
//TEST_F(UARTFixture, SmokeTests)
//{
// TestStats stats;
// QString msgItog;
// QString msg;
// QStringList tests;
// bool fail = extract_struct_from_elf("Snapshot_HK_t");
// if (fail == true) { return; }
// try
// {
// tests = TestLoader::loadCases(g_options.group,
// g_options.caseFilter,
// g_options.repeat);
// }
// catch (ErrOpenFile &errOpen)
// {
// qDebug(logCritical()) << QString("%1, path: %2")
// .arg(errOpen.getMessage(),
// errOpen.getFilePath());
// Logger::writeToConsol(QString("\nERROR: %1, path: %2\n").arg(
// errOpen.getMessage(),errOpen.getFilePath()));
// return;
// }
// QString curTime = QDateTime::currentDateTime().toString("dd_MM_yyyy_hh_mm_ss");
// QString curTime1 = QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm:ss");
// QString logFile = QString("%1%2.log").arg(path.test_log_path, curTime);
// QString htmlFile = path.stand_report_html_path;
// QFile::remove(logFile);
// for (const QString &caseFile : qAsConst(tests))
// {
// QFile caseFileJson(caseFile);
// QString error;
// if (!caseFileJson.open(QIODevice::ReadOnly))
// {
// msg = QString("%1, path: %2").arg("json test case not found", caseFile);
// msgItog.append(msg);
// qDebug(logCritical()) << msg << Qt::endl;
// Logger::writeToConsol(QString("\nERROR: %1\n").arg(msg));
// }
// QJsonObject cfg =
// QJsonDocument::fromJson(caseFileJson.readAll()).object();
// QString binaryFile = cfg["binary"].toString();
// QFile cmdFile(binaryFile);
// if (!cmdFile.open(QIODevice::ReadOnly))
// {
// msg = QString("%1, path: %2").arg("command binary not found", binaryFile);
// msgItog.append(msg);
// qDebug(logCritical()) << msg << Qt::endl;
// Logger::writeToConsol(QString("\nERROR: %1\n").arg(msg));
// }
// QByteArray tx_cmd = cmdFile.readAll();
// QString err;
// if (!uart.send(tx_cmd, err))
// {
// msg = QString("%1: %2").arg("UART data did not send", err);
// msgItog.append(msg);
// qDebug(logCritical()) << msg << Qt::endl;
// Logger::writeToConsol(QString("\nERROR: %1\n").arg(msg));
// }
// QByteArray rx_data = uart.receive(cfg["timeout_msec"].toInt());
// ProtocolHandler handler(cfg["expectations"].toObject());
// handler.feed(rx_data);
// QString msg1;
// bool passed = handler.isDone(msg1);
// if (!passed)
// {
// msgItog.append(msg1);
// error = QString("Protocol validation failed: \n %1").arg(msgItog);
// }
// Logger::saveTestLog(logFile, handler, cfg, passed, error);
// stats.total++;
// if (passed) stats.passed++;
// else stats.failed++;
// EXPECT_TRUE(passed);
// if (g_options.delayMs > 0)
// {
// QThread::msleep(g_options.delayMs);
// }
// }
// Logger::appendSummary(logFile, stats.total, stats.passed, stats.failed);
// HtmlReport::generate(logFile, htmlFile, curTime1);
//}

304
src/tests/uart_fixture.cpp Normal file
View File

@@ -0,0 +1,304 @@
#include "uart_fixture.h"
UART UARTFixture::uart;
comSettings_t UARTFixture::comPortSettings;
path_t UARTFixture::path;
QString UARTFixture::logFile;
int UARTFixture::extract_struct_from_elf(QString structTypeName)
{
QProcess process;
QString program = path.local_python;
QStringList arguments;
arguments << path.elf_parser
<< "-f"
<< path.MK_elf_file
<< "-t"
<< structTypeName
<< "-o"
<< path.iar_json_out;
process.start(program, arguments);
if (!process.waitForFinished(3000)) {
QString msg = QString("timeout processing scipt %1").arg(path.map_parser);
qDebug(logCritical()) << msg;
Logger::writeToConsol(QString("\nERROR: %1").arg(msg));
return 1;
}
int exitCode = process.exitCode();
if (exitCode != 0) {
QString msg = QString("scipt %1 return error code %2: %3")
.arg(path.map_parser,
QString(exitCode),
QString::fromUtf8(process.readAllStandardError()));
qDebug(logCritical()) << msg;
Logger::writeToConsol(QString("\nERROR: %1").arg(msg));
return 1;
}
return 0;
}
int UARTFixture::convert_map_file()
{
QProcess process;
QString program = "python";
QStringList arguments;
arguments << path.map_parser
<< "-f"
<< path.MK_map_file
<< "-s"
<< "ENTRY LIST"
<< "-o"
<< path.iar_json_out;
process.start(program, arguments);
if (!process.waitForFinished(3000)) {
QString msg = QString("timeout processing scipt %1").arg(path.map_parser);
qDebug(logCritical()) << msg;
Logger::writeToConsol(QString("\nERROR: %1").arg(msg));
return 1;
}
int exitCode = process.exitCode();
if (exitCode != 0) {
QString msg = QString("scipt %1 return error code %2: %3")
.arg(path.map_parser,
QString(exitCode),
QString::fromUtf8(process.readAllStandardError()));
qDebug(logCritical()) << msg;
Logger::writeToConsol(QString("\nERROR: %1").arg(msg));
return 1;
}
return 0;
}
int UARTFixture::readConfig()
{
JsonProcessor json;
QJsonObject jsonObj;
QString jsonUart = "UART";
QString jsonPath = "Path";
try
{
json.openJsonFile("../config.json", jsonObj);
QJsonObject objUart = jsonObj.value(jsonUart).toObject();
QJsonObject objPath = jsonObj.value(jsonPath).toObject();
json.jsonSetComPortSettings(jsonUart, objUart, comPortSettings);
json.jsonGetStrValue(objPath, "MK_elf_file", path.MK_elf_file, jsonPath);
json.jsonGetStrValue(objPath, "MK_map_file", path.MK_map_file, jsonPath);
json.jsonGetStrValue(objPath, "test_log_path", path.test_log_path, jsonPath);
json.jsonGetStrValue(objPath, "stand_report_html_path", path.stand_report_html_path, jsonPath);
json.jsonGetStrValue(objPath, "map_parser", path.map_parser, jsonPath);
json.jsonGetStrValue(objPath, "elf_parser", path.elf_parser, jsonPath);
json.jsonGetStrValue(objPath, "iar_json_out", path.iar_json_out, jsonPath);
json.jsonGetStrValue(objPath, "local_python", path.local_python, jsonPath);
}
catch (ErrOpenFile &errOpen)
{
qDebug(logCritical()) << QString("%1, path: %2")
.arg(errOpen.getMessage(),
errOpen.getFilePath());
Logger::writeToConsol(QString("\nERROR: %1, path: %2\n").arg(
errOpen.getMessage(),errOpen.getFilePath()));
return 1;
}
catch (ErrInJsonSet &jsonSet)
{
if (jsonSet.checkErrFromJson())
{
QString msg = QString("%1. %2 : %3. file: %4")
.arg(jsonSet.getIntro(),
jsonSet.getErrMsg(),
jsonSet.getErrFromJson(),
jsonSet.getFname());
qDebug(logCritical()) << msg;
Logger::writeToConsol(QString("\nERROR: %1").arg(msg));
return 1;
}
else
{
QString msg = QString("%1. %2: \nJSON parameter: %3\nJSON object: %4.\nfile: %5")
.arg(jsonSet.getIntro(),
jsonSet.getErrMsg(),
jsonSet.getParam(),
jsonSet.getJsonObj(),
jsonSet.getFname());
qDebug(logCritical()) << msg;
Logger::writeToConsol(QString("\nERROR: %1").arg(msg));
return 1;
}
}
return 0;
}
void UARTFixture::SetUpTestSuite()
{
Logger::setupLog();
int fail = UARTFixture::readConfig();
if (fail == true) { return; }
fail = UARTFixture::convert_map_file();
if (fail == true) { return; }
logFile = QString("%1test_cases.log").arg(path.test_log_path);
QFile::remove(logFile);
try
{
uart.connect(comPortSettings);
}
catch (ErrOpenFile &errOpen)
{
qDebug(logCritical()) << QString("%1, path: %2")
.arg(errOpen.getMessage(),
errOpen.getFilePath());
Logger::writeToConsol(QString("\nERROR: %1, path: %2\n").arg(
errOpen.getMessage(),errOpen.getFilePath()));
return;
}
}
void UARTFixture::TearDownTestSuite()
{
uart.close();
QString curTime1 = QDateTime::currentDateTime().toString("dd.MM.yyyy hh:mm:ss");
QString htmlFile = path.stand_report_html_path;
HtmlReport::generate(logFile, htmlFile, curTime1);
}
#include "../core/cli_parser.h"
#include "../core/html_report.h"
extern TestOptions g_options;
TEST_P(UARTFixture, JsonCase)
{
QString caseFile = GetParam();
runCase(caseFile);
}
void UARTFixture::runCase(
const QString& caseFile)
{
QString msgItog;
QString msg;
QString err;
QString error;
QFile caseFileJson(caseFile);
// ASSERT_TRUE(caseFileJson.open(QIODevice::ReadOnly));
if (!caseFileJson.open(QIODevice::ReadOnly))
{
msg = QString("%1, path: %2").arg("json test case not found", caseFile);
msgItog.append(msg);
qDebug(logCritical()) << msg << Qt::endl;
Logger::writeToConsol(QString("\nERROR: %1\n").arg(msg));
}
QJsonObject cfg =
QJsonDocument::fromJson(caseFileJson.readAll()).object();
QString binaryFile = cfg["binary"].toString();
QFile cmdFile(binaryFile);
// ASSERT_TRUE(cmdFile.open(QIODevice::ReadOnly));
if (!cmdFile.open(QIODevice::ReadOnly))
{
msg = QString("%1, path: %2").arg("command binary not found", binaryFile);
msgItog.append(msg);
qDebug(logCritical()) << msg << Qt::endl;
Logger::writeToConsol(QString("\nERROR: %1\n").arg(msg));
}
QByteArray tx_cmd = cmdFile.readAll();
if (!uart.send(tx_cmd, err))
{
msg = QString("%1: %2").arg("UART data did not send", err);
msgItog.append(msg);
qDebug(logCritical()) << msg << Qt::endl;
Logger::writeToConsol(QString("\nERROR: %1\n").arg(msg));
}
// ASSERT_TRUE(uart.send(tx_cmd, err));
QByteArray rx_data = uart.receive(cfg["timeout_msec"].toInt());
ProtocolHandler handler(cfg["expectations"].toObject());
handler.feed(rx_data);
QString protocolMsg;
bool passed = handler.isDone(protocolMsg);
if (!passed)
{ error = QString("Protocol validation failed:\n%1").arg(protocolMsg); }
Logger::saveTestLog(logFile, handler, cfg, passed, error);
EXPECT_TRUE(passed);
if (g_options.delayMs > 0)
{ QThread::msleep(g_options.delayMs); }
}
void UARTFixture::TearDown()
{
// QString cleanupCase =
// "configs/test_cases/test_abort.json";
// QFile f(cleanupCase);
// if (!f.open(QIODevice::ReadOnly))
// return;
// QJsonObject cfg =
// QJsonDocument::fromJson(
// f.readAll()
// ).object();
// QString binary =
// cfg["binary"].toString();
// QFile cmd(binary);
// if (!cmd.open(QIODevice::ReadOnly))
// return;
// QByteArray tx = cmd.readAll();
// QString err;
// uart.send(tx, err);
// uart.receive(
// cfg["timeout_msec"].toInt()
// );
// QThread::msleep(200);
}

30
src/tests/uart_fixture.h Normal file
View File

@@ -0,0 +1,30 @@
#pragma once
#include "../core/declarations.h"
#include "../core/uart.h"
#include "../core/logger.h"
#include "../core/test_loader.h"
#include "../core/test_stats.h"
#include <json_processor.h>
class UARTFixture :
public ::testing::TestWithParam<QString>
{
protected:
static UART uart;
static comSettings_t comPortSettings;
static path_t path;
static QString logFile;
static void SetUpTestSuite();
static void TearDownTestSuite();
static int readConfig();
static int convert_map_file();
static int extract_struct_from_elf(QString structTypeName);
void TearDown() override;
void runCase(const QString& caseFile);
private:
};