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 |
+ |