From 6179eb954a78b25cebd5f6f437f5fc1da2bd8269 Mon Sep 17 00:00:00 2001 From: Danila Date: Mon, 18 May 2026 15:21:18 +0300 Subject: [PATCH] first version of embedded test stand done --- .gitignore | 38 +++ config.json | 21 ++ .../test_endurance_50_60_marchFTE_5.json | 34 ++ .../test_get_badBlockMap_fromNAND.json | 29 ++ .../test_randomDataTest_100_110.json | 32 ++ .../test_set_micron_MT29F16G08AJADAWP.json | 37 +++ ...est_start_March_FTE_all_targ0_repeat2.json | 34 ++ .../test_start_March_FTE_by_blocks.json | 54 ++++ configs/test_plan.json | 25 ++ src/core/cli_parser.cpp | 42 +++ src/core/cli_parser.h | 12 + src/core/declarations.h | 68 ++++ src/core/exceptions_handle.h | 94 ++++++ src/core/html_report.cpp | 158 +++++++++ src/core/html_report.h | 14 + src/core/json_processor.cpp | 182 +++++++++++ src/core/json_processor.h | 39 +++ src/core/logger.cpp | 127 ++++++++ src/core/logger.h | 39 +++ src/core/loggingCategories.cpp | 6 + src/core/loggingCategories.h | 11 + src/core/protocol_handler.cpp | 194 +++++++++++ src/core/protocol_handler.h | 31 ++ src/core/test_loader.cpp | 47 +++ src/core/test_loader.h | 11 + src/core/test_options.cpp | 1 + src/core/test_options.h | 17 + src/core/test_stats.h | 12 + src/core/uart.cpp | 59 ++++ src/core/uart.h | 22 ++ src/gtest_dependency.pri | 39 +++ src/main.cpp | 22 ++ src/tests/test_runner.cpp | 157 +++++++++ src/tests/uart_fixture.cpp | 304 ++++++++++++++++++ src/tests/uart_fixture.h | 30 ++ 35 files changed, 2042 insertions(+) create mode 100644 .gitignore create mode 100644 config.json create mode 100644 configs/test_cases/test_endurance_50_60_marchFTE_5.json create mode 100644 configs/test_cases/test_get_badBlockMap_fromNAND.json create mode 100644 configs/test_cases/test_randomDataTest_100_110.json create mode 100644 configs/test_cases/test_set_micron_MT29F16G08AJADAWP.json create mode 100644 configs/test_cases/test_start_March_FTE_all_targ0_repeat2.json create mode 100644 configs/test_cases/test_start_March_FTE_by_blocks.json create mode 100644 configs/test_plan.json create mode 100644 src/core/cli_parser.cpp create mode 100644 src/core/cli_parser.h create mode 100644 src/core/declarations.h create mode 100644 src/core/exceptions_handle.h create mode 100644 src/core/html_report.cpp create mode 100644 src/core/html_report.h create mode 100644 src/core/json_processor.cpp create mode 100644 src/core/json_processor.h create mode 100644 src/core/logger.cpp create mode 100644 src/core/logger.h create mode 100644 src/core/loggingCategories.cpp create mode 100644 src/core/loggingCategories.h create mode 100644 src/core/protocol_handler.cpp create mode 100644 src/core/protocol_handler.h create mode 100644 src/core/test_loader.cpp create mode 100644 src/core/test_loader.h create mode 100644 src/core/test_options.cpp create mode 100644 src/core/test_options.h create mode 100644 src/core/test_stats.h create mode 100644 src/core/uart.cpp create mode 100644 src/core/uart.h create mode 100644 src/gtest_dependency.pri create mode 100644 src/main.cpp create mode 100644 src/tests/test_runner.cpp create mode 100644 src/tests/uart_fixture.cpp create mode 100644 src/tests/uart_fixture.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4275c91 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/config.json b/config.json new file mode 100644 index 0000000..6a05416 --- /dev/null +++ b/config.json @@ -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" + } +} diff --git a/configs/test_cases/test_endurance_50_60_marchFTE_5.json b/configs/test_cases/test_endurance_50_60_marchFTE_5.json new file mode 100644 index 0000000..fda14a6 --- /dev/null +++ b/configs/test_cases/test_endurance_50_60_marchFTE_5.json @@ -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 +} diff --git a/configs/test_cases/test_get_badBlockMap_fromNAND.json b/configs/test_cases/test_get_badBlockMap_fromNAND.json new file mode 100644 index 0000000..0a7eea0 --- /dev/null +++ b/configs/test_cases/test_get_badBlockMap_fromNAND.json @@ -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 +} diff --git a/configs/test_cases/test_randomDataTest_100_110.json b/configs/test_cases/test_randomDataTest_100_110.json new file mode 100644 index 0000000..b7b50e5 --- /dev/null +++ b/configs/test_cases/test_randomDataTest_100_110.json @@ -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 +} diff --git a/configs/test_cases/test_set_micron_MT29F16G08AJADAWP.json b/configs/test_cases/test_set_micron_MT29F16G08AJADAWP.json new file mode 100644 index 0000000..b071f61 --- /dev/null +++ b/configs/test_cases/test_set_micron_MT29F16G08AJADAWP.json @@ -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 +} diff --git a/configs/test_cases/test_start_March_FTE_all_targ0_repeat2.json b/configs/test_cases/test_start_March_FTE_all_targ0_repeat2.json new file mode 100644 index 0000000..11b20e9 --- /dev/null +++ b/configs/test_cases/test_start_March_FTE_all_targ0_repeat2.json @@ -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 +} diff --git a/configs/test_cases/test_start_March_FTE_by_blocks.json b/configs/test_cases/test_start_March_FTE_by_blocks.json new file mode 100644 index 0000000..b21a361 --- /dev/null +++ b/configs/test_cases/test_start_March_FTE_by_blocks.json @@ -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 +} diff --git a/configs/test_plan.json b/configs/test_plan.json new file mode 100644 index 0000000..66a005b --- /dev/null +++ b/configs/test_plan.json @@ -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" + ] + } +} diff --git a/src/core/cli_parser.cpp b/src/core/cli_parser.cpp new file mode 100644 index 0000000..f3dd76f --- /dev/null +++ b/src/core/cli_parser.cpp @@ -0,0 +1,42 @@ +#include "cli_parser.h" + +#include + +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; +} diff --git a/src/core/cli_parser.h b/src/core/cli_parser.h new file mode 100644 index 0000000..4db5d85 --- /dev/null +++ b/src/core/cli_parser.h @@ -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 diff --git a/src/core/declarations.h b/src/core/declarations.h new file mode 100644 index 0000000..aed68cc --- /dev/null +++ b/src/core/declarations.h @@ -0,0 +1,68 @@ +#ifndef DECLARATIONS_H +#define DECLARATIONS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "QLoggingCategory" +#include "QProcess" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 diff --git a/src/core/exceptions_handle.h b/src/core/exceptions_handle.h new file mode 100644 index 0000000..5cb3b21 --- /dev/null +++ b/src/core/exceptions_handle.h @@ -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 + diff --git a/src/core/html_report.cpp b/src/core/html_report.cpp new file mode 100644 index 0000000..9a6809e --- /dev/null +++ b/src/core/html_report.cpp @@ -0,0 +1,158 @@ +#include "html_report.h" + +#include +#include +#include + +static QMap extractSummary( + const QString& content) +{ + QMap 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 += QString("

