1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <string.h>
6 
7 #include <memory>
8 
9 #include "base/bind.h"
10 #include "base/lazy_instance.h"
11 #include "base/synchronization/lock.h"
12 #include "base/synchronization/waitable_event.h"
13 #include "base/threading/thread.h"
14 #include "base/trace_event/category_registry.h"
15 #include "base/trace_event/trace_category.h"
16 #include "build/build_config.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 
19 namespace base {
20 namespace trace_event {
21 
22 // Static initializers are generally forbidden. However, in the past we ran in
23 // the case of some test using tracing in a static initializer. This test checks
24 // That the category registry doesn't rely on static initializers itself and is
25 // functional even if called from another static initializer.
Initializer()26 bool Initializer() {
27   return CategoryRegistry::kCategoryMetadata &&
28          CategoryRegistry::kCategoryMetadata->is_valid();
29 }
30 bool g_initializer_check = Initializer();
31 
32 class TraceCategoryTest : public testing::Test {
33  public:
SetUp()34   void SetUp() override { CategoryRegistry::Initialize(); }
35 
TearDown()36   void TearDown() override { CategoryRegistry::ResetForTesting(); }
37 
GetOrCreateCategoryByName(const char * name,TraceCategory ** cat)38   static bool GetOrCreateCategoryByName(const char* name, TraceCategory** cat) {
39     static LazyInstance<Lock>::Leaky g_lock = LAZY_INSTANCE_INITIALIZER;
40     bool is_new_cat = false;
41     *cat = CategoryRegistry::GetCategoryByName(name);
42     if (!*cat) {
43       AutoLock lock(g_lock.Get());
44       is_new_cat = CategoryRegistry::GetOrCreateCategoryLocked(
45           name, [](TraceCategory*) {}, cat);
46     }
47     return is_new_cat;
48   }
49 
GetAllCategories()50   static CategoryRegistry::Range GetAllCategories() {
51     return CategoryRegistry::GetAllCategories();
52   }
53 
TestRaceThreadMain(WaitableEvent * event)54   static void TestRaceThreadMain(WaitableEvent* event) {
55     TraceCategory* cat = nullptr;
56     event->Wait();
57     GetOrCreateCategoryByName("__test_race", &cat);
58     EXPECT_NE(nullptr, cat);
59   }
60 
GetBuiltinCategoryByName(const char * category_group)61   static constexpr TraceCategory* GetBuiltinCategoryByName(
62       const char* category_group) {
63     return CategoryRegistry::GetBuiltinCategoryByName(category_group);
64   }
65 };
66 
TEST_F(TraceCategoryTest,Basic)67 TEST_F(TraceCategoryTest, Basic) {
68   ASSERT_NE(nullptr, CategoryRegistry::kCategoryMetadata);
69   ASSERT_TRUE(CategoryRegistry::kCategoryMetadata->is_valid());
70   ASSERT_FALSE(CategoryRegistry::kCategoryMetadata->is_enabled());
71 
72   // Metadata category is built-in and should create a new category.
73   TraceCategory* cat_meta = nullptr;
74   const char* kMetadataName = CategoryRegistry::kCategoryMetadata->name();
75   ASSERT_FALSE(GetOrCreateCategoryByName(kMetadataName, &cat_meta));
76   ASSERT_EQ(CategoryRegistry::kCategoryMetadata, cat_meta);
77 
78   TraceCategory* cat_1 = nullptr;
79   ASSERT_TRUE(GetOrCreateCategoryByName("__test_basic_ab", &cat_1));
80   ASSERT_FALSE(cat_1->is_enabled());
81   ASSERT_EQ(0u, cat_1->enabled_filters());
82   cat_1->set_state_flag(TraceCategory::ENABLED_FOR_RECORDING);
83   cat_1->set_state_flag(TraceCategory::ENABLED_FOR_FILTERING);
84   ASSERT_EQ(TraceCategory::ENABLED_FOR_RECORDING |
85                 TraceCategory::ENABLED_FOR_FILTERING,
86             cat_1->state());
87 
88   cat_1->set_enabled_filters(129);
89   ASSERT_EQ(129u, cat_1->enabled_filters());
90   ASSERT_EQ(cat_1, CategoryRegistry::GetCategoryByStatePtr(cat_1->state_ptr()));
91 
92   cat_1->clear_state_flag(TraceCategory::ENABLED_FOR_FILTERING);
93   ASSERT_EQ(TraceCategory::ENABLED_FOR_RECORDING, cat_1->state());
94   ASSERT_EQ(TraceCategory::ENABLED_FOR_RECORDING, *cat_1->state_ptr());
95   ASSERT_TRUE(cat_1->is_enabled());
96 
97   TraceCategory* cat_2 = nullptr;
98   ASSERT_TRUE(GetOrCreateCategoryByName("__test_basic_a", &cat_2));
99   ASSERT_FALSE(cat_2->is_enabled());
100   cat_2->set_state_flag(TraceCategory::ENABLED_FOR_RECORDING);
101 
102   TraceCategory* cat_2_copy = nullptr;
103   ASSERT_FALSE(GetOrCreateCategoryByName("__test_basic_a", &cat_2_copy));
104   ASSERT_EQ(cat_2, cat_2_copy);
105 
106   TraceCategory* cat_3 = nullptr;
107   ASSERT_TRUE(
108       GetOrCreateCategoryByName("__test_basic_ab,__test_basic_a", &cat_3));
109   ASSERT_FALSE(cat_3->is_enabled());
110   ASSERT_EQ(0u, cat_3->enabled_filters());
111 
112   int num_test_categories_seen = 0;
113   for (const TraceCategory& cat : GetAllCategories()) {
114     if (strcmp(cat.name(), kMetadataName) == 0)
115       ASSERT_TRUE(CategoryRegistry::IsMetaCategory(&cat));
116 
117     if (strncmp(cat.name(), "__test_basic_", 13) == 0) {
118       ASSERT_FALSE(CategoryRegistry::IsMetaCategory(&cat));
119       num_test_categories_seen++;
120     }
121   }
122   ASSERT_EQ(3, num_test_categories_seen);
123   ASSERT_TRUE(g_initializer_check);
124 }
125 
126 // Tries to cover the case of multiple threads creating the same category
127 // simultaneously. Should never end up with distinct entries with the same name.
128 #if defined(OS_FUCHSIA)
129 // TODO(crbug.com/738275): This is flaky on Fuchsia.
130 #define MAYBE_ThreadRaces DISABLED_ThreadRaces
131 #else
132 #define MAYBE_ThreadRaces ThreadRaces
133 #endif
TEST_F(TraceCategoryTest,MAYBE_ThreadRaces)134 TEST_F(TraceCategoryTest, MAYBE_ThreadRaces) {
135   const int kNumThreads = 32;
136   std::unique_ptr<Thread> threads[kNumThreads];
137   for (int i = 0; i < kNumThreads; i++) {
138     threads[i].reset(new Thread("test thread"));
139     threads[i]->Start();
140   }
141   WaitableEvent sync_event(WaitableEvent::ResetPolicy::MANUAL,
142                            WaitableEvent::InitialState::NOT_SIGNALED);
143   for (int i = 0; i < kNumThreads; i++) {
144     threads[i]->task_runner()->PostTask(
145         FROM_HERE, BindOnce(&TestRaceThreadMain, Unretained(&sync_event)));
146   }
147   sync_event.Signal();
148   for (int i = 0; i < kNumThreads; i++)
149     threads[i]->Stop();
150 
151   int num_times_seen = 0;
152   for (const TraceCategory& cat : GetAllCategories()) {
153     if (strcmp(cat.name(), "__test_race") == 0)
154       num_times_seen++;
155   }
156   ASSERT_EQ(1, num_times_seen);
157 }
158 
159 // Tests getting trace categories by name at compile-time.
TEST_F(TraceCategoryTest,GetCategoryAtCompileTime)160 TEST_F(TraceCategoryTest, GetCategoryAtCompileTime) {
161   static_assert(GetBuiltinCategoryByName("nonexistent") == nullptr,
162                 "nonexistent found");
163 #if defined(OS_WIN) && defined(COMPONENT_BUILD)
164   static_assert(GetBuiltinCategoryByName("toplevel") == nullptr,
165                 "toplevel found");
166 #else
167   static_assert(GetBuiltinCategoryByName("toplevel") != nullptr,
168                 "toplevel not found");
169 #endif
170 }
171 
172 }  // namespace trace_event
173 }  // namespace base
174