| // Copyright 2019 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include <lib/debuglog.h> |
| #include <lib/unittest/unittest.h> |
| #include <lib/zircon-internal/macros.h> |
| #include <zircon/types.h> |
| |
| #include <ktl/string_view.h> |
| #include <ktl/unique_ptr.h> |
| |
| #include "debuglog_internal.h" |
| |
| #include <ktl/enforce.h> |
| |
| namespace { |
| struct DLogOutputTest final : public DLog { |
| AutounsignalEvent output; |
| // Buffer to hold last rendered log - size = dlog max data + additional buffer for log header. |
| char output_log_buffer[DLOG_MAX_DATA + 128] TA_GUARDED(output_lock); |
| ktl::string_view last_output_log TA_GUARDED(output_lock); |
| DECLARE_LOCK(DLogOutputTest, Mutex) output_lock; |
| |
| protected: |
| void OutputLogMessage(ktl::string_view log) override { |
| { |
| Guard<Mutex> output_guard(&output_lock); |
| memcpy(output_log_buffer, log.data(), log.length()); |
| last_output_log = {output_log_buffer, log.length()}; |
| } |
| output.Signal(); |
| } |
| }; |
| } // namespace |
| |
| struct DebuglogTests { |
| static bool log_format() { |
| BEGIN_TEST; |
| |
| fbl::AllocChecker ac; |
| ktl::unique_ptr<DLog> log = ktl::make_unique<DLog>(&ac); |
| ASSERT_TRUE(ac.check(), ""); |
| |
| char msg[] = "Hello World"; |
| |
| log->Write(DEBUGLOG_WARNING, 0, {msg, sizeof(msg)}); |
| |
| // log->data_[0] is not necessarily aligned as a dlog_header would be so copy rather than |
| // access them directly. |
| dlog_header header{}; |
| static_assert(sizeof(log->data_) >= sizeof(header)); |
| memcpy(&header, log->data_, sizeof(header)); |
| |
| EXPECT_EQ(DEBUGLOG_WARNING, header.severity); |
| EXPECT_EQ(0, header.flags); |
| EXPECT_EQ(ZX_KOID_INVALID, header.pid); |
| EXPECT_NE(ZX_KOID_INVALID, header.tid); |
| ASSERT_EQ(12, header.datalen); |
| |
| uint8_t* msg_bytes = log->data_ + sizeof(dlog_header); |
| |
| EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(msg), msg_bytes, sizeof(msg)); |
| |
| END_TEST; |
| } |
| |
| static bool log_wrap() { |
| BEGIN_TEST; |
| |
| fbl::AllocChecker ac; |
| ktl::unique_ptr<DLog> log = ktl::make_unique<DLog>(&ac); |
| ASSERT_TRUE(ac.check(), ""); |
| |
| char msg[] = "Hello World"; |
| const size_t kTruncateTarget = |
| kDLogHeaderFifoAlignment + 1; // An unaligned length somewhere in the middle of msg. |
| const size_t pad = DLOG_ALIGN_TRUNC(DLOG_SIZE - sizeof(dlog_header) - kTruncateTarget); |
| |
| // We need to trigger some writes, but we don't care what we write, so we just need a buffer |
| // large enough to be copied from. Doesn't much matter what's in it. |
| char ignored_buffer[DLOG_MAX_DATA]{0}; |
| |
| for (size_t to_write = pad; to_write;) { |
| size_t write = to_write - sizeof(dlog_header); |
| write = write > DLOG_MAX_DATA ? DLOG_MAX_DATA : write; |
| |
| log->Write(DEBUGLOG_WARNING, 0, {ignored_buffer, write}); |
| to_write -= write + sizeof(dlog_header); |
| } |
| |
| EXPECT_EQ(pad, log->head_); |
| |
| log->Write(DEBUGLOG_WARNING, 0, {msg, sizeof(msg)}); |
| |
| // log->data_[pad] is not necessarily aligned as a dlog_header would be so copy rather than |
| // access them directly. |
| dlog_header header{}; |
| static_assert(sizeof(log->data_) - pad >= sizeof(header)); |
| memcpy(&header, log->data_ + pad, sizeof(header)); |
| |
| EXPECT_EQ(DEBUGLOG_WARNING, header.severity); |
| EXPECT_EQ(0, header.flags); |
| EXPECT_EQ(ZX_KOID_INVALID, header.pid); |
| EXPECT_NE(ZX_KOID_INVALID, header.tid); |
| ASSERT_EQ(sizeof(msg), header.datalen); |
| |
| uint8_t* msg_bytes = log->data_ + pad + sizeof(dlog_header); |
| |
| const size_t kTruncatedSize = DLOG_SIZE - pad - sizeof(dlog_header); |
| |
| EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&msg[0]), msg_bytes, kTruncatedSize); |
| EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(&msg[8]), log->data_, sizeof(msg) - kTruncatedSize); |
| |
| END_TEST; |
| } |
| |
| // Read a record from the debuglog and verify its fields. |
| static bool log_reader_read() { |
| BEGIN_TEST; |
| |
| fbl::AllocChecker ac; |
| ktl::unique_ptr<DLog> log = ktl::make_unique<DLog>(&ac); |
| ASSERT_TRUE(ac.check()); |
| |
| const zx_time_t now = current_time(); |
| |
| char msg[] = "Message!"; |
| ASSERT_EQ(ZX_OK, log->Write(DEBUGLOG_WARNING, 0, {msg, sizeof(msg)})); |
| |
| DlogReader reader; |
| reader.Initialize(nullptr, nullptr, log.get()); |
| size_t got; |
| dlog_record_t rec{}; |
| ASSERT_EQ(ZX_OK, reader.Read(0, &rec, &got)); |
| ASSERT_EQ(sizeof(dlog_header) + sizeof(msg), got); |
| EXPECT_EQ(0u, rec.hdr.preamble); |
| // Sequence should start at 0. |
| EXPECT_EQ(0u, rec.hdr.sequence); |
| EXPECT_EQ(sizeof(msg), rec.hdr.datalen); |
| EXPECT_EQ(DEBUGLOG_WARNING, rec.hdr.severity); |
| EXPECT_EQ(0, rec.hdr.flags); |
| EXPECT_GE(rec.hdr.timestamp, now); |
| EXPECT_EQ(0ull, rec.hdr.pid); |
| EXPECT_EQ(Thread::Current::Get()->tid(), rec.hdr.tid); |
| |
| reader.Disconnect(); |
| |
| END_TEST; |
| } |
| |
| // Write to the log, exceeding its capacity and see that data is lost. |
| static bool log_reader_dataloss() { |
| BEGIN_TEST; |
| |
| fbl::AllocChecker ac; |
| ktl::unique_ptr<DLog> log = ktl::make_unique<DLog>(&ac); |
| ASSERT_TRUE(ac.check(), ""); |
| |
| DlogReader reader; |
| reader.Initialize(nullptr, nullptr, log.get()); |
| |
| char msg[] = "Hello World"; |
| |
| uint64_t num_written = 0; |
| |
| log->Write(DEBUGLOG_WARNING, 0, {msg, sizeof(msg)}); |
| num_written++; |
| |
| dlog_record_t rec{}; |
| size_t got; |
| |
| ASSERT_EQ(ZX_OK, reader.Read(0, &rec, &got)); |
| ASSERT_EQ(sizeof(dlog_header) + sizeof(msg), got); |
| |
| EXPECT_EQ(DEBUGLOG_WARNING, rec.hdr.severity); |
| EXPECT_EQ(0u, rec.hdr.preamble); |
| EXPECT_EQ(0, rec.hdr.flags); |
| EXPECT_EQ(ZX_KOID_INVALID, rec.hdr.pid); |
| EXPECT_NE(ZX_KOID_INVALID, rec.hdr.tid); |
| ASSERT_EQ(sizeof(msg), rec.hdr.datalen); |
| |
| EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(msg), reinterpret_cast<uint8_t*>(rec.data), |
| sizeof(msg)); |
| |
| for (size_t i = 0; i < DLOG_SIZE; i += sizeof(dlog_header) + sizeof(msg)) { |
| log->Write(DEBUGLOG_WARNING, 0, {msg, sizeof(msg)}); |
| num_written++; |
| } |
| |
| uint64_t num_read = 0; |
| zx_status_t status; |
| uint64_t expected_sequence = 0; |
| uint64_t dropped = 0; |
| while ((status = reader.Read(0, &rec, &got)) == ZX_OK) { |
| num_read++; |
| ASSERT_EQ(sizeof(dlog_header) + sizeof(msg), got); |
| EXPECT_EQ(DEBUGLOG_WARNING, rec.hdr.severity); |
| EXPECT_EQ(0u, rec.hdr.preamble); |
| EXPECT_EQ(0, rec.hdr.flags); |
| EXPECT_EQ(ZX_KOID_INVALID, rec.hdr.pid); |
| EXPECT_NE(ZX_KOID_INVALID, rec.hdr.tid); |
| ASSERT_EQ(sizeof(msg), rec.hdr.datalen); |
| EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(msg), reinterpret_cast<uint8_t*>(rec.data), |
| sizeof(msg)); |
| dropped += (rec.hdr.sequence - expected_sequence); |
| expected_sequence = rec.hdr.sequence + 1; |
| } |
| EXPECT_EQ(ZX_ERR_SHOULD_WAIT, status); |
| |
| // See that we read fewer records than we wrote. |
| EXPECT_LT(num_read, num_written); |
| |
| EXPECT_EQ(0, rec.hdr.flags); |
| EXPECT_EQ(ZX_KOID_INVALID, rec.hdr.pid); |
| EXPECT_NE(ZX_KOID_INVALID, rec.hdr.tid); |
| ASSERT_EQ(sizeof(msg), rec.hdr.datalen); |
| |
| EXPECT_BYTES_EQ(reinterpret_cast<uint8_t*>(msg), reinterpret_cast<uint8_t*>(rec.data), |
| sizeof(msg)); |
| |
| // See that all messages are acconted for. That is, the number dropped matches the differnce. |
| EXPECT_EQ(dropped, num_written - num_read); |
| |
| reader.Disconnect(); |
| END_TEST; |
| } |
| |
| // Verify that logs written are output correctly by the DumperThread. |
| static bool log_dumper_test() { |
| BEGIN_TEST; |
| |
| fbl::AllocChecker ac; |
| ktl::unique_ptr<DLogOutputTest> log = ktl::make_unique<DLogOutputTest>(&ac); |
| ASSERT_TRUE(ac.check()); |
| |
| // Start the dumper thread. |
| log->StartThreads(); |
| |
| // This is the size of the header. |
| const size_t min_output_size = []() { |
| dlog_header_t empty_hdr; |
| memset(&empty_hdr, 0, sizeof(empty_hdr)); |
| return DLog::FormatHeader({}, empty_hdr); |
| }(); |
| |
| // A small helper which will count the number of occurrences of |msg| in |
| // |str|. |
| auto CountOccurences = [min_output_size](const ktl::string_view& str, |
| const ktl::string_view& msg) -> size_t { |
| size_t count = 0; |
| size_t offset = min_output_size; |
| while ((offset = str.find(msg, offset)) != str.npos) { |
| ++count; |
| ++offset; |
| } |
| return count; |
| }; |
| |
| // Helper function to verify that a message is output correctly. |
| auto write_log_and_check_output = [&log, &all_ok, min_output_size, |
| &CountOccurences](char* msg) -> bool { |
| auto len = strlen(msg); |
| ASSERT_OK(log->Write(DEBUGLOG_INFO, 0, {msg, len})); |
| // Wait for the log to get output by the DumperThread. |
| ASSERT_OK(log->output.Wait()); |
| |
| Guard<Mutex> output_guard(&log->output_lock); |
| // Length of output log is at least header length + message length. |
| EXPECT_GE(log->last_output_log.length(), min_output_size + len); |
| ASSERT_GT(log->last_output_log.length(), 0lu); |
| // Check that the log ends with a newline. |
| EXPECT_EQ('\n', log->last_output_log[log->last_output_log.length() - 1]); |
| // Check that the log contains the message (if not empty) after the header. |
| if (len) { |
| ASSERT_EQ(1u, CountOccurences(log->last_output_log, msg)); |
| } |
| return all_ok; |
| }; |
| |
| // Check that a simple message appears in the log dump. |
| char msg0[] = "Hello World!\n"; |
| EXPECT_TRUE(write_log_and_check_output(msg0)); |
| |
| // Check that a message without newline appears in the log dump and contains newline. |
| char msg1[] = "Hello!"; |
| EXPECT_TRUE(write_log_and_check_output(msg1)); |
| |
| // Check that a message containing only newline in the log dump. |
| char msg2[] = "\n"; |
| EXPECT_TRUE(write_log_and_check_output(msg2)); |
| |
| // Check that an empty log still gets rendered and contains newline. |
| char msg3[] = ""; |
| EXPECT_TRUE(write_log_and_check_output(msg3)); |
| |
| log->Shutdown(ZX_TIME_INFINITE); |
| |
| END_TEST; |
| } |
| |
| static bool render_to_crashlog() { |
| BEGIN_TEST; |
| |
| fbl::AllocChecker ac; |
| ktl::unique_ptr<DLog> log = ktl::make_unique<DLog>(&ac); |
| ASSERT_TRUE(ac.check()); |
| |
| // Define our test message and figure out a expected minimum size for the |
| // message when rendered. While we are not 100% sure how large the header |
| // for a given rendered record will be, we we know that a record with a |
| // timestamp, pid, and tid of zero will have the smallest rendered header |
| // possible. Note that our test message does not end with a newline, but we |
| // expect rendering to add one to each record which does not end with a |
| // newline. |
| const ktl::string_view msg{"Message!"}; |
| const size_t min_rendered_size = [&msg]() { |
| dlog_header_t empty_hdr; |
| memset(&empty_hdr, 0, sizeof(empty_hdr)); |
| return DLog::FormatHeader({}, empty_hdr) + msg.size() + 1; |
| }(); |
| |
| // Allocate two targets for rendering data into, one normal sized, and one |
| // small. Using a dedicated small buffer (instead of simply artificially |
| // limiting the length of a span wrapped around the large buffer) should |
| // help ASAN in detecting any buffer overflows. |
| constexpr size_t kLargeRenderBufferSize = 1024; |
| ktl::unique_ptr<char[]> large_storage{new (&ac) char[kLargeRenderBufferSize]}; |
| ASSERT_TRUE(ac.check()); |
| ASSERT_LE(min_rendered_size, kLargeRenderBufferSize); |
| |
| constexpr size_t kSmallRenderBufferSize = 1; |
| ktl::unique_ptr<char[]> small_storage{new (&ac) char[kSmallRenderBufferSize]}; |
| ASSERT_TRUE(ac.check()); |
| |
| ktl::span<char> large_target{large_storage.get(), kLargeRenderBufferSize}; |
| ktl::span<char> small_target{small_storage.get(), kSmallRenderBufferSize}; |
| |
| // A small helper which will count the number of occurrences of |msg| in |
| // |str|. |
| auto CountOccurences = [](const ktl::string_view& str, const ktl::string_view& msg) -> size_t { |
| size_t count = 0; |
| size_t offset = 0; |
| while ((offset = str.find(msg, offset)) != str.npos) { |
| ++count; |
| ++offset; |
| } |
| return count; |
| }; |
| |
| // A small helper which renders a debuglog into a span provided, and gives |
| // back a string view of the result. |
| auto DoRender = [](const DLog& log, ktl::span<char> target) -> ktl::string_view { |
| return ktl::string_view{target.data(), log.RenderToCrashlog(target)}; |
| }; |
| |
| // Rendering from an empty log should produce nothing. |
| ktl::string_view rendered; |
| rendered = DoRender(*log, large_target); |
| ASSERT_EQ(0u, rendered.size()); |
| |
| // Add record to the log, then render the log and verify that it contains at |
| // least a minimum number of expected bytes, and exactly one occurrence of |
| // the test string in the buffer. |
| ASSERT_EQ(ZX_OK, log->Write(DEBUGLOG_WARNING, 0, msg)); |
| rendered = DoRender(*log, large_target); |
| ASSERT_GE(rendered.size(), min_rendered_size); |
| ASSERT_EQ(1u, CountOccurences(rendered, msg)); |
| |
| // Attempting to render into an empty target from a log with valid records |
| // in should produce no data. |
| rendered = DoRender(*log, {}); |
| ASSERT_EQ(0u, rendered.size()); |
| |
| // Attempting to render into target with is non-empty, but does not have |
| // enough space to hold a rendered record, should also produce no data. |
| rendered = DoRender(*log, small_target); |
| ASSERT_EQ(0u, rendered.size()); |
| |
| // Add two more instances of the test message and re-validate |
| ASSERT_EQ(ZX_OK, log->Write(DEBUGLOG_WARNING, 0, msg)); |
| ASSERT_EQ(ZX_OK, log->Write(DEBUGLOG_WARNING, 0, msg)); |
| rendered = DoRender(*log, large_target); |
| ASSERT_GE(rendered.size(), min_rendered_size * 3); |
| ASSERT_EQ(3u, CountOccurences(rendered, msg)); |
| |
| // Figure out how many instances of our message _should_ fit in the dlog |
| // buffer, then add that many, forcing the log to wrap in the process. Note |
| // that records are always padded out to a 4 bytes boundary. |
| const size_t kRecordSize = (msg.size() + sizeof(dlog_header_t) + 0x3) & ~0x3; |
| const size_t kRecordCount = DLOG_SIZE / kRecordSize; |
| for (size_t i = 0; i < kRecordCount; ++i) { |
| ASSERT_EQ(ZX_OK, log->Write(DEBUGLOG_WARNING, 0, msg)); |
| } |
| |
| // It is not clear exactly how many records should fit into our render |
| // buffer at this point, but the number should be more than 3. |
| rendered = DoRender(*log, large_target); |
| ASSERT_GT(rendered.size(), min_rendered_size * 3); |
| ASSERT_GT(CountOccurences(rendered, msg), 3u); |
| |
| END_TEST; |
| } |
| |
| // This is a regression test introduced after discovering https://fxbug.dev/42071359. It |
| // attempt to make sure that the internal methods ReadRecord and |
| // ReassembleFromOffset return proper results, even when headers and payload |
| // span the break in the ring buffer. |
| static bool read_record() { |
| BEGIN_TEST; |
| |
| fbl::AllocChecker ac; |
| ktl::unique_ptr<DLog> log = ktl::make_unique<DLog>(&ac); |
| ASSERT_TRUE(ac.check()); |
| |
| const char kPayloadData[] = { |
| 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, |
| 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, |
| 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, |
| }; |
| const ktl::string_view kPayload{kPayloadData, ktl::size(kPayloadData)}; |
| const size_t kPayloadSize = sizeof(kPayloadData); |
| const size_t kTotalRecordSize = sizeof(dlog_header_t) + kPayloadSize; |
| |
| constexpr uint8_t kTestSeverity = 0xba; |
| constexpr uint8_t kTestFlags = 0xad; |
| const uint64_t our_tid = Thread::Current::Get()->tid(); |
| |
| static_assert( |
| (kPayloadSize % 4) == 0, |
| "Payload data must be a multiple of 4 bytes in order to be able to align its end exactly " |
| "with the end of the ring buffer"); |
| |
| constexpr ktl::array kOffsets = { |
| static_cast<size_t>(0), // Start of the buffer |
| DLOG_SIZE - kTotalRecordSize, // Aligned with end |
| DLOG_SIZE - kTotalRecordSize + (kPayloadSize / 2), // Splitting the payload |
| DLOG_SIZE - (sizeof(dlog_header_t) / 2), // Splitting the header |
| }; |
| |
| for (const size_t offset : kOffsets) { |
| Guard<MonitoredSpinLock, IrqSave> guard{&log->lock_, SOURCE_TAG}; |
| |
| // Set up the logs ring buffer so that it looks like it is empty, but |
| // ready to write the next record starting at the test vector offset. |
| // Also, zero out our entire ring buffer just so we are starting from a |
| // known state with each vector. |
| ASSERT_TRUE((offset % 4) == 0); // Records must start on 4 byte boundaries. |
| ASSERT_LT(offset, DLOG_SIZE); |
| log->head_ = log->tail_ = offset; |
| memset(log->data_, 0, sizeof(log->data_)); |
| |
| // Go ahead and write our record, then confirm that head and tail end up |
| // where we expect them to. Note that these pointers are always absolute |
| // pointers, to compute offsets from them, one needs to & with the |
| // DLOG_MASK. |
| zx_status_t status = ZX_ERR_INTERNAL; |
| |
| const zx_time_t before_ts = current_time(); |
| guard.CallUnlocked([&log, &status, kPayload]() { |
| status = log->Write(kTestSeverity, kTestFlags, kPayload); |
| }); |
| const zx_time_t after_ts = current_time(); |
| |
| ASSERT_OK(status); |
| EXPECT_EQ(offset, log->tail_); |
| EXPECT_EQ(offset + kTotalRecordSize, log->head_); |
| |
| // Reassemble the header from the offset and make sure it looks right. |
| dlog_header_t hdr; |
| memset(&hdr, 0, sizeof(hdr)); |
| log->ReassembleFromOffset(offset, {reinterpret_cast<uint8_t*>(&hdr), sizeof(hdr)}); |
| |
| EXPECT_EQ(DLOG_HDR_GET_FIFOLEN(hdr.preamble), kTotalRecordSize); |
| EXPECT_EQ(DLOG_HDR_GET_READLEN(hdr.preamble), kTotalRecordSize); |
| EXPECT_EQ(kPayloadSize, hdr.datalen); |
| EXPECT_GE(hdr.timestamp, before_ts); |
| EXPECT_LE(hdr.timestamp, after_ts); |
| EXPECT_EQ(kTestSeverity, hdr.severity); |
| EXPECT_EQ(kTestFlags, hdr.flags); |
| EXPECT_EQ(0u, hdr.pid); // Kernel PID is always 0 |
| EXPECT_EQ(our_tid, hdr.tid); |
| EXPECT_EQ(log->sequence_count_ - 1u, hdr.sequence); |
| |
| // Now attempt to read the record info at the same offset. We should get |
| // the same header base, and regions which exist completely within the |
| // ring buffer, and have the test vector pattern in them. |
| const zx::result<DLog::Record> maybe_record = log->ReadRecord(offset); |
| ASSERT_TRUE(maybe_record.is_ok()); |
| const DLog::Record& record = maybe_record.value(); |
| const uintptr_t log_data = reinterpret_cast<uintptr_t>(log->data_); |
| |
| EXPECT_BYTES_EQ(reinterpret_cast<const uint8_t*>(&hdr), |
| reinterpret_cast<const uint8_t*>(&record.hdr), sizeof(hdr)); |
| EXPECT_GT(record.region1.size(), 0u); |
| EXPECT_LE(record.region1.size(), DLOG_MAX_RECORD); |
| ASSERT_GE(reinterpret_cast<uintptr_t>(record.region1.data()), log_data); |
| ASSERT_LE(reinterpret_cast<uintptr_t>(record.region1.data()) + record.region1.size(), |
| log_data + DLOG_SIZE); |
| |
| if (record.region2.size() > 0u) { |
| EXPECT_LE(record.region2.size(), DLOG_MAX_RECORD); |
| ASSERT_GE(reinterpret_cast<uintptr_t>(record.region2.data()), log_data); |
| ASSERT_LE(reinterpret_cast<uintptr_t>(record.region2.data()) + record.region2.size(), |
| log_data + DLOG_SIZE); |
| } |
| |
| ASSERT_EQ(kPayloadSize, record.region1.size() + record.region2.size()); |
| EXPECT_BYTES_EQ(reinterpret_cast<const uint8_t*>(kPayloadData), |
| reinterpret_cast<const uint8_t*>(record.region1.data()), |
| record.region1.size()); |
| EXPECT_BYTES_EQ(reinterpret_cast<const uint8_t*>(kPayloadData) + record.region1.size(), |
| reinterpret_cast<const uint8_t*>(record.region2.data()), |
| record.region2.size()); |
| EXPECT_FALSE(record.ends_with_newline); |
| } |
| |
| END_TEST; |
| } |
| |
| // Test that write fails with an error once the |DLog| has been shutdown. |
| static bool shutdown() { |
| BEGIN_TEST; |
| |
| fbl::AllocChecker ac; |
| ktl::unique_ptr<DLog> log = ktl::make_unique<DLog>(&ac); |
| ASSERT_TRUE(ac.check()); |
| |
| // Write one message and see that it succeeds. |
| char msg[] = "Message!"; |
| ASSERT_EQ(ZX_OK, log->Write(DEBUGLOG_WARNING, 0, {msg, sizeof(msg)})); |
| |
| // Now ask the DLog to shutdown, write another, see that it fails. |
| log->Shutdown(0); |
| ASSERT_EQ(ZX_ERR_BAD_STATE, log->Write(DEBUGLOG_WARNING, 0, msg)); |
| |
| // See that there is only one message in the DLog. |
| DlogReader reader; |
| reader.Initialize(nullptr, nullptr, log.get()); |
| size_t got; |
| dlog_record_t rec{}; |
| ASSERT_EQ(ZX_OK, reader.Read(0, &rec, &got)); |
| ASSERT_EQ(sizeof(dlog_header) + sizeof(msg), got); |
| ASSERT_EQ(ZX_ERR_SHOULD_WAIT, reader.Read(0, &rec, &got)); |
| reader.Disconnect(); |
| |
| END_TEST; |
| } |
| }; |
| |
| #define DEBUGLOG_UNITTEST(fname) UNITTEST(#fname, fname) |
| |
| UNITTEST_START_TESTCASE(debuglog_tests) |
| DEBUGLOG_UNITTEST(DebuglogTests::log_format) |
| DEBUGLOG_UNITTEST(DebuglogTests::log_wrap) |
| DEBUGLOG_UNITTEST(DebuglogTests::log_reader_read) |
| DEBUGLOG_UNITTEST(DebuglogTests::log_reader_dataloss) |
| DEBUGLOG_UNITTEST(DebuglogTests::log_dumper_test) |
| DEBUGLOG_UNITTEST(DebuglogTests::render_to_crashlog) |
| DEBUGLOG_UNITTEST(DebuglogTests::read_record) |
| DEBUGLOG_UNITTEST(DebuglogTests::shutdown) |
| UNITTEST_END_TESTCASE(debuglog_tests, "debuglog_tests", "Debuglog test") |