| // 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 "base/debug/activity_tracker.h" |
| |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/files/file.h" |
| #include "base/files/file_util.h" |
| #include "base/files/memory_mapped_file.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/pending_task.h" |
| #include "base/rand_util.h" |
| #include "base/synchronization/condition_variable.h" |
| #include "base/synchronization/lock.h" |
| #include "base/synchronization/spin_wait.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/threading/simple_thread.h" |
| #include "base/time/time.h" |
| #include "starboard/memory.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace base { |
| namespace debug { |
| |
| namespace { |
| |
| class TestActivityTracker : public ThreadActivityTracker { |
| public: |
| TestActivityTracker(std::unique_ptr<char[]> memory, size_t mem_size) |
| : ThreadActivityTracker(SbMemorySet(memory.get(), 0, mem_size), mem_size), |
| mem_segment_(std::move(memory)) {} |
| |
| ~TestActivityTracker() override = default; |
| |
| private: |
| std::unique_ptr<char[]> mem_segment_; |
| }; |
| |
| } // namespace |
| |
| |
| class ActivityTrackerTest : public testing::Test { |
| public: |
| const int kMemorySize = 1 << 20; // 1MiB |
| const int kStackSize = 1 << 10; // 1KiB |
| |
| using ActivityId = ThreadActivityTracker::ActivityId; |
| |
| ActivityTrackerTest() = default; |
| |
| ~ActivityTrackerTest() override { |
| GlobalActivityTracker* global_tracker = GlobalActivityTracker::Get(); |
| if (global_tracker) { |
| global_tracker->ReleaseTrackerForCurrentThreadForTesting(); |
| delete global_tracker; |
| } |
| } |
| |
| std::unique_ptr<ThreadActivityTracker> CreateActivityTracker() { |
| std::unique_ptr<char[]> memory(new char[kStackSize]); |
| return std::make_unique<TestActivityTracker>(std::move(memory), kStackSize); |
| } |
| |
| size_t GetGlobalActiveTrackerCount() { |
| GlobalActivityTracker* global_tracker = GlobalActivityTracker::Get(); |
| if (!global_tracker) |
| return 0; |
| return global_tracker->thread_tracker_count_.load( |
| std::memory_order_relaxed); |
| } |
| |
| size_t GetGlobalInactiveTrackerCount() { |
| GlobalActivityTracker* global_tracker = GlobalActivityTracker::Get(); |
| if (!global_tracker) |
| return 0; |
| AutoLock autolock(global_tracker->thread_tracker_allocator_lock_); |
| return global_tracker->thread_tracker_allocator_.cache_used(); |
| } |
| |
| size_t GetGlobalUserDataMemoryCacheUsed() { |
| return GlobalActivityTracker::Get()->user_data_allocator_.cache_used(); |
| } |
| |
| void HandleProcessExit(int64_t id, |
| int64_t stamp, |
| int code, |
| GlobalActivityTracker::ProcessPhase phase, |
| std::string&& command, |
| ActivityUserData::Snapshot&& data) { |
| exit_id_ = id; |
| exit_stamp_ = stamp; |
| exit_code_ = code; |
| exit_phase_ = phase; |
| exit_command_ = std::move(command); |
| exit_data_ = std::move(data); |
| } |
| |
| int64_t exit_id_ = 0; |
| int64_t exit_stamp_; |
| int exit_code_; |
| GlobalActivityTracker::ProcessPhase exit_phase_; |
| std::string exit_command_; |
| ActivityUserData::Snapshot exit_data_; |
| }; |
| |
| TEST_F(ActivityTrackerTest, UserDataTest) { |
| char buffer[256]; |
| SbMemorySet(buffer, 0, sizeof(buffer)); |
| ActivityUserData data(buffer, sizeof(buffer)); |
| size_t space = sizeof(buffer) - sizeof(ActivityUserData::MemoryHeader); |
| ASSERT_EQ(space, data.available_); |
| |
| data.SetInt("foo", 1); |
| space -= 24; |
| ASSERT_EQ(space, data.available_); |
| |
| data.SetUint("b", 1U); // Small names fit beside header in a word. |
| space -= 16; |
| ASSERT_EQ(space, data.available_); |
| |
| data.Set("c", buffer, 10); |
| space -= 24; |
| ASSERT_EQ(space, data.available_); |
| |
| data.SetString("dear john", "it's been fun"); |
| space -= 32; |
| ASSERT_EQ(space, data.available_); |
| |
| data.Set("c", buffer, 20); |
| ASSERT_EQ(space, data.available_); |
| |
| data.SetString("dear john", "but we're done together"); |
| ASSERT_EQ(space, data.available_); |
| |
| data.SetString("dear john", "bye"); |
| ASSERT_EQ(space, data.available_); |
| |
| data.SetChar("d", 'x'); |
| space -= 8; |
| ASSERT_EQ(space, data.available_); |
| |
| data.SetBool("ee", true); |
| space -= 16; |
| ASSERT_EQ(space, data.available_); |
| |
| data.SetString("f", ""); |
| space -= 8; |
| ASSERT_EQ(space, data.available_); |
| } |
| |
| TEST_F(ActivityTrackerTest, PushPopTest) { |
| std::unique_ptr<ThreadActivityTracker> tracker = CreateActivityTracker(); |
| ThreadActivityTracker::Snapshot snapshot; |
| |
| ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); |
| ASSERT_EQ(0U, snapshot.activity_stack_depth); |
| ASSERT_EQ(0U, snapshot.activity_stack.size()); |
| |
| char origin1; |
| ActivityId id1 = tracker->PushActivity(&origin1, Activity::ACT_TASK, |
| ActivityData::ForTask(11)); |
| ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); |
| ASSERT_EQ(1U, snapshot.activity_stack_depth); |
| ASSERT_EQ(1U, snapshot.activity_stack.size()); |
| EXPECT_NE(0, snapshot.activity_stack[0].time_internal); |
| EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[0].activity_type); |
| EXPECT_EQ(reinterpret_cast<uintptr_t>(&origin1), |
| snapshot.activity_stack[0].origin_address); |
| EXPECT_EQ(11U, snapshot.activity_stack[0].data.task.sequence_id); |
| |
| char origin2; |
| char lock2; |
| ActivityId id2 = tracker->PushActivity(&origin2, Activity::ACT_LOCK, |
| ActivityData::ForLock(&lock2)); |
| ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); |
| ASSERT_EQ(2U, snapshot.activity_stack_depth); |
| ASSERT_EQ(2U, snapshot.activity_stack.size()); |
| EXPECT_LE(snapshot.activity_stack[0].time_internal, |
| snapshot.activity_stack[1].time_internal); |
| EXPECT_EQ(Activity::ACT_LOCK, snapshot.activity_stack[1].activity_type); |
| EXPECT_EQ(reinterpret_cast<uintptr_t>(&origin2), |
| snapshot.activity_stack[1].origin_address); |
| EXPECT_EQ(reinterpret_cast<uintptr_t>(&lock2), |
| snapshot.activity_stack[1].data.lock.lock_address); |
| |
| tracker->PopActivity(id2); |
| ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); |
| ASSERT_EQ(1U, snapshot.activity_stack_depth); |
| ASSERT_EQ(1U, snapshot.activity_stack.size()); |
| EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[0].activity_type); |
| EXPECT_EQ(reinterpret_cast<uintptr_t>(&origin1), |
| snapshot.activity_stack[0].origin_address); |
| EXPECT_EQ(11U, snapshot.activity_stack[0].data.task.sequence_id); |
| |
| tracker->PopActivity(id1); |
| ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); |
| ASSERT_EQ(0U, snapshot.activity_stack_depth); |
| ASSERT_EQ(0U, snapshot.activity_stack.size()); |
| } |
| |
| TEST_F(ActivityTrackerTest, ScopedTaskTest) { |
| GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0); |
| |
| ThreadActivityTracker* tracker = |
| GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread(); |
| ThreadActivityTracker::Snapshot snapshot; |
| ASSERT_EQ(0U, GetGlobalUserDataMemoryCacheUsed()); |
| |
| ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); |
| ASSERT_EQ(0U, snapshot.activity_stack_depth); |
| ASSERT_EQ(0U, snapshot.activity_stack.size()); |
| |
| { |
| PendingTask task1(FROM_HERE, DoNothing()); |
| ScopedTaskRunActivity activity1(task1); |
| ActivityUserData& user_data1 = activity1.user_data(); |
| (void)user_data1; // Tell compiler it's been used. |
| |
| ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); |
| ASSERT_EQ(1U, snapshot.activity_stack_depth); |
| ASSERT_EQ(1U, snapshot.activity_stack.size()); |
| EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[0].activity_type); |
| |
| { |
| PendingTask task2(FROM_HERE, DoNothing()); |
| ScopedTaskRunActivity activity2(task2); |
| ActivityUserData& user_data2 = activity2.user_data(); |
| (void)user_data2; // Tell compiler it's been used. |
| |
| ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); |
| ASSERT_EQ(2U, snapshot.activity_stack_depth); |
| ASSERT_EQ(2U, snapshot.activity_stack.size()); |
| EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[1].activity_type); |
| } |
| |
| ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); |
| ASSERT_EQ(1U, snapshot.activity_stack_depth); |
| ASSERT_EQ(1U, snapshot.activity_stack.size()); |
| EXPECT_EQ(Activity::ACT_TASK, snapshot.activity_stack[0].activity_type); |
| } |
| |
| ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); |
| ASSERT_EQ(0U, snapshot.activity_stack_depth); |
| ASSERT_EQ(0U, snapshot.activity_stack.size()); |
| ASSERT_EQ(2U, GetGlobalUserDataMemoryCacheUsed()); |
| } |
| |
| namespace { |
| |
| class SimpleLockThread : public SimpleThread { |
| public: |
| SimpleLockThread(const std::string& name, Lock* lock) |
| : SimpleThread(name, Options()), |
| lock_(lock), |
| data_changed_(false), |
| is_running_(false) {} |
| |
| ~SimpleLockThread() override = default; |
| |
| void Run() override { |
| ThreadActivityTracker* tracker = |
| GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread(); |
| uint32_t pre_version = tracker->GetDataVersionForTesting(); |
| |
| is_running_.store(true, std::memory_order_relaxed); |
| lock_->Acquire(); |
| data_changed_ = tracker->GetDataVersionForTesting() != pre_version; |
| lock_->Release(); |
| is_running_.store(false, std::memory_order_relaxed); |
| } |
| |
| bool IsRunning() { return is_running_.load(std::memory_order_relaxed); } |
| |
| bool WasDataChanged() { return data_changed_; }; |
| |
| private: |
| Lock* lock_; |
| bool data_changed_; |
| std::atomic<bool> is_running_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SimpleLockThread); |
| }; |
| |
| } // namespace |
| |
| TEST_F(ActivityTrackerTest, LockTest) { |
| GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0); |
| |
| ThreadActivityTracker* tracker = |
| GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread(); |
| ThreadActivityTracker::Snapshot snapshot; |
| ASSERT_EQ(0U, GetGlobalUserDataMemoryCacheUsed()); |
| |
| Lock lock; |
| uint32_t pre_version = tracker->GetDataVersionForTesting(); |
| |
| // Check no activity when only "trying" a lock. |
| EXPECT_TRUE(lock.Try()); |
| EXPECT_EQ(pre_version, tracker->GetDataVersionForTesting()); |
| lock.Release(); |
| EXPECT_EQ(pre_version, tracker->GetDataVersionForTesting()); |
| |
| // Check no activity when acquiring a free lock. |
| SimpleLockThread t1("locker1", &lock); |
| t1.Start(); |
| t1.Join(); |
| EXPECT_FALSE(t1.WasDataChanged()); |
| |
| // Check that activity is recorded when acquring a busy lock. |
| SimpleLockThread t2("locker2", &lock); |
| lock.Acquire(); |
| t2.Start(); |
| while (!t2.IsRunning()) |
| PlatformThread::Sleep(TimeDelta::FromMilliseconds(10)); |
| // t2 can't join until the lock is released but have to give time for t2 to |
| // actually block on the lock before releasing it or the results will not |
| // be correct. |
| PlatformThread::Sleep(TimeDelta::FromMilliseconds(200)); |
| lock.Release(); |
| // Now the results will be valid. |
| t2.Join(); |
| EXPECT_TRUE(t2.WasDataChanged()); |
| } |
| |
| TEST_F(ActivityTrackerTest, ExceptionTest) { |
| GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0); |
| GlobalActivityTracker* global = GlobalActivityTracker::Get(); |
| |
| ThreadActivityTracker* tracker = |
| GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread(); |
| ThreadActivityTracker::Snapshot snapshot; |
| ASSERT_EQ(0U, GetGlobalUserDataMemoryCacheUsed()); |
| |
| ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); |
| ASSERT_EQ(0U, snapshot.last_exception.activity_type); |
| |
| char origin; |
| global->RecordException(&origin, 42); |
| |
| ASSERT_TRUE(tracker->CreateSnapshot(&snapshot)); |
| EXPECT_EQ(Activity::ACT_EXCEPTION, snapshot.last_exception.activity_type); |
| EXPECT_EQ(reinterpret_cast<uintptr_t>(&origin), |
| snapshot.last_exception.origin_address); |
| EXPECT_EQ(42U, snapshot.last_exception.data.exception.code); |
| } |
| |
| TEST_F(ActivityTrackerTest, CreateWithFileTest) { |
| const char temp_name[] = "CreateWithFileTest"; |
| ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| FilePath temp_file = temp_dir.GetPath().AppendASCII(temp_name); |
| const size_t temp_size = 64 << 10; // 64 KiB |
| |
| // Create a global tracker on a new file. |
| ASSERT_FALSE(PathExists(temp_file)); |
| GlobalActivityTracker::CreateWithFile(temp_file, temp_size, 0, "foo", 3); |
| GlobalActivityTracker* global = GlobalActivityTracker::Get(); |
| EXPECT_EQ(std::string("foo"), global->allocator()->Name()); |
| global->ReleaseTrackerForCurrentThreadForTesting(); |
| delete global; |
| |
| // Create a global tracker over an existing file, replacing it. If the |
| // replacement doesn't work, the name will remain as it was first created. |
| ASSERT_TRUE(PathExists(temp_file)); |
| GlobalActivityTracker::CreateWithFile(temp_file, temp_size, 0, "bar", 3); |
| global = GlobalActivityTracker::Get(); |
| EXPECT_EQ(std::string("bar"), global->allocator()->Name()); |
| global->ReleaseTrackerForCurrentThreadForTesting(); |
| delete global; |
| } |
| |
| |
| // GlobalActivityTracker tests below. |
| |
| TEST_F(ActivityTrackerTest, BasicTest) { |
| GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0); |
| GlobalActivityTracker* global = GlobalActivityTracker::Get(); |
| |
| // Ensure the data repositories have backing store, indicated by non-zero ID. |
| EXPECT_NE(0U, global->process_data().id()); |
| } |
| |
| namespace { |
| |
| class SimpleActivityThread : public SimpleThread { |
| public: |
| SimpleActivityThread(const std::string& name, |
| const void* origin, |
| Activity::Type activity, |
| const ActivityData& data) |
| : SimpleThread(name, Options()), |
| origin_(origin), |
| activity_(activity), |
| data_(data), |
| ready_(false), |
| exit_(false), |
| exit_condition_(&lock_) {} |
| |
| ~SimpleActivityThread() override = default; |
| |
| void Run() override { |
| ThreadActivityTracker::ActivityId id = |
| GlobalActivityTracker::Get() |
| ->GetOrCreateTrackerForCurrentThread() |
| ->PushActivity(origin_, activity_, data_); |
| |
| { |
| AutoLock auto_lock(lock_); |
| ready_.store(true, std::memory_order_release); |
| while (!exit_.load(std::memory_order_relaxed)) |
| exit_condition_.Wait(); |
| } |
| |
| GlobalActivityTracker::Get()->GetTrackerForCurrentThread()->PopActivity(id); |
| } |
| |
| void Exit() { |
| AutoLock auto_lock(lock_); |
| exit_.store(true, std::memory_order_relaxed); |
| exit_condition_.Signal(); |
| } |
| |
| void WaitReady() { |
| SPIN_FOR_1_SECOND_OR_UNTIL_TRUE(ready_.load(std::memory_order_acquire)); |
| } |
| |
| private: |
| const void* origin_; |
| Activity::Type activity_; |
| ActivityData data_; |
| |
| std::atomic<bool> ready_; |
| std::atomic<bool> exit_; |
| Lock lock_; |
| ConditionVariable exit_condition_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SimpleActivityThread); |
| }; |
| |
| } // namespace |
| |
| TEST_F(ActivityTrackerTest, ThreadDeathTest) { |
| GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0); |
| GlobalActivityTracker::Get()->GetOrCreateTrackerForCurrentThread(); |
| const size_t starting_active = GetGlobalActiveTrackerCount(); |
| const size_t starting_inactive = GetGlobalInactiveTrackerCount(); |
| |
| SimpleActivityThread t1("t1", nullptr, Activity::ACT_TASK, |
| ActivityData::ForTask(11)); |
| t1.Start(); |
| t1.WaitReady(); |
| EXPECT_EQ(starting_active + 1, GetGlobalActiveTrackerCount()); |
| EXPECT_EQ(starting_inactive, GetGlobalInactiveTrackerCount()); |
| |
| t1.Exit(); |
| t1.Join(); |
| EXPECT_EQ(starting_active, GetGlobalActiveTrackerCount()); |
| EXPECT_EQ(starting_inactive + 1, GetGlobalInactiveTrackerCount()); |
| |
| // Start another thread and ensure it re-uses the existing memory. |
| |
| SimpleActivityThread t2("t2", nullptr, Activity::ACT_TASK, |
| ActivityData::ForTask(22)); |
| t2.Start(); |
| t2.WaitReady(); |
| EXPECT_EQ(starting_active + 1, GetGlobalActiveTrackerCount()); |
| EXPECT_EQ(starting_inactive, GetGlobalInactiveTrackerCount()); |
| |
| t2.Exit(); |
| t2.Join(); |
| EXPECT_EQ(starting_active, GetGlobalActiveTrackerCount()); |
| EXPECT_EQ(starting_inactive + 1, GetGlobalInactiveTrackerCount()); |
| } |
| |
| TEST_F(ActivityTrackerTest, ProcessDeathTest) { |
| // This doesn't actually create and destroy a process. Instead, it uses for- |
| // testing interfaces to simulate data created by other processes. |
| const int64_t other_process_id = GetCurrentProcId() + 1; |
| |
| GlobalActivityTracker::CreateWithLocalMemory(kMemorySize, 0, "", 3, 0); |
| GlobalActivityTracker* global = GlobalActivityTracker::Get(); |
| ThreadActivityTracker* thread = global->GetOrCreateTrackerForCurrentThread(); |
| |
| // Get callbacks for process exit. |
| global->SetProcessExitCallback( |
| Bind(&ActivityTrackerTest::HandleProcessExit, Unretained(this))); |
| |
| // Pretend than another process has started. |
| global->RecordProcessLaunch(other_process_id, FILE_PATH_LITERAL("foo --bar")); |
| |
| // Do some activities. |
| PendingTask task(FROM_HERE, DoNothing()); |
| ScopedTaskRunActivity activity(task); |
| ActivityUserData& user_data = activity.user_data(); |
| ASSERT_NE(0U, user_data.id()); |
| |
| // Get the memory-allocator references to that data. |
| PersistentMemoryAllocator::Reference proc_data_ref = |
| global->allocator()->GetAsReference( |
| global->process_data().GetBaseAddress(), |
| GlobalActivityTracker::kTypeIdProcessDataRecord); |
| ASSERT_TRUE(proc_data_ref); |
| PersistentMemoryAllocator::Reference tracker_ref = |
| global->allocator()->GetAsReference( |
| thread->GetBaseAddress(), |
| GlobalActivityTracker::kTypeIdActivityTracker); |
| ASSERT_TRUE(tracker_ref); |
| PersistentMemoryAllocator::Reference user_data_ref = |
| global->allocator()->GetAsReference( |
| user_data.GetBaseAddress(), |
| GlobalActivityTracker::kTypeIdUserDataRecord); |
| ASSERT_TRUE(user_data_ref); |
| |
| // Make a copy of the thread-tracker state so it can be restored later. |
| const size_t tracker_size = global->allocator()->GetAllocSize(tracker_ref); |
| std::unique_ptr<char[]> tracker_copy(new char[tracker_size]); |
| SbMemoryCopy(tracker_copy.get(), thread->GetBaseAddress(), tracker_size); |
| |
| // Change the objects to appear to be owned by another process. Use a "past" |
| // time so that exit-time is always later than create-time. |
| const int64_t past_stamp = Time::Now().ToInternalValue() - 1; |
| int64_t owning_id; |
| int64_t stamp; |
| ASSERT_TRUE(ActivityUserData::GetOwningProcessId( |
| global->process_data().GetBaseAddress(), &owning_id, &stamp)); |
| EXPECT_NE(other_process_id, owning_id); |
| ASSERT_TRUE(ThreadActivityTracker::GetOwningProcessId( |
| thread->GetBaseAddress(), &owning_id, &stamp)); |
| EXPECT_NE(other_process_id, owning_id); |
| ASSERT_TRUE(ActivityUserData::GetOwningProcessId(user_data.GetBaseAddress(), |
| &owning_id, &stamp)); |
| EXPECT_NE(other_process_id, owning_id); |
| global->process_data().SetOwningProcessIdForTesting(other_process_id, |
| past_stamp); |
| thread->SetOwningProcessIdForTesting(other_process_id, past_stamp); |
| user_data.SetOwningProcessIdForTesting(other_process_id, past_stamp); |
| ASSERT_TRUE(ActivityUserData::GetOwningProcessId( |
| global->process_data().GetBaseAddress(), &owning_id, &stamp)); |
| EXPECT_EQ(other_process_id, owning_id); |
| ASSERT_TRUE(ThreadActivityTracker::GetOwningProcessId( |
| thread->GetBaseAddress(), &owning_id, &stamp)); |
| EXPECT_EQ(other_process_id, owning_id); |
| ASSERT_TRUE(ActivityUserData::GetOwningProcessId(user_data.GetBaseAddress(), |
| &owning_id, &stamp)); |
| EXPECT_EQ(other_process_id, owning_id); |
| |
| // Check that process exit will perform callback and free the allocations. |
| ASSERT_EQ(0, exit_id_); |
| ASSERT_EQ(GlobalActivityTracker::kTypeIdProcessDataRecord, |
| global->allocator()->GetType(proc_data_ref)); |
| ASSERT_EQ(GlobalActivityTracker::kTypeIdActivityTracker, |
| global->allocator()->GetType(tracker_ref)); |
| ASSERT_EQ(GlobalActivityTracker::kTypeIdUserDataRecord, |
| global->allocator()->GetType(user_data_ref)); |
| global->RecordProcessExit(other_process_id, 0); |
| EXPECT_EQ(other_process_id, exit_id_); |
| EXPECT_EQ("foo --bar", exit_command_); |
| EXPECT_EQ(GlobalActivityTracker::kTypeIdProcessDataRecordFree, |
| global->allocator()->GetType(proc_data_ref)); |
| EXPECT_EQ(GlobalActivityTracker::kTypeIdActivityTrackerFree, |
| global->allocator()->GetType(tracker_ref)); |
| EXPECT_EQ(GlobalActivityTracker::kTypeIdUserDataRecordFree, |
| global->allocator()->GetType(user_data_ref)); |
| |
| // Restore memory contents and types so things don't crash when doing real |
| // process clean-up. |
| SbMemoryCopy(const_cast<void*>(thread->GetBaseAddress()), tracker_copy.get(), |
| tracker_size); |
| global->allocator()->ChangeType( |
| proc_data_ref, GlobalActivityTracker::kTypeIdProcessDataRecord, |
| GlobalActivityTracker::kTypeIdUserDataRecordFree, false); |
| global->allocator()->ChangeType( |
| tracker_ref, GlobalActivityTracker::kTypeIdActivityTracker, |
| GlobalActivityTracker::kTypeIdActivityTrackerFree, false); |
| global->allocator()->ChangeType( |
| user_data_ref, GlobalActivityTracker::kTypeIdUserDataRecord, |
| GlobalActivityTracker::kTypeIdUserDataRecordFree, false); |
| } |
| |
| } // namespace debug |
| } // namespace base |