// 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 "chrome/app/chrome_watcher_command_line_win.h" #include #include "base/command_line.h" #include "base/files/file_path.h" #include "base/process/process.h" #include "base/process/process_handle.h" #include "base/win/scoped_handle.h" #include "testing/gtest/include/gtest/gtest.h" namespace { base::FilePath ExampleExe() { static const wchar_t kExampleExe[] = FILE_PATH_LITERAL("example.exe"); base::FilePath example_exe(kExampleExe); return example_exe; } } // namespace class TestChromeWatcherCommandLineGenerator : public ChromeWatcherCommandLineGenerator { public: TestChromeWatcherCommandLineGenerator() : ChromeWatcherCommandLineGenerator(ExampleExe()) { } // In the normal case the generator and interpreter are run in separate // processes. However, since they are both being run in the same process in // these tests the handle tracker will explode as they both claim ownership of // the same handle. This function allows the generator handles to be released // before they are subsequently claimed by an interpreter. void ReleaseHandlesWithoutClosing() { on_initialized_event_handle_.Take(); parent_process_handle_.Take(); } }; // A fixture for tests that involve parsed command-lines. Contains utility // functions for standing up a well-configured valid generator. class ChromeWatcherCommandLineTest : public testing::Test { public: ChromeWatcherCommandLineTest() : cmd_line_(base::CommandLine::NO_PROGRAM) { } void GenerateAndInterpretCommandLine() { process_ = base::Process::OpenWithAccess( base::GetCurrentProcId(), PROCESS_QUERY_INFORMATION | SYNCHRONIZE); ASSERT_TRUE(process_.Handle()); event_.Set(::CreateEvent(nullptr, FALSE, FALSE, nullptr)); ASSERT_TRUE(event_.IsValid()); // The above handles are duplicated by the generator. generator_.SetOnInitializedEventHandle(event_.Get()); generator_.SetParentProcessHandle(process_.Handle()); // Expect there to be two inherited handles created by the generator. std::vector handles; generator_.GetInheritedHandles(&handles); EXPECT_EQ(2U, handles.size()); cmd_line_ = generator_.GenerateCommandLine(); // In the normal case the generator and interpreter are run in separate // processes. However, since they are both being run in the same process in // this test that will lead to both the generator and the interpreter // claiming ownership of the same handles, as far as the handle tracker is // concerned. To prevent this the handles are first released from tracking // by the generator. generator_.ReleaseHandlesWithoutClosing(); interpreted_ = ChromeWatcherCommandLine::InterpretCommandLine(cmd_line_); EXPECT_TRUE(interpreted_); } base::Process process_; base::win::ScopedHandle event_; TestChromeWatcherCommandLineGenerator generator_; base::CommandLine cmd_line_; scoped_ptr interpreted_; }; // The corresponding death test fixture. using ChromeWatcherCommandLineDeathTest = ChromeWatcherCommandLineTest; TEST(ChromeWatcherCommandLineGeneratorTest, UseOfBadHandlesFails) { // Handles are always machine word aligned so there is no way these are valid. HANDLE bad_handle_1 = reinterpret_cast(0x01FC00B1); HANDLE bad_handle_2 = reinterpret_cast(0x01FC00B3); // The above handles are duplicated by the generator. TestChromeWatcherCommandLineGenerator generator; EXPECT_FALSE(generator.SetOnInitializedEventHandle(bad_handle_1)); EXPECT_FALSE(generator.SetParentProcessHandle(bad_handle_2)); // Expect there to be no inherited handles created by the generator. std::vector handles; generator.GetInheritedHandles(&handles); EXPECT_TRUE(handles.empty()); } TEST(ChromeWatcherCommandLineGeneratorTest, BadCommandLineFailsInterpretation) { // Create an invalid command-line that is missing several fields. base::CommandLine cmd_line(ExampleExe()); // Parse the command line. auto interpreted = ChromeWatcherCommandLine::InterpretCommandLine(cmd_line); EXPECT_FALSE(interpreted); } TEST_F(ChromeWatcherCommandLineDeathTest, HandlesLeftUntakenCausesDeath) { EXPECT_NO_FATAL_FAILURE(GenerateAndInterpretCommandLine()); // Leave the handles in the interpreter and expect it to explode upon // destruction. EXPECT_DEATH(interpreted_.reset(), "Handles left untaken."); // The above call to the destructor only runs in the context of the death test // child process. To prevent the parent process from exploding in a similar // fashion, release the handles so the destructor is happy. interpreted_->TakeOnInitializedEventHandle().Close(); interpreted_->TakeParentProcessHandle().Close(); } TEST_F(ChromeWatcherCommandLineTest, SuccessfulParse) { EXPECT_NO_FATAL_FAILURE(GenerateAndInterpretCommandLine()); EXPECT_EQ(::GetCurrentThreadId(), interpreted_->main_thread_id()); // Explicitly take the handles from the interpreter so it doesn't explode. base::win::ScopedHandle on_init = interpreted_->TakeOnInitializedEventHandle(); base::win::ScopedHandle proc = interpreted_->TakeParentProcessHandle(); EXPECT_TRUE(on_init.IsValid()); EXPECT_TRUE(proc.IsValid()); } // TODO(chrisha): Remove this test upon switching to using the new command-line // classes. TEST(OldChromeWatcherCommandLineTest, BasicTest) { // Ownership of these handles is passed to the ScopedHandles below via // InterpretChromeWatcherCommandLine(). base::ProcessHandle current = ::OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, TRUE, // Inheritable. ::GetCurrentProcessId()); ASSERT_NE(nullptr, current); HANDLE event = ::CreateEvent(nullptr, FALSE, FALSE, nullptr); ASSERT_NE(nullptr, event); DWORD current_thread_id = ::GetCurrentThreadId(); base::CommandLine cmd_line = GenerateChromeWatcherCommandLine( ExampleExe(), current, current_thread_id, event); base::win::ScopedHandle current_result; DWORD current_thread_id_result = 0; base::win::ScopedHandle event_result; ASSERT_TRUE(InterpretChromeWatcherCommandLine( cmd_line, ¤t_result, ¤t_thread_id_result, &event_result)); ASSERT_EQ(current, current_result.Get()); ASSERT_EQ(current_thread_id, current_thread_id_result); ASSERT_EQ(event, event_result.Get()); }