#include "test.hh" #include "mocks/mock-reporter.hh" #include "../../src/merge-file-parser.cc" #include #include #include class MockParser : public IFileParser { public: MAKE_MOCK2(addFile, bool(const std::string &filename, struct phdr_data_entry *phdr_data)); MAKE_MOCK1(registerLineListener, void(ILineListener &listener)); MAKE_MOCK1(registerFileListener, void(IFileListener &listener)); MAKE_MOCK0(parse, bool()); MAKE_MOCK0(getChecksum, uint64_t()); MAKE_MOCK0(getParserType, std::string()); MAKE_MOCK1(setupParser, void(IFilter *)); MAKE_MOCK0(maxPossibleHits, enum IFileParser::PossibleHits()); MAKE_MOCK3(matchParser, unsigned int(const std::string &filename, uint8_t *mock_data, size_t dataSize)); }; static bool mocked_file_exists(const std::string &path) { return true; } static std::vector mock_data; static void *mocked_read_file(size_t *out_size, const char *path) { *out_size = mock_data.size(); void *p = xmalloc(*out_size); memcpy(p, mock_data.data(), *out_size); return p; } static std::unordered_map path_to_data; static int mocked_write_file(const void *data, size_t size, const char *path) { path_to_data[path] = std::string((const char *)data); return 0; } static uint64_t mocked_ts; static uint64_t mocked_get_timestamp(const std::string &filename) { return mocked_ts; } class LineListenerFixture : public IFileParser::ILineListener { public: virtual ~LineListenerFixture() { } void onLine(const std::string &file, unsigned int lineNr, unsigned long addr) { m_lineToAddr[lineNr] = addr; } protected: std::unordered_map m_lineToAddr; }; class AddressListenerFixture : public IReporter::IListener, public ICollector::IListener { public: virtual ~AddressListenerFixture() { } void onAddress(uint64_t addr, unsigned long hits) { m_addrToHits[addr] = hits; } void onAddressHit(uint64_t addr, unsigned long hits) { m_breakpointToHits[addr] = hits; } protected: std::unordered_map m_addrToHits; std::unordered_map m_breakpointToHits; }; TESTSUITE(merge_parser) { TEST(marshal) { MockParser mockParser; MockReporter reporter; auto &filter = IFilter::create(); REQUIRE_CALL(mockParser, registerFileListener(_)) .TIMES(2) ; REQUIRE_CALL(mockParser, registerLineListener(_)) .TIMES(2) ; mock_data = {'a', '\n', 'b', '\n', '\0'}; mocked_ts = 1; MergeParser parser(mockParser, reporter, "/tmp", "/tmp/kalle", filter); mock_file_exists(mocked_file_exists); mock_read_file(mocked_read_file); mock_get_file_timestamp(mocked_get_timestamp); parser.onLine("a", 1, 2); parser.onLine("a", 1, 3); // Two addresses on the same line parser.onLine("a", 2, 3); parser.onLine("c", 2, 5); const struct file_data *p; // Does not exist p = parser.marshalFile("b"); ASSERT_TRUE(!p); // But this one does p = parser.marshalFile("a"); ASSERT_TRUE(p); ASSERT_TRUE(be_to_host(p->magic) == MERGE_MAGIC); ASSERT_TRUE(be_to_host(p->version) == MERGE_VERSION); ASSERT_TRUE(be_to_host(p->n_entries) == 2); char *name = ((char *)p) + be_to_host(p->file_name_offset); ASSERT_TRUE(std::string(name) == "a"); ASSERT_TRUE(name + strlen(name) + 1 - (char *)p == be_to_host(p->size)); ASSERT_TRUE(be_to_host(p->entries[0].line) == 1); ASSERT_TRUE(be_to_host(p->entries[0].n_addresses) == 2); uint64_t *addressTable = (uint64_t *)((char *)p + be_to_host(p->address_table_offset)); uint32_t start = be_to_host(p->entries[0].address_start); ASSERT_TRUE(be_to_host(addressTable[start]) == parser.hashAddress("a", 1, 2)); ASSERT_TRUE(be_to_host(addressTable[start + 1]) == parser.hashAddress("a", 1, 3) + 1); // No output MergeParser parser2(mockParser, reporter, "/tmp", "/tmp/kalle", filter); parser2.onLine("c", 4, 1); // New timestamp for the "a" file mocked_ts = 2; parser2.onLine("a", 1, 2); const struct file_data *p2; // Test that the checksum changes on new TS p2 = parser2.marshalFile("a"); ASSERT_TRUE(p2); ASSERT_TRUE(p->checksum == p2->checksum); ASSERT_TRUE(p->timestamp != p2->timestamp); // ... but is the same with the old TS p = parser.marshalFile("c"); p2 = parser2.marshalFile("c"); ASSERT_TRUE(p); ASSERT_TRUE(p2); // Should be the same ASSERT_TRUE(p->checksum == p2->checksum); ASSERT_TRUE(p->timestamp == p2->timestamp); // Same timestamp, different data parser.onLine("d", 9, 1); mock_data = {'a', '\n', 'b', 'c', '\n', '\0'}; parser2.onLine("d", 9, 1); p = parser.marshalFile("d"); p2 = parser2.marshalFile("d"); ASSERT_TRUE(p); ASSERT_TRUE(p2); ASSERT_TRUE(p->timestamp == p2->timestamp); ASSERT_TRUE(p->checksum != p2->checksum); } TEST(output) { auto &filter = IFilter::create(); MockParser mockParser; MockReporter reporter; REQUIRE_CALL(mockParser, registerFileListener(_)) .TIMES(1) ; REQUIRE_CALL(mockParser, registerLineListener(_)) .TIMES(1) ; mock_data = {'a', '\n', 'b', '\n', '\0'}; mocked_ts = 1; MergeParser parser(mockParser, reporter, "/tmp", "/tmp/kalle", filter); mock_file_exists(mocked_file_exists); mock_read_file(mocked_read_file); mock_write_file(mocked_write_file); mock_get_file_timestamp(mocked_get_timestamp); parser.onLine("a", 1, 2); parser.onLine("b", 2, 3); parser.onStop(); std::string p0 = parser.m_outputDirectory + "/metadata/" + fmt("%08x", hash_block("a", 1)); std::string p1 = parser.m_outputDirectory + "/metadata/" + fmt("%08x", hash_block("b", 1)); ASSERT_TRUE(path_to_data.find(p0) != path_to_data.end()); ASSERT_TRUE(path_to_data.find(p1) != path_to_data.end()); } TEST(input, LineListenerFixture, AddressListenerFixture) { auto &filter = IFilter::create(); MockParser mockParser; MockReporter reporter; REQUIRE_CALL(mockParser, registerFileListener(_)) .TIMES(2) ; REQUIRE_CALL(mockParser, registerLineListener(_)) .TIMES(2) ; mock_data = {'a', '\n', 'b', '\n', '\0'}; mocked_ts = 1; MergeParser parser(mockParser, reporter, "/tmp", "/tmp/kalle", filter); mock_file_exists(mocked_file_exists); mock_read_file(mocked_read_file); mock_write_file(mocked_write_file); mock_get_file_timestamp(mocked_get_timestamp); // Register the collector address listener parser.registerListener(*this); parser.onLine("a", 1, 2); parser.onLine("a", 3, 9); parser.onLine("a", 4, 72); parser.onLine("b", 2, 3); ASSERT_TRUE(m_breakpointToHits[parser.hashAddress("b", 2, 3)] == 0); parser.onAddress(3, 2); ASSERT_TRUE(m_breakpointToHits[parser.hashAddress("b", 2, 3)] == 2); // Register afterwards to get everything on parse below... parser.registerLineListener(*this); // Non-hash file parser.parseOne("/tmp/kalle/df/", "some-other-file"); // Non-existing parser.parseOne("/tmp/kalle/df/", "12345678"); // Existing (but fake, anyway) parser.parseOne("/tmp/kalle/df/", fmt("0x%08x", parser.m_files["a"]->m_checksum)); const struct file_data *p; p = parser.marshalFile("a"); ASSERT_TRUE(p); MergeParser parser2(mockParser, reporter, "/tmp", "/tmp/kalle", filter); parser2.registerLineListener(*this); parser2.registerListener(*this); // Valid file and data ASSERT_TRUE(m_lineToAddr.size() == 0); ASSERT_TRUE(m_breakpointToHits[parser.hashAddress("a", 4, 72)] == 0); // See to it that we "know of" file "a" parser2.onLine("a", 4, 72); // Mark all entries as executed in the table (should yield hits below) uint64_t *table = (uint64_t*)((const char *)p + be_to_host(p->address_table_offset)); for (unsigned int i = 0; i < 4; i++) table[i] = to_be((be_to_host(table[i]) | (1ULL << 63))); mock_data.clear(); uint8_t *b = (uint8_t *)p; for (unsigned int i = 0; i < be_to_host(p->size); i++) mock_data.push_back(b[i]); parser2.parseOne("/tmp/kalle/df/", fmt("0x%08x", parser.m_files["a"]->m_checksum)); ASSERT_TRUE(m_lineToAddr.size() == 3); ASSERT_TRUE(m_lineToAddr[1] == parser.hashAddress("a", 1, 2)); ASSERT_TRUE(m_breakpointToHits[parser.hashAddress("a", 4, 72)] == 1); free((void *)p); } }