// Copyright 2018 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/trace_event/trace_arguments.h" #include #include #include #include "base/memory/raw_ptr.h" namespace base { namespace trace_event { namespace { // Simple convertable that holds a string to append to the trace, // and can also write to a boolean flag on destruction. class MyConvertable : public ConvertableToTraceFormat { public: MyConvertable(const char* text, bool* destroy_flag = nullptr) : text_(text), destroy_flag_(destroy_flag) {} ~MyConvertable() override { if (destroy_flag_) *destroy_flag_ = true; } void AppendAsTraceFormat(std::string* out) const override { *out += text_; } const char* text() const { return text_; } private: const char* text_; raw_ptr destroy_flag_; }; } // namespace TEST(TraceArguments, StringStorageDefaultConstruction) { StringStorage storage; EXPECT_TRUE(storage.empty()); EXPECT_FALSE(storage.data()); EXPECT_EQ(0U, storage.size()); } TEST(TraceArguments, StringStorageConstructionWithSize) { const size_t kSize = 128; StringStorage storage(kSize); EXPECT_FALSE(storage.empty()); EXPECT_TRUE(storage.data()); EXPECT_EQ(kSize, storage.size()); EXPECT_EQ(storage.data(), storage.begin()); EXPECT_EQ(storage.data() + kSize, storage.end()); } TEST(TraceArguments, StringStorageReset) { StringStorage storage(128); EXPECT_FALSE(storage.empty()); storage.Reset(); EXPECT_TRUE(storage.empty()); EXPECT_FALSE(storage.data()); EXPECT_EQ(0u, storage.size()); } TEST(TraceArguments, StringStorageResetWithSize) { StringStorage storage; EXPECT_TRUE(storage.empty()); const size_t kSize = 128; storage.Reset(kSize); EXPECT_FALSE(storage.empty()); EXPECT_TRUE(storage.data()); EXPECT_EQ(kSize, storage.size()); EXPECT_EQ(storage.data(), storage.begin()); EXPECT_EQ(storage.data() + kSize, storage.end()); } TEST(TraceArguments, StringStorageEstimateTraceMemoryOverhead) { StringStorage storage; EXPECT_EQ(0u, storage.EstimateTraceMemoryOverhead()); const size_t kSize = 128; storage.Reset(kSize); EXPECT_EQ(sizeof(size_t) + kSize, storage.EstimateTraceMemoryOverhead()); } static void CheckJSONFor(TraceValue v, char type, const char* expected) { std::string out; v.AppendAsJSON(type, &out); EXPECT_STREQ(expected, out.c_str()); } static void CheckStringFor(TraceValue v, char type, const char* expected) { std::string out; v.AppendAsString(type, &out); EXPECT_STREQ(expected, out.c_str()); } TEST(TraceArguments, TraceValueAppend) { TraceValue v; v.Init(-1024); CheckJSONFor(v, TRACE_VALUE_TYPE_INT, "-1024"); CheckStringFor(v, TRACE_VALUE_TYPE_INT, "-1024"); v.Init(1024ULL); CheckJSONFor(v, TRACE_VALUE_TYPE_UINT, "1024"); CheckStringFor(v, TRACE_VALUE_TYPE_UINT, "1024"); v.Init(3.1415926535); CheckJSONFor(v, TRACE_VALUE_TYPE_DOUBLE, "3.1415926535"); CheckStringFor(v, TRACE_VALUE_TYPE_DOUBLE, "3.1415926535"); v.Init(2.0); CheckJSONFor(v, TRACE_VALUE_TYPE_DOUBLE, "2.0"); CheckStringFor(v, TRACE_VALUE_TYPE_DOUBLE, "2.0"); v.Init(0.5); CheckJSONFor(v, TRACE_VALUE_TYPE_DOUBLE, "0.5"); CheckStringFor(v, TRACE_VALUE_TYPE_DOUBLE, "0.5"); v.Init(-0.5); CheckJSONFor(v, TRACE_VALUE_TYPE_DOUBLE, "-0.5"); CheckStringFor(v, TRACE_VALUE_TYPE_DOUBLE, "-0.5"); v.Init(std::numeric_limits::quiet_NaN()); CheckJSONFor(v, TRACE_VALUE_TYPE_DOUBLE, "\"NaN\""); CheckStringFor(v, TRACE_VALUE_TYPE_DOUBLE, "NaN"); v.Init(std::numeric_limits::quiet_NaN()); CheckJSONFor(v, TRACE_VALUE_TYPE_DOUBLE, "\"NaN\""); CheckStringFor(v, TRACE_VALUE_TYPE_DOUBLE, "NaN"); v.Init(std::numeric_limits::infinity()); CheckJSONFor(v, TRACE_VALUE_TYPE_DOUBLE, "\"Infinity\""); CheckStringFor(v, TRACE_VALUE_TYPE_DOUBLE, "Infinity"); v.Init(-std::numeric_limits::infinity()); CheckJSONFor(v, TRACE_VALUE_TYPE_DOUBLE, "\"-Infinity\""); CheckStringFor(v, TRACE_VALUE_TYPE_DOUBLE, "-Infinity"); v.Init(true); CheckJSONFor(v, TRACE_VALUE_TYPE_BOOL, "true"); CheckStringFor(v, TRACE_VALUE_TYPE_BOOL, "true"); v.Init(false); CheckJSONFor(v, TRACE_VALUE_TYPE_BOOL, "false"); CheckStringFor(v, TRACE_VALUE_TYPE_BOOL, "false"); v.Init("Some \"nice\" String"); CheckJSONFor(v, TRACE_VALUE_TYPE_STRING, "\"Some \\\"nice\\\" String\""); CheckStringFor(v, TRACE_VALUE_TYPE_STRING, "Some \"nice\" String"); CheckJSONFor(v, TRACE_VALUE_TYPE_COPY_STRING, "\"Some \\\"nice\\\" String\""); CheckStringFor(v, TRACE_VALUE_TYPE_COPY_STRING, "Some \"nice\" String"); int* p = nullptr; v.Init(static_cast(p)); CheckJSONFor(v, TRACE_VALUE_TYPE_POINTER, "\"0x0\""); CheckStringFor(v, TRACE_VALUE_TYPE_POINTER, "0x0"); const char kText[] = "Hello World"; bool destroy_flag = false; TraceArguments args("arg1", std::make_unique(kText, &destroy_flag)); CheckJSONFor(std::move(args.values()[0]), args.types()[0], kText); CheckStringFor(std::move(args.values()[0]), args.types()[0], kText); } TEST(TraceArguments, DefaultConstruction) { TraceArguments args; EXPECT_EQ(0U, args.size()); } TEST(TraceArguments, ConstructorSingleInteger) { TraceArguments args("foo_int", int(10)); EXPECT_EQ(1U, args.size()); EXPECT_EQ(TRACE_VALUE_TYPE_INT, args.types()[0]); EXPECT_STREQ("foo_int", args.names()[0]); EXPECT_EQ(10, args.values()[0].as_int); } TEST(TraceArguments, ConstructorSingleFloat) { TraceArguments args("foo_pi", float(3.1415)); double expected = float(3.1415); EXPECT_EQ(1U, args.size()); EXPECT_EQ(TRACE_VALUE_TYPE_DOUBLE, args.types()[0]); EXPECT_STREQ("foo_pi", args.names()[0]); EXPECT_EQ(expected, args.values()[0].as_double); } TEST(TraceArguments, ConstructorSingleNoCopyString) { const char kText[] = "Persistent string"; TraceArguments args("foo_cstring", kText); EXPECT_EQ(1U, args.size()); EXPECT_EQ(TRACE_VALUE_TYPE_STRING, args.types()[0]); EXPECT_STREQ("foo_cstring", args.names()[0]); EXPECT_EQ(kText, args.values()[0].as_string); } TEST(TraceArguments, ConstructorSingleStdString) { std::string text = "Non-persistent string"; TraceArguments args("foo_stdstring", text); EXPECT_EQ(1U, args.size()); EXPECT_EQ(TRACE_VALUE_TYPE_COPY_STRING, args.types()[0]); EXPECT_STREQ("foo_stdstring", args.names()[0]); EXPECT_EQ(text.c_str(), args.values()[0].as_string); } TEST(TraceArguments, ConstructorSingleTraceStringWithCopy) { const char kText[] = "Persistent string #2"; TraceArguments args("foo_tracestring", TraceStringWithCopy(kText)); EXPECT_EQ(1U, args.size()); EXPECT_EQ(TRACE_VALUE_TYPE_COPY_STRING, args.types()[0]); EXPECT_STREQ("foo_tracestring", args.names()[0]); EXPECT_EQ(kText, args.values()[0].as_string); } TEST(TraceArguments, ConstructorSinglePointer) { bool destroy_flag = false; { // Simple class that can set a boolean flag on destruction. class Foo { public: Foo(bool* destroy_flag) : destroy_flag_(destroy_flag) {} ~Foo() { if (destroy_flag_) *destroy_flag_ = true; } private: raw_ptr destroy_flag_; }; auto foo = std::make_unique(&destroy_flag); EXPECT_FALSE(destroy_flag); // This test also verifies that the object is not destroyed by the // TraceArguments destructor. This should only be possible for // TRACE_VALUE_TYPE_CONVERTABLE instances. { TraceArguments args("foo_pointer", static_cast(foo.get())); EXPECT_EQ(1U, args.size()); EXPECT_EQ(TRACE_VALUE_TYPE_POINTER, args.types()[0]); EXPECT_STREQ("foo_pointer", args.names()[0]); EXPECT_EQ(foo.get(), args.values()[0].as_pointer); EXPECT_FALSE(destroy_flag); } // Calls TraceArguments destructor. EXPECT_FALSE(destroy_flag); } // Calls Foo destructor. EXPECT_TRUE(destroy_flag); } TEST(TraceArguments, ConstructorSingleConvertable) { bool destroy_flag = false; const char kText[] = "Text for MyConvertable instance"; MyConvertable* ptr = new MyConvertable(kText, &destroy_flag); // This test also verifies that the MyConvertable instance is properly // destroyed when the TraceArguments destructor is called. EXPECT_FALSE(destroy_flag); { TraceArguments args("foo_convertable", std::unique_ptr(ptr)); EXPECT_EQ(1U, args.size()); EXPECT_EQ(TRACE_VALUE_TYPE_CONVERTABLE, args.types()[0]); EXPECT_STREQ("foo_convertable", args.names()[0]); EXPECT_EQ(ptr, args.values()[0].as_convertable); EXPECT_FALSE(destroy_flag); } // Calls TraceArguments destructor. EXPECT_TRUE(destroy_flag); } TEST(TraceArguments, ConstructorWithTwoArguments) { const char kText1[] = "First argument"; const char kText2[] = "Second argument"; bool destroy_flag = false; { MyConvertable* ptr = new MyConvertable(kText2, &destroy_flag); TraceArguments args1("foo_arg1_cstring", kText1, "foo_arg2_convertable", std::unique_ptr(ptr)); EXPECT_EQ(2U, args1.size()); EXPECT_STREQ("foo_arg1_cstring", args1.names()[0]); EXPECT_STREQ("foo_arg2_convertable", args1.names()[1]); EXPECT_EQ(TRACE_VALUE_TYPE_STRING, args1.types()[0]); EXPECT_EQ(TRACE_VALUE_TYPE_CONVERTABLE, args1.types()[1]); EXPECT_EQ(kText1, args1.values()[0].as_string); EXPECT_EQ(ptr, args1.values()[1].as_convertable); EXPECT_FALSE(destroy_flag); } // calls |args1| destructor. Should delete |ptr|. EXPECT_TRUE(destroy_flag); } TEST(TraceArguments, ConstructorLegacyNoConvertables) { const char* const kNames[3] = {"legacy_arg1", "legacy_arg2", "legacy_arg3"}; const unsigned char kTypes[3] = { TRACE_VALUE_TYPE_INT, TRACE_VALUE_TYPE_STRING, TRACE_VALUE_TYPE_POINTER, }; static const char kText[] = "Some text"; const unsigned long long kValues[3] = { 1000042ULL, reinterpret_cast(kText), reinterpret_cast(kText + 2), }; TraceArguments args(3, kNames, kTypes, kValues); // Check that only the first kMaxSize arguments are taken! EXPECT_EQ(2U, args.size()); EXPECT_STREQ(kNames[0], args.names()[0]); EXPECT_STREQ(kNames[1], args.names()[1]); EXPECT_EQ(TRACE_VALUE_TYPE_INT, args.types()[0]); EXPECT_EQ(TRACE_VALUE_TYPE_STRING, args.types()[1]); EXPECT_EQ(kValues[0], args.values()[0].as_uint); EXPECT_EQ(kText, args.values()[1].as_string); } TEST(TraceArguments, ConstructorLegacyWithConvertables) { const char* const kNames[3] = {"legacy_arg1", "legacy_arg2", "legacy_arg3"}; const unsigned char kTypes[3] = { TRACE_VALUE_TYPE_CONVERTABLE, TRACE_VALUE_TYPE_CONVERTABLE, TRACE_VALUE_TYPE_CONVERTABLE, }; std::unique_ptr convertables[3] = { std::make_unique("First one"), std::make_unique("Second one"), std::make_unique("Third one"), }; TraceArguments args(3, kNames, kTypes, nullptr, convertables); // Check that only the first kMaxSize arguments are taken! EXPECT_EQ(2U, args.size()); EXPECT_STREQ(kNames[0], args.names()[0]); EXPECT_STREQ(kNames[1], args.names()[1]); EXPECT_EQ(TRACE_VALUE_TYPE_CONVERTABLE, args.types()[0]); EXPECT_EQ(TRACE_VALUE_TYPE_CONVERTABLE, args.types()[1]); // Check that only the first two items were moved to |args|. EXPECT_FALSE(convertables[0].get()); EXPECT_FALSE(convertables[1].get()); EXPECT_TRUE(convertables[2].get()); } TEST(TraceArguments, MoveConstruction) { const char kText1[] = "First argument"; const char kText2[] = "Second argument"; bool destroy_flag = false; { MyConvertable* ptr = new MyConvertable(kText2, &destroy_flag); TraceArguments args1("foo_arg1_cstring", kText1, "foo_arg2_convertable", std::unique_ptr(ptr)); EXPECT_EQ(2U, args1.size()); EXPECT_STREQ("foo_arg1_cstring", args1.names()[0]); EXPECT_STREQ("foo_arg2_convertable", args1.names()[1]); EXPECT_EQ(TRACE_VALUE_TYPE_STRING, args1.types()[0]); EXPECT_EQ(TRACE_VALUE_TYPE_CONVERTABLE, args1.types()[1]); EXPECT_EQ(kText1, args1.values()[0].as_string); EXPECT_EQ(ptr, args1.values()[1].as_convertable); { TraceArguments args2(std::move(args1)); EXPECT_FALSE(destroy_flag); // |args1| is now empty. EXPECT_EQ(0U, args1.size()); // Check that everything was transferred to |args2|. EXPECT_EQ(2U, args2.size()); EXPECT_STREQ("foo_arg1_cstring", args2.names()[0]); EXPECT_STREQ("foo_arg2_convertable", args2.names()[1]); EXPECT_EQ(TRACE_VALUE_TYPE_STRING, args2.types()[0]); EXPECT_EQ(TRACE_VALUE_TYPE_CONVERTABLE, args2.types()[1]); EXPECT_EQ(kText1, args2.values()[0].as_string); EXPECT_EQ(ptr, args2.values()[1].as_convertable); } // Calls |args2| destructor. Should delete |ptr|. EXPECT_TRUE(destroy_flag); destroy_flag = false; } // Calls |args1| destructor. Should not delete |ptr|. EXPECT_FALSE(destroy_flag); } TEST(TraceArguments, MoveAssignment) { const char kText1[] = "First argument"; const char kText2[] = "Second argument"; bool destroy_flag = false; { MyConvertable* ptr = new MyConvertable(kText2, &destroy_flag); TraceArguments args1("foo_arg1_cstring", kText1, "foo_arg2_convertable", std::unique_ptr(ptr)); EXPECT_EQ(2U, args1.size()); EXPECT_STREQ("foo_arg1_cstring", args1.names()[0]); EXPECT_STREQ("foo_arg2_convertable", args1.names()[1]); EXPECT_EQ(TRACE_VALUE_TYPE_STRING, args1.types()[0]); EXPECT_EQ(TRACE_VALUE_TYPE_CONVERTABLE, args1.types()[1]); EXPECT_EQ(kText1, args1.values()[0].as_string); EXPECT_EQ(ptr, args1.values()[1].as_convertable); { TraceArguments args2; args2 = std::move(args1); EXPECT_FALSE(destroy_flag); // |args1| is now empty. EXPECT_EQ(0U, args1.size()); // Check that everything was transferred to |args2|. EXPECT_EQ(2U, args2.size()); EXPECT_STREQ("foo_arg1_cstring", args2.names()[0]); EXPECT_STREQ("foo_arg2_convertable", args2.names()[1]); EXPECT_EQ(TRACE_VALUE_TYPE_STRING, args2.types()[0]); EXPECT_EQ(TRACE_VALUE_TYPE_CONVERTABLE, args2.types()[1]); EXPECT_EQ(kText1, args2.values()[0].as_string); EXPECT_EQ(ptr, args2.values()[1].as_convertable); } // Calls |args2| destructor. Should delete |ptr|. EXPECT_TRUE(destroy_flag); destroy_flag = false; } // Calls |args1| destructor. Should not delete |ptr|. EXPECT_FALSE(destroy_flag); } TEST(TraceArguments, Reset) { bool destroy_flag = false; { TraceArguments args( "foo_arg1", "Hello", "foo_arg2", std::make_unique("World", &destroy_flag)); EXPECT_EQ(2U, args.size()); EXPECT_FALSE(destroy_flag); args.Reset(); EXPECT_EQ(0U, args.size()); EXPECT_TRUE(destroy_flag); destroy_flag = false; } // Calls |args| destructor. Should not delete twice. EXPECT_FALSE(destroy_flag); } TEST(TraceArguments, CopyStringsTo_NoStrings) { StringStorage storage; TraceArguments args("arg1", 10, "arg2", 42); args.CopyStringsTo(&storage, false, nullptr, nullptr); EXPECT_TRUE(storage.empty()); EXPECT_EQ(0U, storage.size()); } TEST(TraceArguments, CopyStringsTo_OnlyArgs) { StringStorage storage; TraceArguments args("arg1", TraceStringWithCopy("Hello"), "arg2", TraceStringWithCopy("World")); const char kExtra1[] = "extra1"; const char kExtra2[] = "extra2"; const char* extra1 = kExtra1; const char* extra2 = kExtra2; // Types should be copyable strings. EXPECT_EQ(TRACE_VALUE_TYPE_COPY_STRING, args.types()[0]); EXPECT_EQ(TRACE_VALUE_TYPE_COPY_STRING, args.types()[1]); args.CopyStringsTo(&storage, false, &extra1, &extra2); // Storage should be allocated. EXPECT_TRUE(storage.data()); EXPECT_NE(0U, storage.size()); // Types should not be changed. EXPECT_EQ(TRACE_VALUE_TYPE_COPY_STRING, args.types()[0]); EXPECT_EQ(TRACE_VALUE_TYPE_COPY_STRING, args.types()[1]); // names should not be copied. EXPECT_FALSE(storage.Contains(args.names()[0])); EXPECT_FALSE(storage.Contains(args.names()[1])); EXPECT_STREQ("arg1", args.names()[0]); EXPECT_STREQ("arg2", args.names()[1]); // strings should be copied. EXPECT_TRUE(storage.Contains(args.values()[0].as_string)); EXPECT_TRUE(storage.Contains(args.values()[1].as_string)); EXPECT_STREQ("Hello", args.values()[0].as_string); EXPECT_STREQ("World", args.values()[1].as_string); // |extra1| and |extra2| should not be copied. EXPECT_EQ(kExtra1, extra1); EXPECT_EQ(kExtra2, extra2); } TEST(TraceArguments, CopyStringsTo_Everything) { StringStorage storage; TraceArguments args("arg1", "Hello", "arg2", "World"); const char kExtra1[] = "extra1"; const char kExtra2[] = "extra2"; const char* extra1 = kExtra1; const char* extra2 = kExtra2; // Types should be normal strings. EXPECT_EQ(TRACE_VALUE_TYPE_STRING, args.types()[0]); EXPECT_EQ(TRACE_VALUE_TYPE_STRING, args.types()[1]); args.CopyStringsTo(&storage, true, &extra1, &extra2); // Storage should be allocated. EXPECT_TRUE(storage.data()); EXPECT_NE(0U, storage.size()); // Types should be changed to copyable strings. EXPECT_EQ(TRACE_VALUE_TYPE_COPY_STRING, args.types()[0]); EXPECT_EQ(TRACE_VALUE_TYPE_COPY_STRING, args.types()[1]); // names should be copied. EXPECT_TRUE(storage.Contains(args.names()[0])); EXPECT_TRUE(storage.Contains(args.names()[1])); EXPECT_STREQ("arg1", args.names()[0]); EXPECT_STREQ("arg2", args.names()[1]); // strings should be copied. EXPECT_TRUE(storage.Contains(args.values()[0].as_string)); EXPECT_TRUE(storage.Contains(args.values()[1].as_string)); EXPECT_STREQ("Hello", args.values()[0].as_string); EXPECT_STREQ("World", args.values()[1].as_string); // |extra1| and |extra2| should be copied. EXPECT_NE(kExtra1, extra1); EXPECT_NE(kExtra2, extra2); EXPECT_TRUE(storage.Contains(extra1)); EXPECT_TRUE(storage.Contains(extra2)); EXPECT_STREQ(kExtra1, extra1); EXPECT_STREQ(kExtra2, extra2); } } // namespace trace_event } // namespace base