| // 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 <string.h> |
| |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/lazy_instance.h" |
| #include "base/synchronization/lock.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/threading/thread.h" |
| #include "base/trace_event/category_registry.h" |
| #include "base/trace_event/trace_category.h" |
| #include "build/build_config.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace base { |
| namespace trace_event { |
| |
| // Static initializers are generally forbidden. However, in the past we ran in |
| // the case of some test using tracing in a static initializer. This test checks |
| // That the category registry doesn't rely on static initializers itself and is |
| // functional even if called from another static initializer. |
| bool Initializer() { |
| return CategoryRegistry::kCategoryMetadata && |
| CategoryRegistry::kCategoryMetadata->is_valid(); |
| } |
| bool g_initializer_check = Initializer(); |
| |
| class TraceCategoryTest : public testing::Test { |
| public: |
| void SetUp() override { CategoryRegistry::Initialize(); } |
| |
| void TearDown() override { CategoryRegistry::ResetForTesting(); } |
| |
| static bool GetOrCreateCategoryByName(const char* name, TraceCategory** cat) { |
| static LazyInstance<Lock>::Leaky g_lock = LAZY_INSTANCE_INITIALIZER; |
| bool is_new_cat = false; |
| *cat = CategoryRegistry::GetCategoryByName(name); |
| if (!*cat) { |
| AutoLock lock(g_lock.Get()); |
| is_new_cat = CategoryRegistry::GetOrCreateCategoryLocked( |
| name, [](TraceCategory*) {}, cat); |
| } |
| return is_new_cat; |
| }; |
| |
| static CategoryRegistry::Range GetAllCategories() { |
| return CategoryRegistry::GetAllCategories(); |
| } |
| |
| static void TestRaceThreadMain(WaitableEvent* event) { |
| TraceCategory* cat = nullptr; |
| event->Wait(); |
| GetOrCreateCategoryByName("__test_race", &cat); |
| EXPECT_NE(nullptr, cat); |
| } |
| }; |
| |
| TEST_F(TraceCategoryTest, Basic) { |
| ASSERT_NE(nullptr, CategoryRegistry::kCategoryMetadata); |
| ASSERT_TRUE(CategoryRegistry::kCategoryMetadata->is_valid()); |
| ASSERT_FALSE(CategoryRegistry::kCategoryMetadata->is_enabled()); |
| |
| // Metadata category is built-in and should create a new category. |
| TraceCategory* cat_meta = nullptr; |
| const char* kMetadataName = CategoryRegistry::kCategoryMetadata->name(); |
| ASSERT_FALSE(GetOrCreateCategoryByName(kMetadataName, &cat_meta)); |
| ASSERT_EQ(CategoryRegistry::kCategoryMetadata, cat_meta); |
| |
| TraceCategory* cat_1 = nullptr; |
| ASSERT_TRUE(GetOrCreateCategoryByName("__test_basic_ab", &cat_1)); |
| ASSERT_FALSE(cat_1->is_enabled()); |
| ASSERT_EQ(0u, cat_1->enabled_filters()); |
| cat_1->set_state_flag(TraceCategory::ENABLED_FOR_RECORDING); |
| cat_1->set_state_flag(TraceCategory::ENABLED_FOR_FILTERING); |
| ASSERT_EQ(TraceCategory::ENABLED_FOR_RECORDING | |
| TraceCategory::ENABLED_FOR_FILTERING, |
| cat_1->state()); |
| |
| cat_1->set_enabled_filters(129); |
| ASSERT_EQ(129u, cat_1->enabled_filters()); |
| ASSERT_EQ(cat_1, CategoryRegistry::GetCategoryByStatePtr(cat_1->state_ptr())); |
| |
| cat_1->clear_state_flag(TraceCategory::ENABLED_FOR_FILTERING); |
| ASSERT_EQ(TraceCategory::ENABLED_FOR_RECORDING, cat_1->state()); |
| ASSERT_EQ(TraceCategory::ENABLED_FOR_RECORDING, *cat_1->state_ptr()); |
| ASSERT_TRUE(cat_1->is_enabled()); |
| |
| TraceCategory* cat_2 = nullptr; |
| ASSERT_TRUE(GetOrCreateCategoryByName("__test_basic_a", &cat_2)); |
| ASSERT_FALSE(cat_2->is_enabled()); |
| cat_2->set_state_flag(TraceCategory::ENABLED_FOR_RECORDING); |
| |
| TraceCategory* cat_2_copy = nullptr; |
| ASSERT_FALSE(GetOrCreateCategoryByName("__test_basic_a", &cat_2_copy)); |
| ASSERT_EQ(cat_2, cat_2_copy); |
| |
| TraceCategory* cat_3 = nullptr; |
| ASSERT_TRUE( |
| GetOrCreateCategoryByName("__test_basic_ab,__test_basic_a", &cat_3)); |
| ASSERT_FALSE(cat_3->is_enabled()); |
| ASSERT_EQ(0u, cat_3->enabled_filters()); |
| |
| int num_test_categories_seen = 0; |
| for (const TraceCategory& cat : GetAllCategories()) { |
| if (strcmp(cat.name(), kMetadataName) == 0) |
| ASSERT_TRUE(CategoryRegistry::IsBuiltinCategory(&cat)); |
| |
| if (strncmp(cat.name(), "__test_basic_", 13) == 0) { |
| ASSERT_FALSE(CategoryRegistry::IsBuiltinCategory(&cat)); |
| num_test_categories_seen++; |
| } |
| } |
| ASSERT_EQ(3, num_test_categories_seen); |
| ASSERT_TRUE(g_initializer_check); |
| } |
| |
| // Tries to cover the case of multiple threads creating the same category |
| // simultaneously. Should never end up with distinct entries with the same name. |
| #if defined(OS_FUCHSIA) |
| // TODO(crbug.com/738275): This is flaky on Fuchsia. |
| #define MAYBE_ThreadRaces DISABLED_ThreadRaces |
| #else |
| #define MAYBE_ThreadRaces ThreadRaces |
| #endif |
| TEST_F(TraceCategoryTest, MAYBE_ThreadRaces) { |
| const int kNumThreads = 32; |
| std::unique_ptr<Thread> threads[kNumThreads]; |
| for (int i = 0; i < kNumThreads; i++) { |
| threads[i].reset(new Thread("test thread")); |
| threads[i]->Start(); |
| } |
| WaitableEvent sync_event(WaitableEvent::ResetPolicy::MANUAL, |
| WaitableEvent::InitialState::NOT_SIGNALED); |
| for (int i = 0; i < kNumThreads; i++) { |
| threads[i]->task_runner()->PostTask( |
| FROM_HERE, BindOnce(&TestRaceThreadMain, Unretained(&sync_event))); |
| } |
| sync_event.Signal(); |
| for (int i = 0; i < kNumThreads; i++) |
| threads[i]->Stop(); |
| |
| int num_times_seen = 0; |
| for (const TraceCategory& cat : GetAllCategories()) { |
| if (strcmp(cat.name(), "__test_race") == 0) |
| num_times_seen++; |
| } |
| ASSERT_EQ(1, num_times_seen); |
| } |
| |
| } // namespace trace_event |
| } // namespace base |