blob: 1f4501fd90165541ea619806dbb64e2adb1523b1 [file] [edit]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#define PW_TRACE_MODULE_NAME "TST"
#include "pw_trace_tokenized/transfer_handler.h"
#include "pw_bytes/span.h"
#include "pw_result/result.h"
#include "pw_string/string_builder.h"
#include "pw_tokenizer/detokenize.h"
#include "pw_trace/trace.h"
#include "pw_trace_tokenized/decoder.h"
#include "pw_trace_tokenized/trace_buffer.h"
#include "pw_unit_test/framework.h"
namespace {
using pw::tokenizer::Detokenizer;
using pw::trace::DecodedEvent;
using pw::trace::EventType;
using pw::trace::TokenizedDecoder;
using pw::trace::TraceBufferReader;
using pw::trace::TraceTransferHandler;
#define PW_TRACE_TEST_ADD_TO_DECODER(label, builder) \
do { \
uint32_t _token = PW_TRACE_REF(PW_TRACE_EVENT_TYPE_INSTANT, \
PW_TRACE_MODULE_NAME, \
label, \
PW_TRACE_FLAGS_DEFAULT, \
""); \
builder.Add(_token, "PW_TRACE_EVENT_TYPE_INSTANT", label); \
} while (0)
// Helper class that can build a token database as a CSV string, and return a
// decoder that uses it.
class TokenizedDecoderBuilder {
public:
static constexpr uint64_t kTicksPerSec = 1000; // 1 kHz
// Appends a CSV entry to the internal buffer.
void Add(uint32_t token, const char* event_type, const char* label) {
pw::StringBuffer<64> sbuf;
sbuf << event_type << "|0|" << PW_TRACE_MODULE_NAME << "||" << label;
database_["trace"][token].emplace_back(
sbuf.c_str(), pw::tokenizer::TokenDatabase::kDateRemovedNever);
}
// Constructs the detokenizer and uses it to construct a decoder.
//
// The returned decoder contains a reference to this object's detokenizer. As
// a result, this object must outlive the returned decoder.
TokenizedDecoder Finalize() {
PW_ASSERT(!detokenizer_.has_value());
detokenizer_ = Detokenizer(std::move(database_));
return TokenizedDecoder(detokenizer_.value(), kTicksPerSec);
}
private:
pw::tokenizer::DomainTokenEntriesMap database_;
std::optional<Detokenizer> detokenizer_;
};
// Unit tests.
TEST(TraceTransferHandler, ReadOnly) {
TraceBufferReader reader;
TraceTransferHandler handler(123, reader);
EXPECT_EQ(handler.PrepareRead(), pw::OkStatus());
EXPECT_EQ(handler.PrepareWrite(), pw::Status::PermissionDenied());
}
TEST(TraceBufferReader, ReadEmpty) {
PW_TRACE_SET_ENABLED(true);
pw::trace::ClearBuffer();
TraceBufferReader reader;
std::byte buffer[128];
pw::Result<pw::ByteSpan> result = reader.Read(buffer);
EXPECT_EQ(result.status(), pw::Status::OutOfRange());
}
TEST(TraceBufferReader, ReadData) {
TokenizedDecoderBuilder decoder_builder;
PW_TRACE_TEST_ADD_TO_DECODER("Test", decoder_builder);
TokenizedDecoder decoder = decoder_builder.Finalize();
PW_TRACE_SET_ENABLED(true);
pw::trace::ClearBuffer();
PW_TRACE_INSTANT("Test");
// Check that contents match.
TraceBufferReader reader;
pw::Result<DecodedEvent> event = decoder.ReadSizePrefixed(reader);
ASSERT_EQ(event.status(), pw::OkStatus());
EXPECT_EQ(event->type, EventType::PW_TRACE_EVENT_TYPE_INSTANT);
EXPECT_STREQ(event->flags_str.c_str(), "0");
EXPECT_STREQ(event->module.c_str(), PW_TRACE_MODULE_NAME);
EXPECT_STREQ(event->group.c_str(), "");
EXPECT_STREQ(event->label.c_str(), "Test");
// Second read should be OutOfRange as the buffer is now empty.
std::byte buffer[128];
pw::Result<pw::ByteSpan> encoded = reader.Read(buffer);
EXPECT_EQ(encoded.status(), pw::Status::OutOfRange());
}
TEST(TraceBufferReader, ReadPartial) {
TokenizedDecoderBuilder decoder_builder;
PW_TRACE_TEST_ADD_TO_DECODER("Test", decoder_builder);
PW_TRACE_TEST_ADD_TO_DECODER("Test2", decoder_builder);
TokenizedDecoder decoder = decoder_builder.Finalize();
pw::trace::ClearBuffer();
PW_TRACE_SET_ENABLED(true);
PW_TRACE_INSTANT("Test");
PW_TRACE_INSTANT("Test2");
// Read a single byte first. Reading should succeed if any data is available.
TraceBufferReader reader;
std::byte buffer[128];
pw::ByteSpan bytes(buffer);
pw::Result<pw::ByteSpan> result = reader.Read(bytes.subspan(0, 1));
ASSERT_EQ(result.status(), pw::OkStatus());
size_t total_size = result->size();
// Read the rest.
result = reader.Read(bytes.subspan(1));
ASSERT_EQ(result.status(), pw::OkStatus());
total_size += result->size();
bytes = bytes.subspan(0, total_size);
// Third read should be OutOfRange.
result = reader.Read(bytes);
EXPECT_EQ(result.status(), pw::Status::OutOfRange());
// Check that contents match. We don't have an easy way to know how many bytes
// correspond to each object, so try incrementally. In practice, callers will
// use `ReadSizePrefixed` as in the previous unit test.
pw::Result<DecodedEvent> event = pw::Status::OutOfRange();
for (size_t i = 1; i < bytes.size(); ++i) {
size_t offset = pw::varint::EncodedSize(i);
if (i <= offset) {
continue;
}
event = decoder.Decode(bytes.subspan(offset, i - offset));
if (event.ok()) {
bytes = bytes.subspan(i);
break;
}
}
ASSERT_EQ(event.status(), pw::OkStatus());
EXPECT_EQ(event->type, EventType::PW_TRACE_EVENT_TYPE_INSTANT);
EXPECT_STREQ(event->flags_str.c_str(), "0");
EXPECT_STREQ(event->module.c_str(), PW_TRACE_MODULE_NAME);
EXPECT_STREQ(event->group.c_str(), "");
EXPECT_STREQ(event->label.c_str(), "Test");
size_t offset = pw::varint::EncodedSize(bytes.size());
event = decoder.Decode(bytes.subspan(offset));
ASSERT_EQ(event.status(), pw::OkStatus());
EXPECT_EQ(event->type, EventType::PW_TRACE_EVENT_TYPE_INSTANT);
EXPECT_STREQ(event->flags_str.c_str(), "0");
EXPECT_STREQ(event->module.c_str(), PW_TRACE_MODULE_NAME);
EXPECT_STREQ(event->group.c_str(), "");
EXPECT_STREQ(event->label.c_str(), "Test2");
}
#undef PW_TRACE_TEST_ADD_TO_DECODER
#undef _PW_TRACE_TEST_ADD_TO_DECODER
} // namespace