/** * @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 #include #include #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 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 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")), 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()); }