Chromium Code Reviews| Index: handler/win/registrar_test.cc |
| diff --git a/handler/win/registrar_test.cc b/handler/win/registrar_test.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..f509977da5dcf62ad8c26919812dcf86eb0ee5a0 |
| --- /dev/null |
| +++ b/handler/win/registrar_test.cc |
| @@ -0,0 +1,188 @@ |
| +// Copyright 2015 The Crashpad Authors. All rights reserved. |
| +// |
| +// Licensed under the Apache License, Version 2.0 (the "License"); |
| +// you may not use this file except in compliance with the License. |
| +// You may obtain a copy of the License at |
| +// |
| +// http://www.apache.org/licenses/LICENSE-2.0 |
| +// |
| +// Unless required by applicable law or agreed to in writing, software |
| +// distributed under the License is distributed on an "AS IS" BASIS, |
| +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| +// See the License for the specific language governing permissions and |
| +// limitations under the License. |
| + |
| +#include "handler/win/registrar.h" |
| + |
| +#include <windows.h> |
| + |
| +#include <vector> |
| + |
| +#include "base/basictypes.h" |
| +#include "base/logging.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "gtest/gtest.h" |
| +#include "test/win/win_child_process.h" |
| +#include "util/win/scoped_handle.h" |
| + |
| +namespace crashpad { |
| +namespace test { |
| +namespace { |
| + |
| +// Reads a `HANDLE` value from |handle|. Assume sender has same bitness as |
| +// reader. CHECKs upon failure. |
| +ScopedKernelHANDLE ReadHandle(HANDLE handle) { |
| + HANDLE value = nullptr; |
| + DWORD bytes_read = 0; |
|
scottmg
2015/06/29 20:29:05
Replace 36-38 with CheckReadFile() from util/file/
|
| + CHECK(::ReadFile(handle, &value, sizeof(value), &bytes_read, nullptr)); |
| + CHECK_EQ(sizeof(value), bytes_read); |
| + return ScopedKernelHANDLE(value); |
| +} |
| + |
| +// Writes |value| to |handle|. Logs a GTest failure upon failure. |
| +void WriteHandle(HANDLE handle, HANDLE value) { |
| + DWORD bytes_written = 0; |
|
scottmg
2015/06/29 20:29:05
And CheckedWriteFile here.
|
| + EXPECT_TRUE( |
| + ::WriteFile(handle, &value, sizeof(value), &bytes_written, nullptr)); |
| + EXPECT_EQ(sizeof(value), bytes_written); |
| +} |
| + |
| +// Implements a client process that may be registered. Reads the handle pair |
| +// from its parent and proceeds to request a dump. After receiving notice that |
| +// the dump completed, exits with 0. |
| +class ClientProcess : public test::WinChildProcess { |
| + public: |
| + ClientProcess() {} |
| + |
| + private: |
| + int Run() override { |
| + // In the worst case, this process will exit due to a broken pipe if the |
| + // parent process disappears. |
| + ScopedKernelHANDLE request_report_event = ReadHandle(ReadPipeHandle()); |
| + ScopedKernelHANDLE report_complete_event = ReadHandle(ReadPipeHandle()); |
| + PCHECK(SetEvent(request_report_event.get())); |
| + CHECK_EQ(WAIT_OBJECT_0, |
| + WaitForSingleObject(report_complete_event.get(), INFINITE)); |
| + return 0; |
| + } |
| + |
| + DISALLOW_COPY_AND_ASSIGN(ClientProcess); |
| +}; |
| + |
| +// Handles crash dump requests, logging the requesting client's PID. |
| +class MockDelegate : Registrar::Delegate { |
| + public: |
| + // Instantiates a Registrar::Delegate that logs requesting clients' PIDs to |
| + // |process_ids|. |
| + explicit MockDelegate(std::vector<DWORD>* process_ids) |
| + : process_ids_(process_ids) {} |
| + |
| + // Registrar::Delegate: |
| + void GenerateReportForClient(HANDLE client_process) override { |
| + process_ids_->push_back(GetProcessId(client_process)); |
| + } |
| + |
| + private: |
| + std::vector<DWORD>* process_ids_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(MockDelegate); |
| +}; |
| + |
| +// Duplicates |handle| with PROCESS_ALL_ACCESS. Logs a GTest failure upon |
| +// failure. |
| +ScopedKernelHANDLE DuplicateProcessHandleWithAllAccess(HANDLE handle) { |
| + HANDLE result = nullptr; |
| + if (!DuplicateHandle(GetCurrentProcess(), |
| + handle, |
| + GetCurrentProcess(), |
| + &result, |
| + PROCESS_ALL_ACCESS, |
| + false, |
| + 0)) { |
| + PLOG(ERROR) << "DuplicateHandle"; |
| + ADD_FAILURE() << "DuplicateHandle"; |
| + return ScopedKernelHANDLE(); |
| + } |
| + return ScopedKernelHANDLE(result); |
| +} |
| + |
| +class RegistrarTest : public testing::Test { |
| + public: |
| + RegistrarTest() |
| + : registrar_( |
| + scoped_ptr<Registrar::Delegate>(new MockDelegate(&process_ids_))) {} |
| + |
| + protected: |
| + Registrar& registrar() { return registrar_; } |
| + std::vector<DWORD>& triggered_process_ids() { return process_ids_; } |
| + |
| + private: |
| + std::vector<DWORD> process_ids_; |
| + Registrar registrar_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(RegistrarTest); |
| +}; |
| + |
| +// Exercises registration, report request handling, and process termination. |
| +TEST_F(RegistrarTest, RegisterProcess) { |
| + test::WinChildProcess::EntryPoint<ClientProcess>(); |
| + |
| + struct ProcessState { |
| + scoped_ptr<test::WinChildProcess::Handles> child_handles; |
| + HANDLE request_handle; |
| + HANDLE done_handle; |
| + } process_states[3]; |
| + |
| + for (int i = 0; i < arraysize(process_states); ++i) { |
| + process_states[i].child_handles = test::WinChildProcess::Launch(); |
| + ASSERT_TRUE(registrar().RegisterProcess( |
| + DuplicateProcessHandleWithAllAccess( |
| + process_states[i].child_handles->process.get()), |
| + &process_states[i].request_handle, |
| + &process_states[i].done_handle)); |
| + } |
| + |
| + // Kill a process. This should not cause any hiccups for the Registrar. |
| + ASSERT_TRUE( |
| + TerminateProcess(process_states[0].child_handles->process.get(), 0)); |
| + |
| + // Send the event pairs over to each of the child processes, and verify that |
| + // they are able to trigger a report request, that the request is handed to |
| + // our delegate, and that the "report complete" event is triggered and may be |
| + // detected by the client. |
| + for (int i = 1; i < arraysize(process_states); ++i) { |
| + triggered_process_ids().clear(); |
| + |
| + // Verify that the child process is still running. |
| + ASSERT_EQ( |
| + WAIT_TIMEOUT, |
| + WaitForSingleObject(process_states[i].child_handles->process.get(), 0)); |
| + |
| + // Send the handle pair along to the child, which will proceed to request a |
| + // dump and wait for the "dump complete" signal. |
| + WriteHandle(process_states[i].child_handles->write.get(), |
| + process_states[i].request_handle); |
| + WriteHandle(process_states[i].child_handles->write.get(), |
| + process_states[i].done_handle); |
| + |
| + // Wait for the process to exit with code 0 (which indicates that the "dump |
| + // complete" signal was received). |
| + ASSERT_EQ(WAIT_OBJECT_0, |
| + WaitForSingleObject( |
| + process_states[i].child_handles->process.get(), INFINITE)); |
| + DWORD exit_code = 0; |
| + ASSERT_TRUE(GetExitCodeProcess( |
| + process_states[i].child_handles->process.get(), &exit_code)); |
| + ASSERT_EQ(0, exit_code); |
| + |
| + // Verify that our delegate was invoked, with the correct process handle. |
| + ASSERT_EQ(1, triggered_process_ids().size()); |
| + ASSERT_EQ(GetProcessId(process_states[i].child_handles->process.get()), |
| + triggered_process_ids()[0]); |
| + } |
| +} |
| + |
| +} // namespace |
| +} // namespace test |
| +} // namespace crashpad |
| + |