TetraIF migration and Unittest Implementation for it
Entire-Checkpoint: 14d1fef0cf36
This commit is contained in:
1 parent
a86f9b5bac
commit
ba212f9dce
78 files changed
+1611
-640
No files matched your search
@@ -0,0 +1,32 @@
|
||||
# Unit tests for LogView.
|
||||
# LogView itself is an executable, so it cannot be linked into a test binary.
|
||||
# The non-GUI sources under test are compiled directly into the test executable
|
||||
# instead. GoogleTest is resolved once in the top-level CMakeLists.txt.
|
||||
|
||||
set(logview_under_test
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../logger.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../LogViewField.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../LogViewXdrElement.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../LogViewXdrStandardType.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../LogViewTetraTelegramsParser.cpp
|
||||
)
|
||||
|
||||
add_executable(LogViewTests
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../unittest_main.cpp
|
||||
LogViewFieldTest.cpp
|
||||
LogViewTetraTelegramsParserTest.cpp
|
||||
${logview_under_test}
|
||||
)
|
||||
|
||||
target_include_directories(LogViewTests PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||
)
|
||||
|
||||
target_link_libraries(LogViewTests PRIVATE
|
||||
NGLLogger
|
||||
Qt6::Core
|
||||
GTest::gtest
|
||||
)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(LogViewTests)
|
||||
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* @file LogViewFieldTest.cpp
|
||||
* @brief Unit tests for N_LogView::LogViewField.
|
||||
*
|
||||
* LogViewField is the tree node LogView builds from a telegram specification.
|
||||
* These tests cover the name/label handling, the value list, the child tree and
|
||||
* the timestamp-field detection. They deliberately avoid the accessors that
|
||||
* dereference the XDR element, which needs a parsed specification file.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
#include "LogViewField.hpp"
|
||||
|
||||
using N_LogView::LogViewField;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
LogViewField makeField(const QString &name,
|
||||
LogViewField::E_FieldKind kind = LogViewField::e_FieldKindEdit)
|
||||
{
|
||||
return LogViewField(name, kind, nullptr);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(LogViewFieldTest, ConstructorStoresNameAndKind)
|
||||
{
|
||||
const LogViewField field = makeField(QStringLiteral("issi"), LogViewField::e_FieldKindBorder);
|
||||
|
||||
EXPECT_EQ(field.name(), QStringLiteral("issi"));
|
||||
EXPECT_EQ(field.kind(), LogViewField::e_FieldKindBorder);
|
||||
EXPECT_EQ(field.xdrElement(), nullptr);
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, SetNameReplacesTheName)
|
||||
{
|
||||
LogViewField field = makeField(QStringLiteral("old"));
|
||||
|
||||
field.setName(QStringLiteral("new"));
|
||||
|
||||
EXPECT_EQ(field.name(), QStringLiteral("new"));
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, MakeCleanCapitalisesWordsAndDropsUnderscores)
|
||||
{
|
||||
EXPECT_EQ(LogViewField::makeClean(QStringLiteral("utm_nordwert")),
|
||||
QStringLiteral("Utm Nordwert"));
|
||||
EXPECT_EQ(LogViewField::makeClean(QStringLiteral("issi")), QStringLiteral("Issi"));
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, MakeCleanStripsTheRecordTypeSuffix)
|
||||
{
|
||||
EXPECT_EQ(LogViewField::makeClean(QStringLiteral("gef_header_rt")),
|
||||
QStringLiteral("Gef Header"));
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, MakeCleanHandlesTheEmptyString)
|
||||
{
|
||||
EXPECT_EQ(LogViewField::makeClean(QString()), QString());
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, CleanNameUsesMakeClean)
|
||||
{
|
||||
const LogViewField field = makeField(QStringLiteral("gps_zeit"));
|
||||
|
||||
EXPECT_EQ(field.cleanName(), LogViewField::makeClean(QStringLiteral("gps_zeit")));
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, ValueListStartsEmpty)
|
||||
{
|
||||
const LogViewField field = makeField(QStringLiteral("issi"));
|
||||
|
||||
EXPECT_EQ(field.valueListSize(), 0);
|
||||
EXPECT_TRUE(field.value(0).isEmpty());
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, AddValueAppendsInOrder)
|
||||
{
|
||||
LogViewField field = makeField(QStringLiteral("issi"));
|
||||
|
||||
field.addValue(QStringLiteral("11"));
|
||||
field.addValue(QStringLiteral("22"));
|
||||
|
||||
EXPECT_EQ(field.valueListSize(), 2);
|
||||
EXPECT_EQ(field.value(0), QStringLiteral("11"));
|
||||
EXPECT_EQ(field.value(1), QStringLiteral("22"));
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, ValueReturnsEmptyForAnOutOfRangeIndex)
|
||||
{
|
||||
LogViewField field = makeField(QStringLiteral("issi"));
|
||||
field.addValue(QStringLiteral("11"));
|
||||
|
||||
EXPECT_TRUE(field.value(1).isEmpty());
|
||||
EXPECT_TRUE(field.value(99).isEmpty());
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, AddValueBinaryHexExpandsToDecimalBytes)
|
||||
{
|
||||
LogViewField field = makeField(QStringLiteral("payload"));
|
||||
|
||||
field.addValueBinaryHex(QStringLiteral("00ff10"));
|
||||
|
||||
ASSERT_EQ(field.valueListSize(), 3);
|
||||
EXPECT_EQ(field.value(0), QStringLiteral("0"));
|
||||
EXPECT_EQ(field.value(1), QStringLiteral("255"));
|
||||
EXPECT_EQ(field.value(2), QStringLiteral("16"));
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, DateTimeFieldIsDetectedCaseInsensitively)
|
||||
{
|
||||
EXPECT_TRUE(makeField(QStringLiteral("gps_zeit")).isDateTimeField());
|
||||
EXPECT_TRUE(makeField(QStringLiteral("GPS_ZEIT")).isDateTimeField());
|
||||
EXPECT_FALSE(makeField(QStringLiteral("issi")).isDateTimeField());
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, MSecsFieldIsDetectedCaseInsensitively)
|
||||
{
|
||||
EXPECT_TRUE(makeField(QStringLiteral("timestamp")).isMSecsField());
|
||||
EXPECT_TRUE(makeField(QStringLiteral("EffectStart")).isMSecsField());
|
||||
EXPECT_FALSE(makeField(QStringLiteral("gps_zeit")).isMSecsField());
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, RawValueSkipsTheTimestampConversion)
|
||||
{
|
||||
LogViewField field = makeField(QStringLiteral("timestamp"));
|
||||
field.addValue(QStringLiteral("1500000000000"));
|
||||
|
||||
EXPECT_EQ(field.value(0, true), QStringLiteral("1500000000000"));
|
||||
EXPECT_NE(field.value(0), QStringLiteral("1500000000000"));
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, ScalarPropertiesRoundTrip)
|
||||
{
|
||||
LogViewField field = makeField(QStringLiteral("issi"));
|
||||
|
||||
field.setType(QStringLiteral("uint32_t"));
|
||||
field.setSize(4);
|
||||
field.setIsRightAligned(true);
|
||||
field.setToolTipText(QStringLiteral("individual subscriber identity"));
|
||||
field.setHasContent(false);
|
||||
|
||||
EXPECT_EQ(field.type(), QStringLiteral("uint32_t"));
|
||||
EXPECT_EQ(field.size(), 4);
|
||||
EXPECT_TRUE(field.isRightAligned());
|
||||
EXPECT_EQ(field.toolTipText(), QStringLiteral("individual subscriber identity"));
|
||||
EXPECT_FALSE(field.hasContent());
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, MaxArraySizeCarriesTheDynamicFlag)
|
||||
{
|
||||
LogViewField field = makeField(QStringLiteral("players"));
|
||||
|
||||
field.setMaxArraySize(8, false);
|
||||
EXPECT_EQ(field.maxArraySize(), 8);
|
||||
EXPECT_FALSE(field.isDynamicArray());
|
||||
|
||||
field.setMaxArraySize(16, true);
|
||||
EXPECT_EQ(field.maxArraySize(), 16);
|
||||
EXPECT_TRUE(field.isDynamicArray());
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, JsonPathRoundTrips)
|
||||
{
|
||||
LogViewField field = makeField(QStringLiteral("issi"));
|
||||
const QStringList path{QStringLiteral("header"), QStringLiteral("issi")};
|
||||
|
||||
field.setJsonPath(path);
|
||||
|
||||
EXPECT_EQ(field.jsonPath(), path);
|
||||
}
|
||||
|
||||
TEST(LogViewFieldTest, ChildrenAreOwnedAndOrdered)
|
||||
{
|
||||
LogViewField parent(QStringLiteral("header"), LogViewField::e_FieldKindBorder, nullptr);
|
||||
EXPECT_EQ(parent.childCount(), 0);
|
||||
|
||||
auto *first = new LogViewField(QStringLiteral("a"), LogViewField::e_FieldKindEdit, nullptr);
|
||||
auto *second = new LogViewField(QStringLiteral("b"), LogViewField::e_FieldKindEdit, nullptr);
|
||||
parent.addChild(first);
|
||||
parent.addChild(second);
|
||||
|
||||
ASSERT_EQ(parent.childCount(), 2);
|
||||
EXPECT_EQ(parent.child(0), first);
|
||||
EXPECT_EQ(parent.child(1), second);
|
||||
|
||||
parent.removeLastChild();
|
||||
EXPECT_EQ(parent.childCount(), 1);
|
||||
EXPECT_EQ(parent.child(0), first);
|
||||
}
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
/**
|
||||
* @file LogViewTetraTelegramsParserTest.cpp
|
||||
* @brief Unit tests for N_LogView::LogViewTetraTelegramsParser.
|
||||
*
|
||||
* The parser reads the C++ telegram specification header and exposes the
|
||||
* structs, enums and POD types it found. It is pure logic with no GUI
|
||||
* dependency, so each test feeds it a small specification written to a
|
||||
* temporary file and inspects the resulting model.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QTemporaryDir>
|
||||
|
||||
#include "LogViewTetraTelegramsParser.hpp"
|
||||
|
||||
using N_LogView::EnumInformation;
|
||||
using N_LogView::LogViewTetraTelegramsParser;
|
||||
using N_LogView::MemberInformation;
|
||||
using N_LogView::PodInformation;
|
||||
using N_LogView::StructInformation;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// Writes a specification snippet to a file inside dir and parses it.
|
||||
bool parseSpecification(LogViewTetraTelegramsParser &parser,
|
||||
const QTemporaryDir &dir,
|
||||
const QString &content)
|
||||
{
|
||||
const QString path = dir.filePath(QStringLiteral("spec.h"));
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
||||
return false;
|
||||
f.write(content.toUtf8());
|
||||
f.close();
|
||||
return parser.parse(path);
|
||||
}
|
||||
|
||||
const MemberInformation *findMember(const StructInformation &si, const QString &name)
|
||||
{
|
||||
for (const MemberInformation &mi : si.member)
|
||||
{
|
||||
if (mi.name == name)
|
||||
return &mi;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(LogViewTetraTelegramsParserTest, BuiltinPodTypesAreKnownUpFront)
|
||||
{
|
||||
const LogViewTetraTelegramsParser parser;
|
||||
|
||||
EXPECT_TRUE(parser.isPod(QStringLiteral("bool")));
|
||||
EXPECT_TRUE(parser.isPod(QStringLiteral("int32_t")));
|
||||
EXPECT_FALSE(parser.isPod(QStringLiteral("NotAType")));
|
||||
|
||||
const PodInformation pod = parser.podFromType(QStringLiteral("int32_t"));
|
||||
EXPECT_EQ(pod.byteLen, 4);
|
||||
EXPECT_TRUE(pod.isIntegral);
|
||||
EXPECT_TRUE(pod.isSigned);
|
||||
}
|
||||
|
||||
TEST(LogViewTetraTelegramsParserTest, ParsingAMissingFileFails)
|
||||
{
|
||||
LogViewTetraTelegramsParser parser;
|
||||
|
||||
EXPECT_FALSE(parser.parse(QStringLiteral("/does/not/exist/spec.h")));
|
||||
}
|
||||
|
||||
TEST(LogViewTetraTelegramsParserTest, ASimpleStructIsCollected)
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
ASSERT_TRUE(dir.isValid());
|
||||
|
||||
LogViewTetraTelegramsParser parser;
|
||||
ASSERT_TRUE(parseSpecification(parser, dir, QStringLiteral(
|
||||
"struct Header\n"
|
||||
"{\n"
|
||||
" int32_t equipmentNumber;\n"
|
||||
" double offset;\n"
|
||||
"};\n")));
|
||||
|
||||
ASSERT_TRUE(parser.isStruct(QStringLiteral("Header")));
|
||||
const StructInformation si = parser.structFromType(QStringLiteral("Header"));
|
||||
EXPECT_EQ(si.type, QStringLiteral("Header"));
|
||||
EXPECT_TRUE(si.baseStruct.isEmpty());
|
||||
ASSERT_EQ(si.member.size(), 2);
|
||||
|
||||
const MemberInformation *eqNr = findMember(si, QStringLiteral("equipmentNumber"));
|
||||
ASSERT_NE(eqNr, nullptr);
|
||||
EXPECT_EQ(eqNr->type, QStringLiteral("int32_t"));
|
||||
EXPECT_TRUE(eqNr->isPod);
|
||||
EXPECT_FALSE(eqNr->isArray);
|
||||
}
|
||||
|
||||
TEST(LogViewTetraTelegramsParserTest, UnsignedFixedWidthTypesAreNotBuiltinPods)
|
||||
{
|
||||
// The built-in POD table only holds bool, int, int32_t, qint64, float and
|
||||
// double. Unsigned fixed-width types are therefore not recognised as PODs.
|
||||
const LogViewTetraTelegramsParser parser;
|
||||
|
||||
EXPECT_FALSE(parser.isPod(QStringLiteral("uint32_t")));
|
||||
EXPECT_FALSE(parser.isPod(QStringLiteral("uint8_t")));
|
||||
}
|
||||
|
||||
TEST(LogViewTetraTelegramsParserTest, BaseStructIsRecorded)
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
ASSERT_TRUE(dir.isValid());
|
||||
|
||||
LogViewTetraTelegramsParser parser;
|
||||
ASSERT_TRUE(parseSpecification(parser, dir, QStringLiteral(
|
||||
"struct Header\n"
|
||||
"{\n"
|
||||
" uint32_t equipmentNumber;\n"
|
||||
"};\n"
|
||||
"struct Position : public Header\n"
|
||||
"{\n"
|
||||
" int32_t easting;\n"
|
||||
"};\n")));
|
||||
|
||||
ASSERT_TRUE(parser.isStruct(QStringLiteral("Position")));
|
||||
const StructInformation si = parser.structFromType(QStringLiteral("Position"));
|
||||
EXPECT_EQ(si.baseStruct, QStringLiteral("Header"));
|
||||
// Base members are merged in after parsing, so the derived struct sees both.
|
||||
EXPECT_NE(findMember(si, QStringLiteral("easting")), nullptr);
|
||||
EXPECT_NE(findMember(si, QStringLiteral("equipmentNumber")), nullptr);
|
||||
}
|
||||
|
||||
TEST(LogViewTetraTelegramsParserTest, EnumValuesAreNumberedImplicitlyAndExplicitly)
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
ASSERT_TRUE(dir.isValid());
|
||||
|
||||
LogViewTetraTelegramsParser parser;
|
||||
ASSERT_TRUE(parseSpecification(parser, dir, QStringLiteral(
|
||||
"enum class State\n"
|
||||
"{\n"
|
||||
" Unknown = 0,\n"
|
||||
" Alive = 1,\n"
|
||||
" Dead = 7,\n"
|
||||
"};\n")));
|
||||
|
||||
ASSERT_TRUE(parser.isEnum(QStringLiteral("State")));
|
||||
const EnumInformation ei = parser.enumFromType(QStringLiteral("State"));
|
||||
ASSERT_EQ(ei.values.size(), 3);
|
||||
EXPECT_EQ(ei.values.at(0).key, QStringLiteral("Unknown"));
|
||||
EXPECT_EQ(ei.values.at(0).value, 0);
|
||||
EXPECT_EQ(ei.values.at(1).key, QStringLiteral("Alive"));
|
||||
EXPECT_EQ(ei.values.at(1).value, 1);
|
||||
EXPECT_EQ(ei.values.at(2).key, QStringLiteral("Dead"));
|
||||
EXPECT_EQ(ei.values.at(2).value, 7);
|
||||
}
|
||||
|
||||
TEST(LogViewTetraTelegramsParserTest, ImplicitEnumValuesKeepTheTrailingComma)
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
ASSERT_TRUE(dir.isValid());
|
||||
|
||||
LogViewTetraTelegramsParser parser;
|
||||
ASSERT_TRUE(parseSpecification(parser, dir, QStringLiteral(
|
||||
"enum class State\n"
|
||||
"{\n"
|
||||
" Unknown,\n"
|
||||
" Alive,\n"
|
||||
"};\n")));
|
||||
|
||||
const EnumInformation ei = parser.enumFromType(QStringLiteral("State"));
|
||||
ASSERT_EQ(ei.values.size(), 2);
|
||||
// Known parser limitation: the trailing comma is only stripped from the value
|
||||
// of an "Key = value," entry, never from the key of an implicitly numbered
|
||||
// one. The shipped specification files always use explicit values.
|
||||
EXPECT_EQ(ei.values.at(0).key, QStringLiteral("Unknown,"));
|
||||
EXPECT_EQ(ei.values.at(0).value, 0);
|
||||
EXPECT_EQ(ei.values.at(1).key, QStringLiteral("Alive,"));
|
||||
EXPECT_EQ(ei.values.at(1).value, 1);
|
||||
}
|
||||
|
||||
TEST(LogViewTetraTelegramsParserTest, StringAndBinaryMembersAreCategorised)
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
ASSERT_TRUE(dir.isValid());
|
||||
|
||||
LogViewTetraTelegramsParser parser;
|
||||
ASSERT_TRUE(parseSpecification(parser, dir, QStringLiteral(
|
||||
"struct Payload\n"
|
||||
"{\n"
|
||||
" std::string name;\n"
|
||||
" std::vector<unsigned char> raw;\n"
|
||||
"};\n")));
|
||||
|
||||
const StructInformation si = parser.structFromType(QStringLiteral("Payload"));
|
||||
const MemberInformation *name = findMember(si, QStringLiteral("name"));
|
||||
const MemberInformation *raw = findMember(si, QStringLiteral("raw"));
|
||||
ASSERT_NE(name, nullptr);
|
||||
ASSERT_NE(raw, nullptr);
|
||||
EXPECT_TRUE(name->isString);
|
||||
EXPECT_TRUE(raw->isBinary);
|
||||
}
|
||||
|
||||
TEST(LogViewTetraTelegramsParserTest, VectorMembersAreMarkedAsArrays)
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
ASSERT_TRUE(dir.isValid());
|
||||
|
||||
LogViewTetraTelegramsParser parser;
|
||||
ASSERT_TRUE(parseSpecification(parser, dir, QStringLiteral(
|
||||
"struct Group\n"
|
||||
"{\n"
|
||||
" std::vector<uint32_t> issis;\n"
|
||||
"};\n")));
|
||||
|
||||
const StructInformation si = parser.structFromType(QStringLiteral("Group"));
|
||||
const MemberInformation *issis = findMember(si, QStringLiteral("issis"));
|
||||
ASSERT_NE(issis, nullptr);
|
||||
EXPECT_TRUE(issis->isArray);
|
||||
}
|
||||
|
||||
TEST(LogViewTetraTelegramsParserTest, GetInnerTypeUnwrapsTemplateArguments)
|
||||
{
|
||||
EXPECT_EQ(LogViewTetraTelegramsParser::getInnerType(QStringLiteral("std::vector<uint32_t>")),
|
||||
QStringLiteral("uint32_t"));
|
||||
EXPECT_TRUE(LogViewTetraTelegramsParser::getInnerType(QStringLiteral("uint32_t")).isEmpty());
|
||||
}
|
||||
|
||||
TEST(LogViewTetraTelegramsParserTest, NoTelegramsAreVisibleWithoutConstraints)
|
||||
{
|
||||
QTemporaryDir dir;
|
||||
ASSERT_TRUE(dir.isValid());
|
||||
|
||||
LogViewTetraTelegramsParser parser;
|
||||
ASSERT_TRUE(parseSpecification(parser, dir, QStringLiteral(
|
||||
"struct Header\n"
|
||||
"{\n"
|
||||
" uint32_t equipmentNumber;\n"
|
||||
"};\n")));
|
||||
|
||||
EXPECT_TRUE(parser.telegramsToCreate().isEmpty());
|
||||
EXPECT_TRUE(parser.nameFromTelegramId(1).isEmpty());
|
||||
}
|
||||
Reference in new issue
Block a user