Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/app/chrome_watcher_client_win.h" | |
| 6 | |
| 7 #include <windows.h> | |
| 8 #include <string> | |
| 9 #include "base/base_switches.h" | |
| 10 #include "base/bind.h" | |
| 11 #include "base/command_line.h" | |
| 12 #include "base/logging.h" | |
| 13 #include "base/process/process_handle.h" | |
| 14 #include "base/strings/string16.h" | |
| 15 #include "base/strings/string_number_conversions.h" | |
| 16 #include "base/synchronization/waitable_event.h" | |
| 17 #include "base/test/multiprocess_test.h" | |
| 18 #include "base/threading/simple_thread.h" | |
| 19 #include "base/time/time.h" | |
| 20 #include "base/win/scoped_handle.h" | |
| 21 #include "testing/gtest/include/gtest/gtest.h" | |
| 22 #include "testing/multiprocess_func_list.h" | |
| 23 | |
| 24 namespace { | |
| 25 | |
| 26 const char kParentHandle[] = "parent-handle"; | |
| 27 const char kEventHandle[] = "event-handle"; | |
| 28 | |
| 29 const base::char16 kExitEventName[] = L"ChromeWatcherClientTestFailEvent"; | |
| 30 const base::char16 kInitializeEventName[] = L"ChromeWatcherClientTestFailEvent"; | |
| 31 | |
| 32 base::win::ScopedHandle InterpretHandleSwitch(base::CommandLine& cmd_line, | |
| 33 const char* switch_name) { | |
| 34 std::string str_handle = | |
| 35 cmd_line.GetSwitchValueASCII(switch_name); | |
| 36 if (str_handle.empty()) { | |
| 37 LOG(ERROR) << "Switch " << switch_name << " unexpectedly absent."; | |
| 38 return base::win::ScopedHandle(); | |
| 39 } | |
| 40 | |
| 41 unsigned int_handle = 0; | |
| 42 if (!base::StringToUint(str_handle, &int_handle)) { | |
| 43 LOG(ERROR) << "Switch " << switch_name << " has invalid value " | |
| 44 << str_handle; | |
| 45 return base::win::ScopedHandle(); | |
| 46 } | |
| 47 | |
| 48 return base::win::ScopedHandle( | |
| 49 reinterpret_cast<base::ProcessHandle>(int_handle)); | |
| 50 } | |
| 51 | |
| 52 // Simulates a Chrome watcher process. Exits when the global event | |
| 53 // kExitEventName is signaled. Signals the "on initialized" event (passed on the | |
| 54 // command-line) when the global event kInitializeEventName is signaled. | |
| 55 MULTIPROCESS_TEST_MAIN(ChromeWatcherClientTestProcess) { | |
| 56 base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess(); | |
| 57 base::win::ScopedHandle exit_event( | |
| 58 ::CreateEvent(NULL, FALSE, FALSE, kExitEventName)); | |
|
Sigurður Ásgeirsson
2015/01/29 16:59:56
I think this will create flakes on parallel tests.
erikwright (departed)
2015/01/30 20:39:57
Done.
| |
| 59 if (!exit_event.IsValid()) { | |
| 60 LOG(ERROR) << "Failed to create event named " << kExitEventName; | |
| 61 return 1; | |
| 62 } | |
| 63 | |
| 64 base::win::ScopedHandle initialize_event( | |
| 65 ::CreateEvent(NULL, FALSE, FALSE, kInitializeEventName)); | |
|
Sigurður Ásgeirsson
2015/01/29 16:59:56
ditto
erikwright (departed)
2015/01/30 20:39:57
Done.
| |
| 66 if (!initialize_event.IsValid()) { | |
| 67 LOG(ERROR) << "Failed to create event named " << kInitializeEventName; | |
| 68 return 1; | |
| 69 } | |
| 70 | |
| 71 base::win::ScopedHandle parent_process( | |
| 72 InterpretHandleSwitch(*cmd_line, kParentHandle)); | |
| 73 if (!parent_process.IsValid()) | |
| 74 return 1; | |
| 75 | |
| 76 base::win::ScopedHandle on_initialized_event( | |
| 77 InterpretHandleSwitch(*cmd_line, kEventHandle)); | |
| 78 if (!on_initialized_event.IsValid()) | |
| 79 return 1; | |
| 80 | |
| 81 while (true) { | |
| 82 // We loop as a convenient way to continue waiting for the exit_event after | |
| 83 // the initialize_event is signaled. We expect to get initialize_event zero | |
| 84 // or one times before exit_event, never more. | |
| 85 HANDLE handles[] = {exit_event.Get(), initialize_event.Get()}; | |
| 86 DWORD result = | |
| 87 ::WaitForMultipleObjects(arraysize(handles), handles, FALSE, INFINITE); | |
| 88 switch (result) { | |
| 89 case WAIT_OBJECT_0: | |
| 90 // exit_event | |
| 91 return 0; | |
| 92 case WAIT_OBJECT_0 + 1: | |
| 93 // initialize_event | |
| 94 ::SetEvent(on_initialized_event.Get()); | |
| 95 break; | |
| 96 case WAIT_FAILED: | |
| 97 PLOG(ERROR) << "Unexpected failure in WaitForMultipleObjects."; | |
| 98 return 1; | |
| 99 default: | |
| 100 NOTREACHED() << "Unexpected result from WaitForMultipleObjects: " | |
| 101 << result; | |
| 102 return 1; | |
| 103 } | |
| 104 } | |
| 105 } | |
| 106 | |
| 107 // Returns a command line to launch back into ChromeWatcherClientTestProcess. | |
| 108 base::CommandLine GenerateCommandLine(HANDLE on_initialized_event, | |
| 109 HANDLE parent_handle) { | |
| 110 base::CommandLine ret = base::GetMultiProcessTestChildBaseCommandLine(); | |
| 111 | |
| 112 ret.AppendSwitchASCII(switches::kTestChildProcess, "VerifyParentHandle"); | |
| 113 ret.AppendSwitchASCII( | |
| 114 kEventHandle, | |
| 115 base::UintToString(reinterpret_cast<unsigned int>(parent_handle))); | |
| 116 ret.AppendSwitchASCII( | |
| 117 kParentHandle, | |
| 118 base::UintToString(reinterpret_cast<unsigned int>(parent_handle))); | |
| 119 return ret; | |
| 120 } | |
| 121 | |
| 122 // Implements a thread to launch the ChromeWatcherClient and block on | |
| 123 // WaitForInitialization. Provides various helpers to interact with the | |
| 124 // ChromeWatcherClient. | |
| 125 class ChromeWatcherClientThread : public base::SimpleThread { | |
| 126 public: | |
| 127 ChromeWatcherClientThread() | |
| 128 : client_(base::Bind(&GenerateCommandLine)), | |
| 129 complete_(false, false), | |
| 130 result_(false), | |
| 131 SimpleThread("ChromeWatcherClientTest thread") {} | |
| 132 | |
| 133 // Waits up to |timeout| for the call to WaitForInitialization to complete. If | |
| 134 // it does, sets |result| to the return value of WaitForInitialization and | |
| 135 // returns true. Otherwise returns false. | |
| 136 bool WaitForResultWithTimeout(const base::TimeDelta& timeout, bool* result) { | |
| 137 if (!complete_.TimedWait(timeout)) | |
| 138 return false; | |
| 139 *result = result_; | |
| 140 return true; | |
| 141 } | |
| 142 | |
| 143 // Waits indefinitely for the call to WaitForInitialization to complete. | |
| 144 // Returns the return value of WaitForInitialization. | |
| 145 bool WaitForResult() { | |
| 146 complete_.Wait(); | |
| 147 return result_; | |
| 148 } | |
| 149 | |
| 150 ChromeWatcherClient& client() { return client_; } | |
| 151 | |
| 152 // base::SimpleThread implementation. | |
| 153 void Run() override { | |
| 154 result_ = client_.LaunchWatcher(); | |
| 155 if (result_) | |
| 156 result_ = client_.WaitForInitialization(); | |
| 157 complete_.Signal(); | |
| 158 } | |
| 159 | |
| 160 private: | |
| 161 // The instance under test. | |
| 162 ChromeWatcherClient client_; | |
| 163 // Signaled when WaitForInitialization returns. | |
| 164 base::WaitableEvent complete_; | |
| 165 // The return value of WaitForInitialization. | |
| 166 bool result_; | |
| 167 | |
| 168 DISALLOW_COPY_AND_ASSIGN(ChromeWatcherClientThread); | |
| 169 }; | |
| 170 | |
| 171 } // namespace | |
| 172 | |
| 173 class ChromeWatcherClientTest : public testing::Test { | |
| 174 protected: | |
| 175 // Sends a signal to the simulated watcher process to exit. Returns true if | |
| 176 // successful. | |
| 177 bool SignalExit() { return FALSE != ::SetEvent(exit_event_.Get()); } | |
| 178 | |
| 179 // Sends a signal to the simulated watcher process to signal its | |
| 180 // "initialization". Returns true if successful. | |
| 181 bool SignalInitialize() { | |
| 182 return FALSE != ::SetEvent(initialize_event_.Get()); | |
| 183 } | |
| 184 | |
| 185 // The helper thread, which also provides access to the ChromeWatcherClient. | |
| 186 ChromeWatcherClientThread& thread() { return thread_; } | |
| 187 | |
| 188 // testing::Test implementation. | |
| 189 void SetUp() override { | |
| 190 exit_event_.Set(::CreateEvent(NULL, FALSE, FALSE, kExitEventName)); | |
| 191 ASSERT_TRUE(exit_event_.IsValid()); | |
| 192 initialize_event_.Set( | |
| 193 ::CreateEvent(NULL, FALSE, FALSE, kInitializeEventName)); | |
| 194 ASSERT_TRUE(initialize_event_.IsValid()); | |
| 195 } | |
| 196 | |
| 197 void TearDown() override { | |
| 198 if (thread_.client().launched()) | |
| 199 thread_.client().TerminateWatcher(99); | |
| 200 thread_.Join(); | |
| 201 } | |
| 202 | |
| 203 private: | |
| 204 // Used to launch and block on the Chrome watcher process in a background | |
| 205 // thread. | |
| 206 ChromeWatcherClientThread thread_; | |
| 207 // Used to signal the Chrome watcher process to exit. | |
| 208 base::win::ScopedHandle exit_event_; | |
| 209 // Used to signal the Chrome watcher process to signal its own | |
| 210 // initialization.. | |
| 211 base::win::ScopedHandle initialize_event_; | |
| 212 }; | |
| 213 | |
| 214 TEST_F(ChromeWatcherClientTest, SuccessTest) { | |
| 215 thread().Start(); | |
| 216 bool result = false; | |
| 217 // Give a broken implementation a chance to exit unexpectedly. | |
| 218 ASSERT_FALSE(thread().WaitForResultWithTimeout( | |
| 219 base::TimeDelta::FromMilliseconds(100), &result)); | |
| 220 ASSERT_TRUE(SignalInitialize()); | |
| 221 ASSERT_TRUE(thread().WaitForResult()); | |
| 222 // The watcher should still be running. Give a broken implementation a chance | |
| 223 // to exit unexpectedly, then kill it. That the returned exit code is what we | |
| 224 // requested proves that the process was still running. | |
| 225 ASSERT_FALSE(thread().WaitForResultWithTimeout( | |
| 226 base::TimeDelta::FromMilliseconds(100), &result)); | |
| 227 ASSERT_EQ(99, thread().client().TerminateWatcher(99)); | |
| 228 } | |
| 229 | |
| 230 TEST_F(ChromeWatcherClientTest, FailureTest) { | |
| 231 thread().Start(); | |
| 232 bool result = false; | |
| 233 // Give a broken implementation a chance to exit unexpectedly. | |
| 234 ASSERT_FALSE(thread().WaitForResultWithTimeout( | |
| 235 base::TimeDelta::FromMilliseconds(100), &result)); | |
| 236 ASSERT_TRUE(SignalExit()); | |
| 237 ASSERT_FALSE(thread().WaitForResult()); | |
| 238 // The exit code will not be as requested since the process exited "normally". | |
| 239 ASSERT_EQ(0, thread().client().TerminateWatcher(99)); | |
| 240 } | |
| OLD | NEW |