// 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/exit_code_watcher_win.h" #include "base/command_line.h" #include "base/process/process.h" #include "base/strings/string16.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 base::char16 kRegistryPath[] = L"Software\\ExitCodeWatcherTest"; 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() : is_killed_(false) { } ~ScopedSleeperProcess() { if (process_.IsValid()) { process_.Terminate(-1, false); EXPECT_TRUE(process_.WaitForExit(nullptr)); } } void Launch() { ASSERT_FALSE(process_.IsValid()); base::CommandLine cmd_line(base::GetMultiProcessTestChildBaseCommandLine()); base::LaunchOptions options; options.start_hidden = true; process_ = base::SpawnMultiProcessTestChild("Sleeper", cmd_line, options); ASSERT_TRUE(process_.IsValid()); } void Kill(int exit_code, bool wait) { ASSERT_TRUE(process_.IsValid()); ASSERT_FALSE(is_killed_); process_.Terminate(exit_code, false); int seen_exit_code = 0; EXPECT_TRUE(process_.WaitForExit(&seen_exit_code)); EXPECT_EQ(exit_code, seen_exit_code); is_killed_ = true; } const base::Process& process() const { return process_; } private: base::Process process_; bool is_killed_; }; class ExitCodeWatcherTest : public testing::Test { public: typedef testing::Test Super; static const int kExitCode = 0xCAFEBABE; ExitCodeWatcherTest() : cmd_line_(base::CommandLine::NO_PROGRAM) {} void SetUp() override { Super::SetUp(); override_manager_.OverrideRegistry(HKEY_CURRENT_USER); } base::Process OpenSelfWithAccess(uint32 access) { return base::Process::OpenWithAccess(base::GetCurrentProcId(), access); } 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(base::StartsWith( it.Name(), base::StringPrintf(L"%d-", proc_id), base::CompareCase::SENSITIVE)); DWORD value = 0; ASSERT_EQ(ERROR_SUCCESS, key.ReadValueDW(it.Name(), &value)); ASSERT_EQ(exit_code, value); } protected: base::CommandLine cmd_line_; registry_util::RegistryOverrideManager override_manager_; }; } // namespace TEST_F(ExitCodeWatcherTest, ExitCodeWatcherInvalidHandleFailsInit) { ExitCodeWatcher watcher(kRegistryPath); // A waitable event has a non process-handle. base::Process event(::CreateEvent(NULL, false, false, NULL)); // A non-process handle should fail. EXPECT_FALSE(watcher.Initialize(event.Pass())); } TEST_F(ExitCodeWatcherTest, ExitCodeWatcherNoAccessHandleFailsInit) { ExitCodeWatcher watcher(kRegistryPath); // Open a SYNCHRONIZE-only handle to this process. base::Process self = OpenSelfWithAccess(SYNCHRONIZE); ASSERT_TRUE(self.IsValid()); // A process handle with insufficient access should fail. EXPECT_FALSE(watcher.Initialize(self.Pass())); } TEST_F(ExitCodeWatcherTest, ExitCodeWatcherSucceedsInit) { ExitCodeWatcher watcher(kRegistryPath); // Open a handle to this process with sufficient access for the watcher. base::Process self = OpenSelfWithAccess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION); ASSERT_TRUE(self.IsValid()); // A process handle with sufficient access should succeed init. EXPECT_TRUE(watcher.Initialize(self.Pass())); } TEST_F(ExitCodeWatcherTest, ExitCodeWatcherOnExitedProcess) { ScopedSleeperProcess sleeper; ASSERT_NO_FATAL_FAILURE(sleeper.Launch()); ExitCodeWatcher watcher(kRegistryPath); EXPECT_TRUE(watcher.Initialize(sleeper.process().Duplicate())); // Verify that the watcher wrote a sentinel for the process. VerifyWroteExitCode(sleeper.process().Pid(), STILL_ACTIVE); // Kill the sleeper, and make sure it's exited before we continue. ASSERT_NO_FATAL_FAILURE(sleeper.Kill(kExitCode, true)); watcher.WaitForExit(); EXPECT_EQ(kExitCode, watcher.exit_code()); VerifyWroteExitCode(sleeper.process().Pid(), kExitCode); } } // namespace browser_watcher