Tests Report (%1)

").arg(curTime); + + html += QString(R"( +

SUMMARY

+ +
+ Total: %1
+ Passed: %2
+ Failed: %3
+
+ )") + .arg(summary["total"]) + .arg(summary["passed"]) + .arg(summary["failed"]); + + QStringList tests = + content.split("<<>>"); + + 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"( +
+ %2 +
%3
+
+ )") + .arg(cls) + .arg(title) + .arg(t.toHtmlEscaped()); + } + + int passRate = 0; + + if (summary["total"] > 0) + { + passRate = + (100 * summary["passed"]) + / summary["total"]; + } + + html += QString(R"( +
+
+ %1% +
+
+ )").arg(passRate); + + html += ""; + + QFile out(htmlFile); + + out.open(QIODevice::WriteOnly); + + out.write(html.toUtf8()); + + out.close(); +} diff --git a/src/core/html_report.h b/src/core/html_report.h new file mode 100644 index 0000000..f329930 --- /dev/null +++ b/src/core/html_report.h @@ -0,0 +1,14 @@ +#ifndef HTML_REPORT_H +#define HTML_REPORT_H + +#include + +class HtmlReport +{ +public: + + static void generate(const QString& logFile, + const QString& htmlFile, QString curTime); +}; + +#endif // HTML_REPORT_H diff --git a/src/core/json_processor.cpp b/src/core/json_processor.cpp new file mode 100644 index 0000000..ea27f4b --- /dev/null +++ b/src/core/json_processor.cpp @@ -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 *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 *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 *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 ¶mValue, 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); +} diff --git a/src/core/json_processor.h b/src/core/json_processor.h new file mode 100644 index 0000000..d529dc2 --- /dev/null +++ b/src/core/json_processor.h @@ -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 *setToStructArr, int arrSize); + virtual void jsonConvertStruct(QJsonObject obj, + QString jsonParamName, QVector *setToStructArr); + virtual void jsonConvertStruct(QJsonObject obj, + QString jsonParamName, QVector *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 ¶mValue, QString jsonObjName); + + +private: + QString jsonPath = ""; +}; + +#endif // JSONPROCESSOR_H diff --git a/src/core/logger.cpp b/src/core/logger.cpp new file mode 100644 index 0000000..094a493 --- /dev/null +++ b/src/core/logger.cpp @@ -0,0 +1,127 @@ +#include "logger.h" + +// Умный указатель на файл логирования +QScopedPointer 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<<>>\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 += "<<>>\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(); // Очищаем буферизированные данные + +} diff --git a/src/core/logger.h b/src/core/logger.h new file mode 100644 index 0000000..e03a43d --- /dev/null +++ b/src/core/logger.h @@ -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 diff --git a/src/core/loggingCategories.cpp b/src/core/loggingCategories.cpp new file mode 100644 index 0000000..00b5814 --- /dev/null +++ b/src/core/loggingCategories.cpp @@ -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") diff --git a/src/core/loggingCategories.h b/src/core/loggingCategories.h new file mode 100644 index 0000000..5445bfc --- /dev/null +++ b/src/core/loggingCategories.h @@ -0,0 +1,11 @@ +#ifndef LOGGINGCATEGORIES_H +#define LOGGINGCATEGORIES_H + +#include + +Q_DECLARE_LOGGING_CATEGORY(logDebug) +Q_DECLARE_LOGGING_CATEGORY(logInfo) +Q_DECLARE_LOGGING_CATEGORY(logWarning) +Q_DECLARE_LOGGING_CATEGORY(logCritical) + +#endif // LOGGINGCATEGORIES_H diff --git a/src/core/protocol_handler.cpp b/src/core/protocol_handler.cpp new file mode 100644 index 0000000..d4f496d --- /dev/null +++ b/src/core/protocol_handler.cpp @@ -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 + ); +} diff --git a/src/core/protocol_handler.h b/src/core/protocol_handler.h new file mode 100644 index 0000000..8e987ec --- /dev/null +++ b/src/core/protocol_handler.h @@ -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; + +}; diff --git a/src/core/test_loader.cpp b/src/core/test_loader.cpp new file mode 100644 index 0000000..d40b9b4 --- /dev/null +++ b/src/core/test_loader.cpp @@ -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; +} diff --git a/src/core/test_loader.h b/src/core/test_loader.h new file mode 100644 index 0000000..954d3eb --- /dev/null +++ b/src/core/test_loader.h @@ -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); +}; diff --git a/src/core/test_options.cpp b/src/core/test_options.cpp new file mode 100644 index 0000000..3a3ce7c --- /dev/null +++ b/src/core/test_options.cpp @@ -0,0 +1 @@ +#include "test_options.h" diff --git a/src/core/test_options.h b/src/core/test_options.h new file mode 100644 index 0000000..256234e --- /dev/null +++ b/src/core/test_options.h @@ -0,0 +1,17 @@ +#ifndef TEST_OPTIONS_H +#define TEST_OPTIONS_H + +#include + +struct TestOptions +{ + QString group = "full"; + + QString caseFilter; + + int repeat = 1; + + int delayMs = 0; +}; + +#endif // TEST_OPTIONS_H diff --git a/src/core/test_stats.h b/src/core/test_stats.h new file mode 100644 index 0000000..c87236d --- /dev/null +++ b/src/core/test_stats.h @@ -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 diff --git a/src/core/uart.cpp b/src/core/uart.cpp new file mode 100644 index 0000000..1505435 --- /dev/null +++ b/src/core/uart.cpp @@ -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(); +} diff --git a/src/core/uart.h b/src/core/uart.h new file mode 100644 index 0000000..bea7524 --- /dev/null +++ b/src/core/uart.h @@ -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; +}; diff --git a/src/gtest_dependency.pri b/src/gtest_dependency.pri new file mode 100644 index 0000000..8a5b134 --- /dev/null +++ b/src/gtest_dependency.pri @@ -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 +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..60d6934 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,22 @@ +#include + +#include "../core/cli_parser.h" +#include +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; +} diff --git a/src/tests/test_runner.cpp b/src/tests/test_runner.cpp new file mode 100644 index 0000000..144ffa7 --- /dev/null +++ b/src/tests/test_runner.cpp @@ -0,0 +1,157 @@ +#include "uart_fixture.h" +#include "../core/cli_parser.h" + +extern TestOptions g_options; + +std::vector getJsonTests() +{ + QStringList list; + std::vector 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& 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); +//} diff --git a/src/tests/uart_fixture.cpp b/src/tests/uart_fixture.cpp new file mode 100644 index 0000000..e26a740 --- /dev/null +++ b/src/tests/uart_fixture.cpp @@ -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); +} diff --git a/src/tests/uart_fixture.h b/src/tests/uart_fixture.h new file mode 100644 index 0000000..8c22426 --- /dev/null +++ b/src/tests/uart_fixture.h @@ -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 + +class UARTFixture : + public ::testing::TestWithParam +{ +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: +};