| Index: components/tracing/core/trace_buffer_reader_unittest.cc
|
| diff --git a/components/tracing/core/trace_buffer_reader_unittest.cc b/components/tracing/core/trace_buffer_reader_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..8a4210b99104ac5185ed142412a20d1919be0a78
|
| --- /dev/null
|
| +++ b/components/tracing/core/trace_buffer_reader_unittest.cc
|
| @@ -0,0 +1,227 @@
|
| +// Copyright 2016 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "components/tracing/core/trace_buffer_reader.h"
|
| +
|
| +#include "base/macros.h"
|
| +#include "base/memory/ptr_util.h"
|
| +#include "components/tracing/core/string_interning.h"
|
| +#include "components/tracing/test/golden_protos/event.pb.h"
|
| +#include "components/tracing/test/golden_protos/events_chunk.pb.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +namespace tracing {
|
| +namespace v2 {
|
| +namespace {
|
| +
|
| +namespace golden_protos = ::tracing::proto;
|
| +
|
| +::testing::AssertionResult ValueEquals(const char* m_expr,
|
| + const char* n_expr,
|
| + const base::Value& m,
|
| + const base::Value& n) {
|
| + if (m.Equals(&n))
|
| + return ::testing::AssertionSuccess();
|
| +
|
| + return ::testing::AssertionFailure() << "\nExpected:\n"
|
| + << m << "\nActual:\n"
|
| + << n;
|
| +}
|
| +
|
| +void ParseChunk(TraceBufferReader* reader, const std::string& chunk) {
|
| + reader->ParseChunk(reinterpret_cast<const uint8_t*>(chunk.data()),
|
| + chunk.size());
|
| +}
|
| +
|
| +// Parse a zero length chunk and a chunk with no events. In both cases parsing
|
| +// should succeed but no event should be decoded.
|
| +TEST(TraceBufferReaderTest, EmptyChunk) {
|
| + golden_protos::EventsChunk chunk;
|
| + chunk.set_writer_id(1);
|
| + chunk.set_seq_id(0);
|
| +
|
| + TraceBufferReader reader(TraceBufferReader::kBuildLegacyJSONEvents);
|
| + std::string zero_len_chunk;
|
| + ParseChunk(&reader, zero_len_chunk);
|
| + ParseChunk(&reader, chunk.SerializeAsString());
|
| + ASSERT_EQ(0u, reader.legacy_events()->GetSize());
|
| +}
|
| +
|
| +TEST(TraceBufferReaderTest, OneChunkOneEvent) {
|
| + golden_protos::Event evt;
|
| + evt.set_type(golden_protos::Event::COMPLETE);
|
| + evt.set_name_str("copied_str_name");
|
| + auto interned_cat_name = InternString("interned_category_name");
|
| + evt.set_category_id(interned_cat_name);
|
| + evt.set_timestamp(42);
|
| + evt.set_thread_timestamp(0xf1f2f3f4);
|
| +
|
| + golden_protos::EventsChunk chunk;
|
| + chunk.set_writer_id(1);
|
| + chunk.set_seq_id(0);
|
| + chunk.add_events(evt.SerializeAsString());
|
| +
|
| + TraceBufferReader reader(TraceBufferReader::kBuildLegacyJSONEvents |
|
| + TraceBufferReader::kBuildInternedStringsTable);
|
| + ParseChunk(&reader, chunk.SerializeAsString());
|
| + ASSERT_EQ(1u, reader.legacy_events()->GetSize());
|
| + base::DictionaryValue* decoded_evt = nullptr;
|
| + ASSERT_TRUE(reader.legacy_events()->GetDictionary(0, &decoded_evt));
|
| +
|
| + base::DictionaryValue expected_value;
|
| + expected_value.SetString("ph", "X");
|
| + expected_value.SetString("name", "copied_str_name");
|
| + expected_value.SetString("cat", "interned_category_name");
|
| + expected_value.SetInteger("ts", 42);
|
| + expected_value.SetInteger("tts", static_cast<int>(0xf1f2f3f4));
|
| + EXPECT_PRED_FORMAT2(ValueEquals, expected_value, *decoded_evt);
|
| +
|
| + ASSERT_EQ(1u, reader.interned_strings().count(interned_cat_name));
|
| + EXPECT_EQ("interned_category_name",
|
| + reader.interned_strings().at(interned_cat_name));
|
| +}
|
| +
|
| +TEST(TraceBufferReaderTest, OneChunkManyEvents) {
|
| + golden_protos::EventsChunk chunk;
|
| + chunk.set_writer_id(1);
|
| + chunk.set_seq_id(42);
|
| + const char* kCatNames[] = {"cat1", "cat2", "cat3", "cat4"};
|
| + const size_t kNumEvents = 100;
|
| +
|
| + for (size_t i = 0; i < kNumEvents; ++i) {
|
| + golden_protos::Event evt;
|
| + evt.set_type(golden_protos::Event::COMPLETE);
|
| + evt.set_name_str("copied_str_name");
|
| + evt.set_category_id(InternString(kCatNames[i % arraysize(kCatNames)]));
|
| + evt.set_timestamp(0xff000000 + i);
|
| + chunk.add_events(evt.SerializeAsString());
|
| + }
|
| +
|
| + TraceBufferReader reader(TraceBufferReader::kBuildLegacyJSONEvents |
|
| + TraceBufferReader::kBuildInternedStringsTable);
|
| + ParseChunk(&reader, chunk.SerializeAsString());
|
| + ASSERT_EQ(kNumEvents, reader.legacy_events()->GetSize());
|
| + for (size_t i = 0; i < kNumEvents; ++i) {
|
| + base::DictionaryValue* decoded_evt = nullptr;
|
| + ASSERT_TRUE(reader.legacy_events()->GetDictionary(i, &decoded_evt));
|
| + base::DictionaryValue expected_value;
|
| + expected_value.SetString("ph", "X");
|
| + expected_value.SetString("name", "copied_str_name");
|
| + expected_value.SetString("cat", kCatNames[i % arraysize(kCatNames)]);
|
| + expected_value.SetInteger("ts", static_cast<int>(0xff000000 + i));
|
| + ASSERT_PRED_FORMAT2(ValueEquals, expected_value, *decoded_evt);
|
| + }
|
| +}
|
| +
|
| +// Test the decoding events that are fragmented across multiple chunks.
|
| +TEST(TraceBufferReaderTest, FragmentedEvents) {
|
| + golden_protos::Event evt[3];
|
| + for (size_t i = 0; i < arraysize(evt); ++i) {
|
| + evt[i].set_type(golden_protos::Event::COMPLETE);
|
| + evt[i].set_name_str("copied_str_name");
|
| + evt[i].set_category_id(InternString("interned_category_name"));
|
| + evt[i].set_timestamp(i);
|
| + }
|
| +
|
| + // The 2nd event (evt[1]) will be split into three fragments and distributed
|
| + // over all three chunks.
|
| + const size_t evt1_size = evt[1].ByteSize();
|
| + DCHECK_GT(evt1_size, 8u);
|
| + std::unique_ptr<uint8_t[]> serialized_evt1(new uint8_t[evt1_size]);
|
| + evt[1].SerializeToArray(serialized_evt1.get(), evt1_size);
|
| +
|
| + // Create three chunks and split the three events as follows:
|
| + // chunk[0]: evt[0] (full), evt[1] (fragment 1/3)
|
| + // chunk[1]: evt[1] (fragment 2/3)
|
| + // chunk[2]: evt[1] (fragment 3/3), evt[2] (full)
|
| +
|
| + golden_protos::EventsChunk chunk[3];
|
| + for (size_t i = 0; i < arraysize(chunk); ++i) {
|
| + chunk[i].set_writer_id(42);
|
| + chunk[i].set_seq_id(i);
|
| + }
|
| +
|
| + chunk[0].set_first_event_continues_from_prev_chunk(false);
|
| + chunk[0].set_last_event_continues_on_next_chunk(true);
|
| + chunk[0].add_events(evt[0].SerializeAsString());
|
| + chunk[0].add_events(&serialized_evt1[0], 4);
|
| +
|
| + chunk[1].set_first_event_continues_from_prev_chunk(true);
|
| + chunk[1].set_last_event_continues_on_next_chunk(true);
|
| + chunk[1].add_events(&serialized_evt1[4], 4);
|
| +
|
| + chunk[2].set_first_event_continues_from_prev_chunk(true);
|
| + chunk[2].add_events(&serialized_evt1[8], evt1_size - 8);
|
| + chunk[2].add_events(evt[2].SerializeAsString());
|
| +
|
| + TraceBufferReader reader(TraceBufferReader::kBuildLegacyJSONEvents |
|
| + TraceBufferReader::kBuildInternedStringsTable);
|
| + for (size_t i = 0; i < arraysize(chunk); ++i)
|
| + ParseChunk(&reader, chunk[i].SerializeAsString());
|
| +
|
| + ASSERT_EQ(arraysize(evt), reader.legacy_events()->GetSize());
|
| + for (size_t i = 0; i < arraysize(evt); ++i) {
|
| + base::DictionaryValue* decoded_evt = nullptr;
|
| + ASSERT_TRUE(reader.legacy_events()->GetDictionary(i, &decoded_evt));
|
| +
|
| + base::DictionaryValue expected_value;
|
| + expected_value.SetString("ph", "X");
|
| + expected_value.SetString("name", "copied_str_name");
|
| + expected_value.SetString("cat", "interned_category_name");
|
| + expected_value.SetInteger("ts", static_cast<int>(i));
|
| + ASSERT_PRED_FORMAT2(ValueEquals, expected_value, *decoded_evt);
|
| + }
|
| +}
|
| +
|
| +// Test that the fragment reconstruction logic discards fragments if one of
|
| +// the chunks is missing.
|
| +TEST(TraceBufferReaderTest, FragmentedEventsWithMissingChunks) {
|
| + golden_protos::Event evt[2];
|
| + for (size_t i = 0; i < arraysize(evt); ++i) {
|
| + evt[i].set_type(golden_protos::Event::METADATA);
|
| + evt[i].set_timestamp(i);
|
| + }
|
| +
|
| + golden_protos::EventsChunk chunk[2];
|
| + for (size_t i = 0; i < arraysize(chunk); ++i) {
|
| + chunk[i].set_writer_id(42);
|
| + chunk[i].set_seq_id(1 + i);
|
| + }
|
| +
|
| + // The 2nd event (evt[1]) will be split into two fragments.
|
| + const size_t evt1_size = evt[1].ByteSize();
|
| + DCHECK_GT(evt1_size, 2u);
|
| + std::unique_ptr<uint8_t[]> serialized_evt1(new uint8_t[evt1_size]);
|
| + evt[1].SerializeToArray(serialized_evt1.get(), evt1_size);
|
| +
|
| + chunk[0].set_first_event_continues_from_prev_chunk(true);
|
| + chunk[0].set_last_event_continues_on_next_chunk(true);
|
| + chunk[0].add_events("should be discarded because seq id 0 is not seen.");
|
| + chunk[0].add_events(evt[0].SerializeAsString());
|
| + chunk[0].add_events(&serialized_evt1[0], 2);
|
| +
|
| + chunk[1].set_first_event_continues_from_prev_chunk(true);
|
| + chunk[1].set_last_event_continues_on_next_chunk(true);
|
| + chunk[1].add_events(&serialized_evt1[0] + 2, evt1_size - 2);
|
| + chunk[1].add_events("should be discarded because seq id 2 is not seen.");
|
| +
|
| + TraceBufferReader reader(TraceBufferReader::kBuildLegacyJSONEvents);
|
| + ParseChunk(&reader, chunk[0].SerializeAsString());
|
| + ParseChunk(&reader, chunk[1].SerializeAsString());
|
| +
|
| + ASSERT_EQ(arraysize(evt), reader.legacy_events()->GetSize());
|
| + for (size_t i = 0; i < arraysize(evt); ++i) {
|
| + base::DictionaryValue* decoded_evt = nullptr;
|
| + ASSERT_TRUE(reader.legacy_events()->GetDictionary(i, &decoded_evt));
|
| +
|
| + base::DictionaryValue expected_value;
|
| + expected_value.SetString("ph", "M");
|
| + expected_value.SetInteger("ts", static_cast<int>(i));
|
| + ASSERT_PRED_FORMAT2(ValueEquals, expected_value, *decoded_evt);
|
| + }
|
| +}
|
| +
|
| +} // namespace
|
| +} // namespace v2
|
| +} // namespace tracing
|
|
|