Index: components/browser_watcher/watcher_win_unittest.cc |
diff --git a/components/browser_watcher/watcher_win_unittest.cc b/components/browser_watcher/watcher_win_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2d0dcb86e6f62141b1ceec42276637a8c5cc6593 |
--- /dev/null |
+++ b/components/browser_watcher/watcher_win_unittest.cc |
@@ -0,0 +1,212 @@ |
+// Copyright (c) 2014 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 "components/browser_watcher/watcher_win.h" |
+ |
+#include "base/command_line.h" |
+#include "base/process/kill.h" |
+#include "base/strings/string_util.h" |
+#include "base/strings/stringprintf.h" |
+#include "base/synchronization/waitable_event.h" |
+#include "base/test/multiprocess_test.h" |
+#include "base/test/test_reg_util_win.h" |
+#include "base/threading/platform_thread.h" |
+#include "base/time/time.h" |
+#include "base/win/scoped_handle.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+#include "testing/multiprocess_func_list.h" |
+ |
+namespace browser_watcher { |
+ |
+namespace { |
+ |
+const wchar_t kRegistryPath[] = L"Software\\BrowserWatcherTest"; |
+ |
+MULTIPROCESS_TEST_MAIN(Sleeper) { |
+ // Sleep forever - the test harness will kill this process to give it an |
+ // exit code. |
+ base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(INFINITE)); |
+ return 1; |
+} |
+ |
+class ScopedSleeperProcess { |
+ public: |
+ ScopedSleeperProcess() : |
+ process_(base::kNullProcessHandle), is_killed_(false) { |
+ } |
+ |
+ ~ScopedSleeperProcess() { |
+ if (process_ != base::kNullProcessHandle) { |
+ base::KillProcess(process_, -1, true); |
+ base::CloseProcessHandle(process_); |
+ } |
+ } |
+ |
+ void Launch() { |
+ ASSERT_EQ(base::kNullProcessHandle, process_); |
+ |
+ base::CommandLine cmd_line(base::GetMultiProcessTestChildBaseCommandLine()); |
+ base::LaunchOptions options; |
+ options.start_hidden = true; |
+ process_ = base::SpawnMultiProcessTestChild("Sleeper", cmd_line, options); |
+ process_id_ = base::GetProcId(process_); |
+ ASSERT_NE(base::kNullProcessHandle, process_); |
+ } |
+ |
+ void Kill(int exit_code, bool wait) { |
+ ASSERT_NE(process_, base::kNullProcessHandle); |
+ ASSERT_FALSE(is_killed_); |
+ ASSERT_TRUE(base::KillProcess(process_, exit_code, wait)); |
+ is_killed_ = true; |
+ } |
+ |
+ base::ProcessHandle process() const { return process_; } |
+ base::ProcessId process_id() const { return process_id_; } |
+ void set_process(base::ProcessHandle process) { process_ = process; } |
+ |
+ private: |
+ base::ProcessHandle process_; |
+ base::ProcessId process_id_; |
+ bool is_killed_; |
+}; |
+ |
+class BrowserWatcherTest : public testing::Test { |
+ public: |
+ typedef testing::Test Super; |
+ |
+ static const int kExitCode = 0xCAFEBABE; |
+ |
+ BrowserWatcherTest() : |
+ cmd_line_(base::CommandLine::NO_PROGRAM), |
+ process_(base::kNullProcessHandle) { |
+ } |
+ |
+ virtual void SetUp() OVERRIDE { |
+ Super::SetUp(); |
+ |
+ override_manager_.OverrideRegistry(HKEY_CURRENT_USER); |
+ } |
+ |
+ virtual void TearDown() OVERRIDE { |
+ if (process_ != base::kNullProcessHandle) { |
+ base::CloseProcessHandle(process_); |
+ process_ = base::kNullProcessHandle; |
+ } |
+ |
+ Super::TearDown(); |
+ } |
+ |
+ void OpenSelfWithAccess(uint32 access) { |
+ ASSERT_EQ(base::kNullProcessHandle, process_); |
+ ASSERT_TRUE(base::OpenProcessHandleWithAccess( |
+ base::GetCurrentProcId(), access, &process_)); |
+ } |
+ |
+ void VerifyWroteExitCode(base::ProcessId proc_id, int exit_code) { |
+ base::win::RegistryValueIterator it( |
+ HKEY_CURRENT_USER, kRegistryPath); |
+ |
+ ASSERT_EQ(1, it.ValueCount()); |
+ base::win::RegKey key(HKEY_CURRENT_USER, |
+ kRegistryPath, |
+ KEY_QUERY_VALUE); |
+ |
+ // The value name should encode the process id at the start. |
+ EXPECT_TRUE(StartsWith(it.Name(), |
+ base::StringPrintf(L"%d-", proc_id), |
+ false)); |
+ DWORD value = 0; |
+ ASSERT_EQ(ERROR_SUCCESS, key.ReadValueDW(it.Name(), &value)); |
+ ASSERT_EQ(exit_code, value); |
+ } |
+ |
+ protected: |
+ base::CommandLine cmd_line_; |
+ base::ProcessHandle process_; |
+ registry_util::RegistryOverrideManager override_manager_; |
+}; |
+ |
+} // namespace |
+ |
+TEST_F(BrowserWatcherTest, ExitCodeWatcherInvalidCmdLineFailsInit) { |
+ ExitCodeWatcher watcher(kRegistryPath); |
+ |
+ // An empty command line should fail. |
+ EXPECT_FALSE(watcher.ParseArguments(cmd_line_)); |
+ |
+ // A non-numeric parent-handle argument should fail. |
+ cmd_line_.AppendSwitchASCII("parent-handle", "asdf"); |
+ EXPECT_FALSE(watcher.ParseArguments(cmd_line_)); |
+} |
+ |
+TEST_F(BrowserWatcherTest, ExitCodeWatcherInvalidHandleFailsInit) { |
+ ExitCodeWatcher watcher(kRegistryPath); |
+ |
+ // A waitable event has a non process-handle. |
+ base::WaitableEvent event(false, false); |
+ |
+ // A non-process handle should fail. |
+ cmd_line_.AppendSwitchASCII("parent-handle", |
+ base::StringPrintf("%d", event.handle())); |
+ EXPECT_FALSE(watcher.ParseArguments(cmd_line_)); |
+} |
+ |
+TEST_F(BrowserWatcherTest, ExitCodeWatcherNoAccessHandleFailsInit) { |
+ ExitCodeWatcher watcher(kRegistryPath); |
+ |
+ // Open a SYNCHRONIZE-only handle to this process. |
+ ASSERT_NO_FATAL_FAILURE(OpenSelfWithAccess(SYNCHRONIZE)); |
+ |
+ // A process handle with insufficient access should fail. |
+ cmd_line_.AppendSwitchASCII("parent-handle", |
+ base::StringPrintf("%d", process_)); |
+ EXPECT_FALSE(watcher.ParseArguments(cmd_line_)); |
+} |
+ |
+TEST_F(BrowserWatcherTest, ExitCodeWatcherSucceedsInit) { |
+ ExitCodeWatcher watcher(kRegistryPath); |
+ |
+ // Open a handle to this process with sufficient access for the watcher. |
+ ASSERT_NO_FATAL_FAILURE( |
+ OpenSelfWithAccess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION)); |
+ |
+ // A process handle with sufficient access should succeed init. |
+ cmd_line_.AppendSwitchASCII("parent-handle", |
+ base::StringPrintf("%d", process_)); |
+ EXPECT_TRUE(watcher.ParseArguments(cmd_line_)); |
+ |
+ ASSERT_EQ(process_, watcher.process()); |
+ |
+ // The watcher takes ownership of the handle, make sure it's not |
+ // double-closed. |
+ process_ = base::kNullProcessHandle; |
+} |
+ |
+TEST_F(BrowserWatcherTest, ExitCodeWatcherOnExitedProcess) { |
+ ScopedSleeperProcess sleeper; |
+ ASSERT_NO_FATAL_FAILURE(sleeper.Launch()); |
+ |
+ ExitCodeWatcher watcher(kRegistryPath); |
+ |
+ cmd_line_.AppendSwitchASCII("parent-handle", |
+ base::StringPrintf("%d", sleeper.process())); |
+ EXPECT_TRUE(watcher.ParseArguments(cmd_line_)); |
+ ASSERT_EQ(sleeper.process(), watcher.process()); |
+ |
+ // Verify that the watcher wrote a sentinel for the process. |
+ VerifyWroteExitCode(sleeper.process_id(), STILL_ACTIVE); |
+ |
+ // Kill the sleeper, and make sure it's exited before we continue. |
+ ASSERT_NO_FATAL_FAILURE(sleeper.Kill(kExitCode, true)); |
+ |
+ // The watcher took ownership of the handle, make sure it's not |
+ // double-closed. |
+ sleeper.set_process(base::kNullProcessHandle); |
+ |
+ watcher.WaitForExit(); |
+ |
+ VerifyWroteExitCode(sleeper.process_id(), kExitCode); |
+} |
+ |
+} // namespace browser_watcher |