diff options
Diffstat (limited to 'ceee/ie/broker/executors_manager_unittest.cc')
-rw-r--r-- | ceee/ie/broker/executors_manager_unittest.cc | 664 |
1 files changed, 664 insertions, 0 deletions
diff --git a/ceee/ie/broker/executors_manager_unittest.cc b/ceee/ie/broker/executors_manager_unittest.cc new file mode 100644 index 0000000..47c6275 --- /dev/null +++ b/ceee/ie/broker/executors_manager_unittest.cc @@ -0,0 +1,664 @@ +// Copyright (c) 2010 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. +// +// Unit tests for executors manager. + +#include <vector> + +#include "base/logging.h" +#include "ceee/ie/broker/executors_manager.h" +#include "ceee/testing/utils/mock_win32.h" +#include "ceee/testing/utils/test_utils.h" +#include "ceee/testing/utils/nt_internals.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using testing::_; +using testing::DoAll; +using testing::InvokeWithoutArgs; +using testing::Return; +using testing::SetArgumentPointee; + +// We mock the IUnknown interface to make sure we properly AddRef and +// Release the executors in the manager's map. +// TODO(mad@chromium.org): replace this with the MockExecutorIUnknown +// in mock_broker_and_friends.h. +class MockExecutor : public IUnknown { + public: + MockExecutor() : ref_count_(0) {} + STDMETHOD_(ULONG, AddRef)() { return ++ref_count_; } + STDMETHOD_(ULONG, Release)() { + EXPECT_GT(ref_count_, 0UL); + return --ref_count_; + } + ULONG ref_count() const { return ref_count_; } + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, QueryInterface, + HRESULT(REFIID, void**)); + private: + ULONG ref_count_; +}; + +// We also need to mock the creator or executors for 1) making sure it is +// called properly, and 2) for it to return our mock executor. +class MockExecutorCreator: public ICeeeExecutorCreator { + public: + // No need to mock AddRef, it is not called. + STDMETHOD_(ULONG, AddRef)() { return 1; } + MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Release, ULONG()); + STDMETHOD (QueryInterface)(REFIID, LPVOID*) { return S_OK; } + + MOCK_METHOD2_WITH_CALLTYPE(__stdcall, CreateWindowExecutor, + HRESULT(long, CeeeWindowHandle)); + MOCK_METHOD1_WITH_CALLTYPE(__stdcall, Teardown, HRESULT(long)); +}; + +// We need to override some virtual functions of the executors manager and +// also provide public access to some protected methods and data. +class TestingExecutorsManager : public ExecutorsManager { + public: + TestingExecutorsManager() + : ExecutorsManager(true), // no thread. + current_executor_(NULL), + current_handle_(NULL), + current_thread_id_(kThreadId) { + } + + ~TestingExecutorsManager() { + nt_internals::PUBLIC_OBJECT_BASIC_INFORMATION pobi; + ULONG length = 0; + for (size_t index = 0; index < opened_handles_.size(); ++index) { + nt_internals::NtQueryObject(opened_handles_[index].first, + nt_internals::ObjectBasicInformation, &pobi, sizeof(pobi), &length); + EXPECT_EQ(length, sizeof(pobi)); + EXPECT_EQ(1UL, pobi.HandleCount); + if (1UL != pobi.HandleCount) { + printf("Leaked handle for: %s\n", opened_handles_[index].second); + } + EXPECT_TRUE(::CloseHandle(opened_handles_[index].first)); + } + } + + MOCK_METHOD2(WaitForSingleObject, DWORD(HANDLE, DWORD)); + MOCK_METHOD4(WaitForMultipleObjects, + DWORD(DWORD, const HANDLE*, BOOL, DWORD)); + + HANDLE GetNewHandle(const char* info) { + // Some tests depend on the handle to be manual reset. + opened_handles_.push_back(std::pair<HANDLE, const char*>( + ::CreateEvent(NULL, TRUE, FALSE, NULL), info)); + HANDLE returned_handle = NULL; + EXPECT_TRUE(::DuplicateHandle(::GetCurrentProcess(), + opened_handles_.back().first, ::GetCurrentProcess(), &returned_handle, + 0, FALSE, DUPLICATE_SAME_ACCESS)); + return returned_handle; + } + + // This is a seem in the base class allowing us ot use our own creator. + HRESULT GetExecutorCreator(ICeeeExecutorCreator** executor_creator) { + *executor_creator = &executor_creator_; + return S_OK; + } + // provide public access so we can test the method. + using ExecutorsManager::GetThreadHandles; + // Wrap the call to ThreadPRoc by first creating the necessary info. + DWORD CallThreadProc() { + // This struct is declared in the unnamed namespace of the implementation. + struct ThreadStartData { + ExecutorsManager* me; + CHandle thread_started_gate; + } thread_start_data; + thread_start_data.me = this; + thread_start_data.thread_started_gate.Attach(GetNewHandle("TSD")); + DCHECK(thread_start_data.thread_started_gate != NULL); + return ExecutorsManager::ThreadProc(&thread_start_data); + } + + // Access to protected handles. + HANDLE GetUpdateHandle() { + return update_threads_list_gate_.m_h; + } + HANDLE GetTerminateHandle() { + return termination_gate_.m_h; + } + + // Fake a pending registration, to properly test that case. + void AddPendingRegistration(HANDLE thread_handle, + ThreadId thread_id = kThreadId) { + pending_registrations_[thread_id].Attach(thread_handle); + } + void RemovePendingRegistration(ThreadId thread_id = kThreadId) { + pending_registrations_.erase(thread_id); + } + + // This method is to be Invoked by the Mock of WaitForXXXObject[s]. + void RegisterExecutorOnWait() { + executors_[current_thread_id_] = ExecutorInfo(current_executor_, + current_handle_); + } + + // Public access to the executors_ map. + void FakeRegisterExecutor(HANDLE handle, + IUnknown* executor = NULL, + ThreadId thread_id = kThreadId) { + executors_[thread_id] = ExecutorInfo(executor, handle); + } + size_t GetNumExecutors() { + return executors_.size(); + } + bool IsExecutorRegistered(ThreadId thread_id = kThreadId) { + return (executors_.find(thread_id) != executors_.end()); + } + + IUnknown* current_executor_; + HANDLE current_handle_; + ThreadId current_thread_id_; + MockExecutorCreator executor_creator_; + static const ThreadId kThreadId; + static const HWND kWindowHwnd; + static const CeeeWindowHandle kWindowHandle; + + // Publicize these protected static const values. + using ExecutorsManager::kTerminationHandleIndexOffset; + using ExecutorsManager::kUpdateHandleIndexOffset; + + std::vector<std::pair<HANDLE, const char*>> opened_handles_; +}; + +const TestingExecutorsManager::ThreadId TestingExecutorsManager::kThreadId = 42; +const HWND TestingExecutorsManager::kWindowHwnd = reinterpret_cast<HWND>(93); +const CeeeWindowHandle TestingExecutorsManager::kWindowHandle + = reinterpret_cast<CeeeWindowHandle>(kWindowHwnd); + +class ExecutorsManagerTests: public testing::Test { + public: + ExecutorsManagerTests() : initial_handle_count_(0) { + } + // This is not reliable enough, but we keep it here so that we can enable + // it from time to time to make sure everything is OK. + /* + virtual void SetUp() { + // This is called from the threadproc and changes the handles count if we + // don't call it first. + ASSERT_HRESULT_SUCCEEDED(::CoInitializeEx(NULL, COINIT_MULTITHREADED)); + + // Acquire the number of handles in the process. + ::GetProcessHandleCount(::GetCurrentProcess(), &initial_handle_count_); + printf("Initial Count: %d\n", initial_handle_count_); + } + + virtual void TearDown() { + // Make sure the number of handles in the process didn't change. + DWORD new_handle_count = 0; + ::GetProcessHandleCount(::GetCurrentProcess(), &new_handle_count); + EXPECT_EQ(initial_handle_count_, new_handle_count); + printf("Final Count: %d\n", new_handle_count); + + ::CoUninitialize(); + } + */ + private: + DWORD initial_handle_count_; +}; + +TEST_F(ExecutorsManagerTests, RegisterExecutor) { + testing::LogDisabler no_dchecks; + TestingExecutorsManager executors_manager; + + // Invalid arguments. + EXPECT_EQ(E_INVALIDARG, executors_manager.RegisterWindowExecutor( + TestingExecutorsManager::kThreadId, NULL)); + + MockExecutor executor; + EXPECT_EQ(E_INVALIDARG, executors_manager.RegisterWindowExecutor( + TestingExecutorsManager::kThreadId, &executor)); + EXPECT_EQ(0, executor.ref_count()); + + executors_manager.AddPendingRegistration( + executors_manager.GetNewHandle("PendingRegistration1")); + EXPECT_EQ(E_INVALIDARG, executors_manager.RegisterWindowExecutor( + TestingExecutorsManager::kThreadId, NULL)); + + // Dead thread... + testing::MockKernel32 kernel32; + EXPECT_CALL(kernel32, OpenThread(SYNCHRONIZE, FALSE, + TestingExecutorsManager::kThreadId)).WillOnce(Return((HANDLE)NULL)); + EXPECT_EQ(S_FALSE, executors_manager.RegisterWindowExecutor( + TestingExecutorsManager::kThreadId, &executor)); + EXPECT_EQ(0, executor.ref_count()); + + // Failed, already registered. + executors_manager.FakeRegisterExecutor( + executors_manager.GetNewHandle("FakeExec1"), &executor); + EXPECT_EQ(1, executors_manager.GetNumExecutors()); + EXPECT_TRUE(executors_manager.IsExecutorRegistered()); + EXPECT_GT(executor.ref_count(), 0UL); + ULONG previous_ref_count = executor.ref_count(); + + EXPECT_EQ(E_UNEXPECTED, executors_manager.RegisterWindowExecutor( + TestingExecutorsManager::kThreadId, &executor)); + EXPECT_EQ(previous_ref_count, executor.ref_count()); + + // Cleanup. + EXPECT_EQ(S_OK, executors_manager.RemoveExecutor( + TestingExecutorsManager::kThreadId)); + EXPECT_EQ(S_FALSE, executors_manager.RemoveExecutor( + TestingExecutorsManager::kThreadId)); + EXPECT_EQ(0, executors_manager.GetNumExecutors()); + EXPECT_FALSE(executors_manager.IsExecutorRegistered()); + EXPECT_EQ(0, executor.ref_count()); + + // Success!!! + EXPECT_CALL(kernel32, OpenThread(SYNCHRONIZE, FALSE, + TestingExecutorsManager::kThreadId)).WillOnce( + Return(executors_manager.GetNewHandle("OpenThread"))); + EXPECT_EQ(S_OK, executors_manager.RegisterWindowExecutor( + TestingExecutorsManager::kThreadId, &executor)); + EXPECT_EQ(1, executors_manager.GetNumExecutors()); + EXPECT_TRUE(executors_manager.IsExecutorRegistered()); + EXPECT_GT(executor.ref_count(), 0UL); + + // Make sure we properly cleanup. + executors_manager.RemovePendingRegistration(); + EXPECT_EQ(S_OK, executors_manager.Terminate()); + EXPECT_EQ(0, executor.ref_count()); +} + +TEST_F(ExecutorsManagerTests, RegisterTabExecutor) { + testing::LogDisabler no_dchecks; + TestingExecutorsManager executors_manager; + + // Already registered. + MockExecutor executor; + HANDLE new_handle = executors_manager.GetNewHandle("FakeExec1"); + executors_manager.FakeRegisterExecutor(new_handle, &executor); + EXPECT_EQ(1, executors_manager.GetNumExecutors()); + EXPECT_TRUE(executors_manager.IsExecutorRegistered()); + EXPECT_GT(executor.ref_count(), 0UL); + ULONG previous_ref_count = executor.ref_count(); + + EXPECT_EQ(S_OK, executors_manager.RegisterTabExecutor( + TestingExecutorsManager::kThreadId, &executor)); + EXPECT_EQ(previous_ref_count, executor.ref_count()); + + // Cleanup. + EXPECT_EQ(S_OK, executors_manager.RemoveExecutor( + TestingExecutorsManager::kThreadId)); + EXPECT_EQ(S_FALSE, executors_manager.RemoveExecutor( + TestingExecutorsManager::kThreadId)); + EXPECT_EQ(0, executors_manager.GetNumExecutors()); + EXPECT_FALSE(executors_manager.IsExecutorRegistered()); + EXPECT_EQ(0, executor.ref_count()); + + // Dead thread... + testing::MockKernel32 kernel32; + EXPECT_CALL(kernel32, OpenThread(SYNCHRONIZE, FALSE, + TestingExecutorsManager::kThreadId)).WillOnce(Return((HANDLE)NULL)); + EXPECT_EQ(E_UNEXPECTED, executors_manager.RegisterTabExecutor( + TestingExecutorsManager::kThreadId, &executor)); + EXPECT_EQ(0, executor.ref_count()); + + // Success!!! + new_handle = executors_manager.GetNewHandle("OpenThread"); + EXPECT_CALL(kernel32, OpenThread(SYNCHRONIZE, FALSE, + TestingExecutorsManager::kThreadId)).WillOnce(Return(new_handle)); + EXPECT_EQ(S_OK, executors_manager.RegisterTabExecutor( + TestingExecutorsManager::kThreadId, &executor)); + EXPECT_EQ(1, executors_manager.GetNumExecutors()); + EXPECT_TRUE(executors_manager.IsExecutorRegistered()); + EXPECT_GT(executor.ref_count(), 0UL); + + // Make sure we properly cleanup. + EXPECT_EQ(S_OK, executors_manager.Terminate()); + EXPECT_EQ(0, executor.ref_count()); + EXPECT_EQ(0, executors_manager.GetNumExecutors()); +} + +TEST_F(ExecutorsManagerTests, GetExecutor) { + testing::LogDisabler no_dchecks; + TestingExecutorsManager executors_manager; + + // Already in the map. + MockExecutor executor; + executors_manager.FakeRegisterExecutor( + executors_manager.GetNewHandle("FakeExec1"), &executor); + EXPECT_GT(executor.ref_count(), 0UL); + ULONG previous_ref_count = executor.ref_count(); + + IUnknown* result_executor = NULL; + EXPECT_CALL(executor, QueryInterface(_, _)).WillOnce( + DoAll(SetArgumentPointee<1>(&executor), Return(S_OK))); + EXPECT_EQ(S_OK, executors_manager.GetExecutor( + TestingExecutorsManager::kThreadId, TestingExecutorsManager::kWindowHwnd, + IID_IUnknown, reinterpret_cast<void**>(&result_executor))); + EXPECT_EQ(&executor, result_executor); + // Since our mocked QI doesn't addref, ref count shouldn't have changed. + EXPECT_EQ(previous_ref_count, executor.ref_count()); + + // Cleanup. + EXPECT_EQ(S_OK, executors_manager.RemoveExecutor( + TestingExecutorsManager::kThreadId)); + // Since our mocked QI doesn't addref, we should be back to 0 now. + EXPECT_EQ(0, executor.ref_count()); + + // Fail Executor creation. + result_executor = NULL; + EXPECT_CALL(executors_manager.executor_creator_, + CreateWindowExecutor(TestingExecutorsManager::kThreadId, + TestingExecutorsManager::kWindowHandle)). + WillOnce(Return(E_FAIL)); + EXPECT_CALL(executors_manager.executor_creator_, Release()). + WillOnce(Return(1)); + EXPECT_EQ(E_FAIL, executors_manager.GetExecutor( + TestingExecutorsManager::kThreadId, TestingExecutorsManager::kWindowHwnd, + IID_IUnknown, reinterpret_cast<void**>(&result_executor))); + EXPECT_EQ(NULL, result_executor); + + // Fail registration. + EXPECT_CALL(executors_manager, WaitForSingleObject(_, _)). + WillOnce(Return(WAIT_OBJECT_0)); + EXPECT_CALL(executors_manager.executor_creator_, + CreateWindowExecutor(TestingExecutorsManager::kThreadId, + TestingExecutorsManager::kWindowHandle)). + WillOnce(Return(S_OK)); + EXPECT_CALL(executors_manager.executor_creator_, + Teardown(TestingExecutorsManager::kThreadId)).WillOnce(Return(S_OK)); + EXPECT_CALL(executors_manager.executor_creator_, Release()). + WillOnce(Return(1)); + EXPECT_EQ(E_UNEXPECTED, executors_manager.GetExecutor( + TestingExecutorsManager::kThreadId, TestingExecutorsManager::kWindowHwnd, + IID_IUnknown, reinterpret_cast<void**>(&result_executor))); + EXPECT_EQ(NULL, result_executor); + + // Pending registration fail. + EXPECT_CALL(executors_manager, WaitForSingleObject(_, _)). + WillOnce(Return(WAIT_OBJECT_0)); + executors_manager.AddPendingRegistration( + executors_manager.GetNewHandle("PendingReg1")); + + EXPECT_EQ(E_UNEXPECTED, executors_manager.GetExecutor( + TestingExecutorsManager::kThreadId, TestingExecutorsManager::kWindowHwnd, + IID_IUnknown, reinterpret_cast<void**>(&result_executor))); + EXPECT_EQ(NULL, result_executor); + + // Success Creating new. + executors_manager.current_executor_ = &executor; + EXPECT_CALL(executors_manager, WaitForSingleObject(_, _)). + WillOnce(DoAll(InvokeWithoutArgs(&executors_manager, + &TestingExecutorsManager::RegisterExecutorOnWait), + Return(WAIT_OBJECT_0))); + EXPECT_CALL(executors_manager.executor_creator_, + CreateWindowExecutor(TestingExecutorsManager::kThreadId, + TestingExecutorsManager::kWindowHandle)). + WillOnce(Return(S_OK)); + EXPECT_CALL(executors_manager.executor_creator_, + Teardown(TestingExecutorsManager::kThreadId)).WillOnce(Return(S_OK)); + EXPECT_CALL(executors_manager.executor_creator_, Release()). + WillOnce(Return(1)); + EXPECT_CALL(executor, QueryInterface(_, _)).WillOnce( + DoAll(SetArgumentPointee<1>(&executor), Return(S_OK))); + EXPECT_EQ(S_OK, executors_manager.GetExecutor( + TestingExecutorsManager::kThreadId, TestingExecutorsManager::kWindowHwnd, + IID_IUnknown, reinterpret_cast<void**>(&result_executor))); + EXPECT_EQ(&executor, result_executor); + EXPECT_GT(executor.ref_count(), 0UL); + + // Cleanup. + EXPECT_EQ(S_OK, executors_manager.RemoveExecutor( + TestingExecutorsManager::kThreadId)); + // Since our mocked QI doesn't addref, we should be back to 0 now. + EXPECT_EQ(0, executor.ref_count()); + + // Success with pending registration. + EXPECT_CALL(executors_manager, WaitForSingleObject(_, _)). + WillOnce(DoAll(InvokeWithoutArgs(&executors_manager, + &TestingExecutorsManager::RegisterExecutorOnWait), + Return(WAIT_OBJECT_0))); + executors_manager.AddPendingRegistration( + executors_manager.GetNewHandle("PendingReg2")); + EXPECT_CALL(executor, QueryInterface(_, _)).WillOnce( + DoAll(SetArgumentPointee<1>(&executor), Return(S_OK))); + EXPECT_EQ(S_OK, executors_manager.GetExecutor( + TestingExecutorsManager::kThreadId, TestingExecutorsManager::kWindowHwnd, + IID_IUnknown, reinterpret_cast<void**>(&result_executor))); + EXPECT_EQ(&executor, result_executor); + EXPECT_GT(executor.ref_count(), 0UL); + + // Make sure we properly cleanup. + EXPECT_EQ(S_OK, executors_manager.Terminate()); + // Since our mocked QI doesn't addref, we should be back to 0 now. + EXPECT_EQ(0, executor.ref_count()); + EXPECT_EQ(0, executors_manager.GetNumExecutors()); +} + +TEST_F(ExecutorsManagerTests, RemoveExecutor) { + TestingExecutorsManager executors_manager; + + // Nothing to remove... + EXPECT_EQ(0, executors_manager.GetNumExecutors()); + EXPECT_EQ(S_FALSE, executors_manager.RemoveExecutor(1)); + EXPECT_EQ(0, executors_manager.GetNumExecutors()); + + // Success. + MockExecutor executor1; + executors_manager.FakeRegisterExecutor( + executors_manager.GetNewHandle("Fake0"), &executor1); + EXPECT_EQ(1, executors_manager.GetNumExecutors()); + EXPECT_GT(executor1.ref_count(), 0UL); + EXPECT_EQ(S_OK, executors_manager.RemoveExecutor( + TestingExecutorsManager::kThreadId)); + EXPECT_EQ(0, executors_manager.GetNumExecutors()); + EXPECT_EQ(0, executor1.ref_count()); + EXPECT_FALSE(executors_manager.IsExecutorRegistered()); + EXPECT_EQ(S_FALSE, executors_manager.RemoveExecutor( + TestingExecutorsManager::kThreadId)); + + // Multiple values, removed one at a time... + MockExecutor executor2; + MockExecutor executor3; + executors_manager.FakeRegisterExecutor( + executors_manager.GetNewHandle("Fake1"), &executor1, 1); + EXPECT_EQ(1, executors_manager.GetNumExecutors()); + EXPECT_GT(executor1.ref_count(), 0UL); + executors_manager.FakeRegisterExecutor( + executors_manager.GetNewHandle("Fake2"), &executor2, 2); + EXPECT_EQ(2, executors_manager.GetNumExecutors()); + EXPECT_GT(executor2.ref_count(), 0UL); + executors_manager.FakeRegisterExecutor( + executors_manager.GetNewHandle("Fake3"), &executor3, 3); + EXPECT_EQ(3, executors_manager.GetNumExecutors()); + EXPECT_GT(executor3.ref_count(), 0UL); + EXPECT_EQ(S_OK, executors_manager.RemoveExecutor(2)); + EXPECT_EQ(2, executors_manager.GetNumExecutors()); + EXPECT_FALSE(executors_manager.IsExecutorRegistered(2)); + EXPECT_TRUE(executors_manager.IsExecutorRegistered(1)); + EXPECT_TRUE(executors_manager.IsExecutorRegistered(3)); + EXPECT_GT(executor1.ref_count(), 0UL); + EXPECT_EQ(0, executor2.ref_count()); + EXPECT_GT(executor3.ref_count(), 0UL); + + EXPECT_EQ(S_OK, executors_manager.RemoveExecutor(3)); + EXPECT_EQ(1, executors_manager.GetNumExecutors()); + EXPECT_FALSE(executors_manager.IsExecutorRegistered(3)); + EXPECT_TRUE(executors_manager.IsExecutorRegistered(1)); + EXPECT_GT(executor1.ref_count(), 0UL); + EXPECT_EQ(0, executor3.ref_count()); + + EXPECT_EQ(S_OK, executors_manager.RemoveExecutor(1)); + EXPECT_EQ(0, executors_manager.GetNumExecutors()); + EXPECT_FALSE(executors_manager.IsExecutorRegistered(1)); + EXPECT_EQ(0, executor1.ref_count()); +} + +// Since the returned handle is a duplicate, making sure we have the +// appropriate handle is a little tricky. +void VerifyIsSameHandle(HANDLE handle1, HANDLE handle2) { + // First make sure neither is set. + EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(handle1, 0)); + EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(handle2, 0)); + + // Now check that setting one also sets the other. + ::SetEvent(handle1); + EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(handle1, 0)); + EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(handle2, 0)); + + // Manual reset. + ::ResetEvent(handle1); + EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(handle1, 0)); + EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(handle2, 0)); +} + +TEST_F(ExecutorsManagerTests, GetThreadHandles) { + TestingExecutorsManager executors_manager; + + // We need to work with invalid, and one other valid value for thread info. + static const TestingExecutorsManager::ThreadId kInvalidThreadId = 0xF0F0F0F0; + static const TestingExecutorsManager::ThreadId kThreadIdA = 0xAAAAAAAA; + static const TestingExecutorsManager::ThreadId kThreadIdB = 0xBBBBBBBB; + + // As for the thread proc code, we work with arrays of handles and thread ids + // on the stack. + CHandle thread_handles[MAXIMUM_WAIT_OBJECTS]; + TestingExecutorsManager::ThreadId thread_ids[MAXIMUM_WAIT_OBJECTS]; + + // Make sure the arrays are not touched. + thread_ids[0] = kInvalidThreadId; + size_t num_threads = executors_manager.GetThreadHandles( + thread_handles, thread_ids, MAXIMUM_WAIT_OBJECTS); + EXPECT_EQ(0, num_threads); + EXPECT_EQ(static_cast<HANDLE>(NULL), thread_handles[0]); + EXPECT_EQ(kInvalidThreadId, thread_ids[0]); + + // Make sure only the first index will get affected. + thread_ids[1] = kInvalidThreadId; + HANDLE thread_handle_a = executors_manager.GetNewHandle("A"); + executors_manager.FakeRegisterExecutor(thread_handle_a, NULL, kThreadIdA); + num_threads = executors_manager.GetThreadHandles(thread_handles, thread_ids, + MAXIMUM_WAIT_OBJECTS); + EXPECT_EQ(1, num_threads); + VerifyIsSameHandle(thread_handle_a, thread_handles[0]); + EXPECT_EQ(kThreadIdA, thread_ids[0]); + EXPECT_EQ(static_cast<HANDLE>(NULL), thread_handles[1]); + EXPECT_EQ(kInvalidThreadId, thread_ids[1]); + // Need to close active handles since GetThreadHandles expect NULL m_h. + thread_handles[0].Close(); + + // Now expect 2 values and make sure the third one isn't affected. + thread_ids[0] = kInvalidThreadId; + thread_ids[2] = kInvalidThreadId; // We asserted above that index 1 is OK. + HANDLE thread_handle_b = executors_manager.GetNewHandle("B"); + executors_manager.FakeRegisterExecutor(thread_handle_b, NULL, kThreadIdB); + num_threads = executors_manager.GetThreadHandles(thread_handles, thread_ids, + MAXIMUM_WAIT_OBJECTS); + EXPECT_EQ(2, num_threads); + // We can't be sure of the order in which the map returns the elements. + if (thread_ids[0] == kThreadIdA) { + VerifyIsSameHandle(thread_handle_a, thread_handles[0]); + EXPECT_EQ(kThreadIdB, thread_ids[1]); + VerifyIsSameHandle(thread_handle_b, thread_handles[1]); + } else { + VerifyIsSameHandle(thread_handle_b, thread_handles[0]); + EXPECT_EQ(kThreadIdB, thread_ids[0]); + VerifyIsSameHandle(thread_handle_a, thread_handles[1]); + EXPECT_EQ(kThreadIdA, thread_ids[1]); + } + EXPECT_EQ(static_cast<HANDLE>(NULL), thread_handles[2]); + EXPECT_EQ(kInvalidThreadId, thread_ids[2]); + thread_handles[0].Close(); + thread_handles[1].Close(); + + // Now remove threads and make sure they won't be returned again. + thread_ids[0] = kInvalidThreadId; + thread_ids[1] = kInvalidThreadId; // Asserted index 2 invalid already. + + EXPECT_EQ(S_OK, executors_manager.RemoveExecutor(kThreadIdA)); + num_threads = executors_manager.GetThreadHandles(thread_handles, thread_ids, + MAXIMUM_WAIT_OBJECTS); + EXPECT_EQ(1, num_threads); + VerifyIsSameHandle(thread_handle_b, thread_handles[0]); + EXPECT_EQ(kThreadIdB, thread_ids[0]); + EXPECT_EQ(static_cast<HANDLE>(NULL), thread_handles[1]); + EXPECT_EQ(kInvalidThreadId, thread_ids[1]); + thread_handles[0].Close(); + + // Now remove the last one... + thread_ids[0] = kInvalidThreadId; + EXPECT_EQ(S_OK, executors_manager.RemoveExecutor(kThreadIdB)); + num_threads = executors_manager.GetThreadHandles(thread_handles, thread_ids, + MAXIMUM_WAIT_OBJECTS); + EXPECT_EQ(0, num_threads); + EXPECT_EQ(static_cast<HANDLE>(NULL), thread_handles[0]); + EXPECT_EQ(kInvalidThreadId, thread_ids[0]); +} + +TEST_F(ExecutorsManagerTests, ThreadProc) { + testing::LogDisabler no_logs; + TestingExecutorsManager executors_manager; + + // Register an object and make sure we wake up to update our list. + MockExecutor executor1; + executors_manager.current_executor_ = &executor1; + executors_manager.current_handle_ = executors_manager.GetNewHandle("1"); + EXPECT_CALL(executors_manager, WaitForMultipleObjects(2, _, FALSE, INFINITE)). + WillOnce(DoAll(InvokeWithoutArgs(&executors_manager, + &TestingExecutorsManager::RegisterExecutorOnWait), + Return(TestingExecutorsManager::kUpdateHandleIndexOffset))); + EXPECT_CALL(executors_manager, WaitForMultipleObjects(3, _, FALSE, INFINITE)). + WillOnce(Return( + TestingExecutorsManager::kTerminationHandleIndexOffset + 1)); + EXPECT_EQ(1, executors_manager.CallThreadProc()); + + EXPECT_TRUE(executors_manager.IsExecutorRegistered()); + EXPECT_EQ(1, executors_manager.GetNumExecutors()); + EXPECT_GT(executor1.ref_count(), 0UL); + + // Add another destination thread. + static const TestingExecutorsManager::ThreadId kOtherThreadId = 14; + MockExecutor executor2; + executors_manager.current_executor_ = &executor2; + executors_manager.current_handle_ = executors_manager.GetNewHandle("2"); + executors_manager.current_thread_id_ = kOtherThreadId; + // Number of handles and return index offset needs to be increased by 1 + // because we already have a destination thread in our map. + EXPECT_CALL(executors_manager, WaitForMultipleObjects(3, _, FALSE, INFINITE)). + WillOnce(DoAll(InvokeWithoutArgs(&executors_manager, + &TestingExecutorsManager::RegisterExecutorOnWait), Return( + TestingExecutorsManager::kUpdateHandleIndexOffset + 1))).WillOnce( + Return(TestingExecutorsManager::kTerminationHandleIndexOffset + 1)); + // Now that we have another destination thread in the map, + // we wait for 4 handles. We return 1 to remove one of them, and wait will + // be called with three handles again, the mock above will be called for the + // second time with 3 handles and will return the proper termination offset. + EXPECT_CALL(executors_manager, WaitForMultipleObjects(4, _, FALSE, INFINITE)). + WillOnce(Return(1)); + EXPECT_EQ(1, executors_manager.CallThreadProc()); + EXPECT_EQ(1, executors_manager.GetNumExecutors()); + // We can't tell which one is at position 1 in the array returned by + // GetThreadHandles since they are fetched from an unsorted map. + EXPECT_TRUE(executors_manager.IsExecutorRegistered(kOtherThreadId) || + executors_manager.IsExecutorRegistered( + TestingExecutorsManager::kThreadId)); + if (executors_manager.IsExecutorRegistered(kOtherThreadId)) { + // We kept the other executor in the map. + EXPECT_EQ(0, executor1.ref_count()); + EXPECT_GT(executor2.ref_count(), 0UL); + } else { + EXPECT_GT(executor1.ref_count(), 0UL); + EXPECT_EQ(0, executor2.ref_count()); + } + + // Cleanup. + executors_manager.Terminate(); + EXPECT_EQ(0, executor1.ref_count()); + EXPECT_EQ(0, executor2.ref_count()); + EXPECT_EQ(0, executors_manager.GetNumExecutors()); + + // Test the thread failure path. + EXPECT_CALL(executors_manager, WaitForMultipleObjects(2, _, FALSE, INFINITE)). + WillOnce(Return(WAIT_FAILED)); + EXPECT_EQ(1, executors_manager.CallThreadProc()); +} + +} // namespace |