/* SPDX-License-Identifier: Unlicense */ #include "tracetab.h" #include "doctest/doctest.h" #include "debug.h" #include #define STRING_SLICE(s) StringSlice{ s, (sizeof s) - 1u } struct StringSlice { const char *str; size_t len; }; TEST_CASE("PC trace") { const auto data = STRING_SLICE("512\n0x202\n0b1000000100\n01006\n"); TraceTable tt{}; const bool ok = ParseTraceData(tt, data.str, data.len, stderr); REQUIRE(ok); CHECK_EQ(tt.TypesCount(), 0); CHECK_EQ(tt.NodesCount(), 4); CHECK_EQ(tt.Shstr(), nullptr); CHECK_EQ(tt.Node(0), TraceNode::Pc(512)); CHECK_EQ(tt.Node(1), TraceNode::Pc(0x202)); CHECK_EQ(tt.Node(2), TraceNode::Pc(0b1000000100)); CHECK_EQ(tt.Node(3), TraceNode::Pc(01006)); } TEST_CASE("pc, fn and mixed newlines") { const auto data = STRING_SLICE( "0x200 pc __start\r\n\r0x491ac fn 30 printf\n\n\n0x49c78 fn 100 fputs"); TraceTable tt{}; const bool ok = ParseTraceData(tt, data.str, data.len, stderr); REQUIRE(ok); CHECK_EQ(tt.TypesCount(), 0); REQUIRE_EQ(tt.NodesCount(), 3); CHECK_NE(tt.Shstr(), nullptr); { const auto n = tt.Node(0); CHECK_EQ(n.kind, TraceNodeKind::kPc); CHECK_EQ(n.address, 0x200); CHECK_EQ(n.size, 0); CHECK_NE(n.name, 0); TRACE("tt.Shstr(n.name) = %s\n", tt.Shstr(n.name)); CHECK_EQ(0, strcmp("__start", tt.Shstr(n.name))); } { const auto n = tt.Node(1); CHECK_EQ(n.kind, TraceNodeKind::kFunction); CHECK_EQ(n.address, 0x491ac); CHECK_EQ(n.size, 30); CHECK_NE(n.name, 0); TRACE("tt.Shstr(n.name) = %s\n", tt.Shstr(n.name)); CHECK_EQ(0, strcmp("printf", tt.Shstr(n.name))); } { const auto n = tt.Node(2); CHECK_EQ(n.kind, TraceNodeKind::kFunction); CHECK_EQ(n.address, 0x49c78); CHECK_EQ(n.size, 100); CHECK_NE(n.name, 0); TRACE("tt.Shstr(n.name) = %s\n", tt.Shstr(n.name)); CHECK_EQ(0, strcmp("fputs", tt.Shstr(n.name))); } } TEST_CASE("str, strz and blob") { const auto data = STRING_SLICE( "0x100 str 0x10 smd_header_sega_genesis\n" "0xbc1b strz 13 windtrap_wsa\n" "0x29bb0 blob 256 copy_of_smd_header\n"); TraceTable tt{}; const bool ok = ParseTraceData(tt, data.str, data.len, stderr); REQUIRE(ok); CHECK_EQ(tt.TypesCount(), 0); REQUIRE_EQ(tt.NodesCount(), 3); CHECK_NE(tt.Shstr(), nullptr); { const auto n = tt.Node(0); CHECK_EQ(n.kind, TraceNodeKind::kData); CHECK_EQ(n.address, 0x100); CHECK_NE(n.name, 0); CHECK_EQ(0, strcmp("smd_header_sega_genesis", tt.Shstr(n.name))); CHECK_EQ(n.data_type.kind, DataTypeKind::kStr); CHECK_EQ(n.data_type.count, 0x10); CHECK_EQ(n.data_type.count * n.data_type.BaseSize(), n.size); CHECK_EQ(0x10, n.size); } { const auto n = tt.Node(1); CHECK_EQ(n.kind, TraceNodeKind::kData); CHECK_EQ(n.address, 0xbc1b); CHECK_NE(n.name, 0); CHECK_EQ(0, strcmp("windtrap_wsa", tt.Shstr(n.name))); CHECK_EQ(n.data_type.kind, DataTypeKind::kStrz); CHECK_EQ(n.data_type.count, 13); CHECK_EQ(n.data_type.count * n.data_type.BaseSize(), n.size); CHECK_EQ(13, n.size); } { const auto n = tt.Node(2); CHECK_EQ(n.kind, TraceNodeKind::kData); CHECK_EQ(n.address, 0x29bb0); CHECK_NE(n.name, 0); CHECK_EQ(0, strcmp("copy_of_smd_header", tt.Shstr(n.name))); CHECK_EQ(n.data_type.kind, DataTypeKind::kBlob); CHECK_EQ(n.data_type.count, 256); CHECK_EQ(n.data_type.count * n.data_type.BaseSize(), n.size); CHECK_EQ(256, n.size); } } TEST_CASE("tables") { const auto data = STRING_SLICE( "0x4 [ptr] 63\n" "0x87f44 [ptr,u32,] 19 soundtest_music_name_ptrs\n" "0x88124 [ptr,u32] passwords_ptrs\n"); TraceTable tt{}; const bool ok = ParseTraceData(tt, data.str, data.len, stderr); REQUIRE(ok); REQUIRE_NE(tt.TypesCount(), 0); REQUIRE_EQ(tt.NodesCount(), 3); CHECK_NE(tt.Shstr(), nullptr); { const auto n = tt.Node(0); CHECK_EQ(n.kind, TraceNodeKind::kData); CHECK_EQ(n.address, 0x4); CHECK_EQ(n.name, 0); CHECK_EQ(n.data_type.kind, DataTypeKind::kTable); CHECK_EQ(n.data_type.count, 63); REQUIRE_EQ(n.data_type.nested_num, 1); const auto ptr_type = tt.Type(n.data_type.nested_idx); CHECK_EQ(ptr_type.kind, DataTypeKind::kPtr); CHECK_EQ(ptr_type.count, 1); CHECK_EQ(n.data_type.count * (ptr_type.count * ptr_type.BaseSize()), n.size); CHECK_NE(0, n.size); } { const auto n = tt.Node(1); CHECK_EQ(n.kind, TraceNodeKind::kData); CHECK_EQ(n.address, 0x87f44); CHECK_NE(n.name, 0); CHECK_EQ(0, strcmp("soundtest_music_name_ptrs", tt.Shstr(n.name))); CHECK_EQ(n.data_type.kind, DataTypeKind::kTable); CHECK_EQ(n.data_type.count, 19); REQUIRE_EQ(n.data_type.nested_num, 2); const auto ptr_type = tt.Type(n.data_type.nested_idx); CHECK_EQ(ptr_type.kind, DataTypeKind::kPtr); CHECK_EQ(ptr_type.count, 1); const auto u32_type = tt.Type(n.data_type.nested_idx + 1); CHECK_EQ(u32_type.kind, DataTypeKind::kU32); CHECK_EQ(u32_type.count, 1); CHECK_EQ( n.data_type.count * (ptr_type.count * ptr_type.BaseSize() + u32_type.count * u32_type.BaseSize()), n.size); CHECK_NE(0, n.size); } { const auto n = tt.Node(2); CHECK_EQ(n.kind, TraceNodeKind::kData); CHECK_EQ(n.address, 0x88124); CHECK_NE(n.name, 0); CHECK_EQ(0, strcmp("passwords_ptrs", tt.Shstr(n.name))); CHECK_EQ(n.data_type.kind, DataTypeKind::kTable); CHECK_EQ(n.data_type.count, 0); REQUIRE_EQ(n.data_type.nested_num, 2); const auto ptr_type = tt.Type(n.data_type.nested_idx); CHECK_EQ(ptr_type.kind, DataTypeKind::kPtr); CHECK_EQ(ptr_type.count, 1); const auto u32_type = tt.Type(n.data_type.nested_idx + 1); CHECK_EQ(u32_type.kind, DataTypeKind::kU32); CHECK_EQ(u32_type.count, 1); CHECK_EQ( n.data_type.count * (ptr_type.count * ptr_type.BaseSize() + u32_type.count * u32_type.BaseSize()), 0); CHECK_EQ(0, n.size); } } TEST_CASE("table types with count specified") { const auto data = STRING_SLICE( "0x88 [ptr 4,u16 2, u8 4] 14 something_idk\n"); TraceTable tt{}; const bool ok = ParseTraceData(tt, data.str, data.len, stderr); REQUIRE(ok); REQUIRE_NE(tt.TypesCount(), 0); REQUIRE_EQ(tt.NodesCount(), 1); CHECK_NE(tt.Shstr(), nullptr); { const auto n = tt.Node(0); CHECK_EQ(n.kind, TraceNodeKind::kData); CHECK_EQ(n.address, 0x88); CHECK_NE(n.name, 0); CHECK_EQ(0, strcmp("something_idk", tt.Shstr(n.name))); CHECK_EQ(n.data_type.kind, DataTypeKind::kTable); CHECK_EQ(n.data_type.count, 14); REQUIRE_EQ(n.data_type.nested_num, 3); const auto ptr_type = tt.Type(n.data_type.nested_idx); CHECK_EQ(ptr_type.kind, DataTypeKind::kPtr); CHECK_EQ(ptr_type.count, 4); const auto u16_type = tt.Type(n.data_type.nested_idx + 1); CHECK_EQ(u16_type.kind, DataTypeKind::kU16); CHECK_EQ(u16_type.count, 2); const auto u8_type = tt.Type(n.data_type.nested_idx + 2); CHECK_EQ(u8_type.kind, DataTypeKind::kU8); CHECK_EQ(u8_type.count, 4); CHECK_EQ( n.data_type.count * (ptr_type.count * ptr_type.BaseSize() + u16_type.count * u16_type.BaseSize() + u8_type.count * u8_type.BaseSize()), n.size); CHECK_NE(0, n.size); } } TEST_CASE("multiline trace node with comments") { const auto data = STRING_SLICE( "0x100 \\\n" " [\\# side comments are ignored\n" " ptr \\\n" " 4\\\n" " ,\\\n" " u32\\#another random comment\n" " ] \\\n" " 4\\# and another one\n" " trace_node#proper trailing comment\n"); TraceTable tt{}; const bool ok = ParseTraceData(tt, data.str, data.len, stderr); REQUIRE(ok); REQUIRE_NE(tt.TypesCount(), 0); REQUIRE_EQ(tt.NodesCount(), 1); CHECK_NE(tt.Shstr(), nullptr); { const auto n = tt.Node(0); CHECK_EQ(n.kind, TraceNodeKind::kData); CHECK_EQ(n.address, 0x100); CHECK_NE(n.name, 0); CHECK_EQ(0, strcmp("trace_node", tt.Shstr(n.name))); CHECK_EQ(n.data_type.kind, DataTypeKind::kTable); CHECK_EQ(n.data_type.count, 4); REQUIRE_EQ(n.data_type.nested_num, 2); const auto ptr_type = tt.Type(n.data_type.nested_idx); CHECK_EQ(ptr_type.kind, DataTypeKind::kPtr); CHECK_EQ(ptr_type.count, 4); const auto u32_type = tt.Type(n.data_type.nested_idx + 1); CHECK_EQ(u32_type.kind, DataTypeKind::kU32); CHECK_EQ(u32_type.count, 1); CHECK_EQ( n.data_type.count * (ptr_type.count * ptr_type.BaseSize() + u32_type.count * u32_type.BaseSize()), n.size); CHECK_NE(0, n.size); } } TEST_CASE("multiline trace node with comments") { const auto data = STRING_SLICE( "# Heading comments are appended to the trace node.\r" "# And they may be multiline!\n" "# The newlines in the heading comment are preserved\r\n" "# if there is more than one line.\n" "0x100 str 8 trace_node #side comments are ignored\n" "#Just a stray comment without trace node because empty line follows\n" "\n" "# Single line heading comment won't preserve trailing newline symbol\n" "0x200 strz 4 trace_node2 # side comments are ignored\n" "#Just a comment without trace node"); TraceTable tt{}; const bool ok = ParseTraceData(tt, data.str, data.len, stderr); REQUIRE(ok); REQUIRE_EQ(tt.TypesCount(), 0); REQUIRE_EQ(tt.NodesCount(), 2); CHECK_NE(tt.Shstr(), nullptr); { const auto n = tt.Node(0); CHECK_EQ(n.kind, TraceNodeKind::kData); CHECK_EQ(n.address, 0x100); CHECK_NE(n.name, 0); TRACE("name = %s", tt.Shstr(n.name)); CHECK_EQ(0, strcmp("trace_node", tt.Shstr(n.name))); CHECK_NE(n.comment, 0); TRACE("comment = %s", tt.Shstr(n.comment)); CHECK_EQ(0, strcmp( " Heading comments are appended to the trace node.\n" " And they may be multiline!\n" " The newlines in the heading comment are preserved\n" " if there is more than one line.", tt.Shstr(n.comment))); CHECK_EQ(n.data_type.kind, DataTypeKind::kStr); CHECK_EQ(n.data_type.count, 8); CHECK_EQ(n.data_type.count * n.data_type.BaseSize(), n.size); CHECK_EQ(8, n.size); } { const auto n = tt.Node(1); CHECK_EQ(n.kind, TraceNodeKind::kData); CHECK_EQ(n.address, 0x200); CHECK_NE(n.name, 0); TRACE("n.name = %s", tt.Shstr(n.name)); CHECK_EQ(0, strcmp("trace_node2", tt.Shstr(n.name))); CHECK_NE(n.comment, 0); TRACE("comment = %s", tt.Shstr(n.comment)); CHECK_EQ(0, strcmp( " Single line heading comment won't preserve trailing newline symbol", tt.Shstr(n.comment))); CHECK_EQ(n.data_type.kind, DataTypeKind::kStrz); CHECK_EQ(n.data_type.count, 4); CHECK_EQ(n.data_type.count * n.data_type.BaseSize(), n.size); CHECK_EQ(4, n.size); } }