blob: 173af8983294fe56c998210539d3b3193a7efceb [file] [log] [blame]
// 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 <zircon/types.h>
#include <ktl/unique_ptr.h>
#include "debuglog_internal.h"
namespace {
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)});
dlog_header* header = reinterpret_cast<dlog_header*>(log->data);
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[0]), msg_bytes, sizeof(msg));
END_TEST;
}
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 = 5; // An unaligned length somewhere in the middle of msg.
const size_t pad = ALIGN4_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 dummy[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, {dummy, write});
to_write -= write + sizeof(dlog_header);
}
EXPECT_EQ(pad, log->head);
log->write(DEBUGLOG_WARNING, 0, {msg, sizeof(msg)});
dlog_header* header = reinterpret_cast<dlog_header*>(log->data + pad);
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.
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.InitializeForTest(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.
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.InitializeForTest(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;
}
// Test that write fails with an error once the |DLog| has been shutdown.
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();
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.InitializeForTest(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;
}
} // namespace
#define DEBUGLOG_UNITTEST(fname) UNITTEST(#fname, fname)
UNITTEST_START_TESTCASE(debuglog_tests)
DEBUGLOG_UNITTEST(log_format)
DEBUGLOG_UNITTEST(log_wrap)
DEBUGLOG_UNITTEST(log_reader_read)
DEBUGLOG_UNITTEST(log_reader_dataloss)
DEBUGLOG_UNITTEST(shutdown)
UNITTEST_END_TESTCASE(debuglog_tests, "debuglog_tests", "Debuglog test")