mark a. foltz | 54537f0 | 2023-08-31 04:33:41 | [diff] [blame] | 1 | // Copyright 2022 The Chromium Authors |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 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 "content/public/browser/browser_child_process_observer.h" |
| 6 | |
Avi Drissman | adac2199 | 2023-01-11 23:46:39 | [diff] [blame] | 7 | #include "base/functional/bind.h" |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 8 | #include "base/run_loop.h" |
| 9 | #include "build/build_config.h" |
| 10 | #include "content/browser/browser_child_process_host_impl.h" |
Will Harris | cd57b83 | 2023-01-05 20:03:10 | [diff] [blame] | 11 | #include "content/browser/child_process_host_impl.h" |
Emily Andrews | 2677e32f | 2025-02-24 18:37:05 | [diff] [blame] | 12 | #include "content/browser/service_host/utility_process_host.h" |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 13 | #include "content/public/browser/browser_child_process_host.h" |
| 14 | #include "content/public/browser/browser_child_process_host_delegate.h" |
| 15 | #include "content/public/browser/child_process_data.h" |
| 16 | #include "content/public/common/content_switches.h" |
| 17 | #include "content/public/common/process_type.h" |
| 18 | #include "content/public/common/sandboxed_process_launcher_delegate.h" |
| 19 | #include "content/public/test/browser_test.h" |
| 20 | #include "content/public/test/content_browser_test.h" |
| 21 | #include "content/public/test/test_service.mojom.h" |
| 22 | #include "sandbox/policy/sandbox_type.h" |
| 23 | #include "testing/gmock/include/gmock/gmock.h" |
| 24 | |
| 25 | namespace content { |
| 26 | |
| 27 | namespace { |
| 28 | |
| 29 | // An enum that represent the different type of notitifcations that exist in |
| 30 | // BrowserChildProcessObserver. |
| 31 | enum class Notification { |
| 32 | kLaunchedAndConnected, |
| 33 | kDisconnected, |
| 34 | kCrashed, |
| 35 | kKilled, |
| 36 | kLaunchFailed, |
Patrick Monette | 3ded71b3 | 2022-09-28 14:53:04 | [diff] [blame] | 37 | kExitedNormally, |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 38 | }; |
| 39 | |
| 40 | // Nicer test output. |
| 41 | std::ostream& operator<<(std::ostream& os, Notification notification) { |
| 42 | switch (notification) { |
| 43 | case Notification::kLaunchedAndConnected: |
| 44 | os << "LaunchedAndConnected"; |
| 45 | break; |
| 46 | case Notification::kDisconnected: |
| 47 | os << "Disconnected"; |
| 48 | break; |
| 49 | case Notification::kCrashed: |
| 50 | os << "Crashed"; |
| 51 | break; |
| 52 | case Notification::kKilled: |
| 53 | os << "Killed"; |
| 54 | break; |
| 55 | case Notification::kLaunchFailed: |
| 56 | os << "LaunchFailed"; |
| 57 | break; |
Patrick Monette | 3ded71b3 | 2022-09-28 14:53:04 | [diff] [blame] | 58 | case Notification::kExitedNormally: |
| 59 | os << "ExitedNormally"; |
| 60 | break; |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 61 | } |
| 62 | return os; |
| 63 | } |
| 64 | |
| 65 | // Returns true if a child process whose ID is |child_id| is still alive. |
| 66 | bool IsHostAlive(int child_id) { |
| 67 | return BrowserChildProcessHost::FromID(child_id) != nullptr; |
| 68 | } |
| 69 | |
| 70 | } // namespace |
| 71 | |
| 72 | // A test BrowserChildProcessObserver that transforms every call to one of the |
| 73 | // observer's method to a call to the notification callback. |
| 74 | class BrowserChildProcessNotificationObserver |
| 75 | : public BrowserChildProcessObserver { |
| 76 | public: |
| 77 | using OnNotificationCallback = |
| 78 | base::RepeatingCallback<void(Notification notification)>; |
| 79 | |
| 80 | BrowserChildProcessNotificationObserver( |
| 81 | int child_id, |
| 82 | OnNotificationCallback on_notification_callback) |
| 83 | : child_id_(child_id), |
| 84 | on_notification_callback_(std::move(on_notification_callback)) { |
| 85 | BrowserChildProcessObserver::Add(this); |
| 86 | } |
| 87 | |
| 88 | ~BrowserChildProcessNotificationObserver() override { |
| 89 | BrowserChildProcessObserver::Remove(this); |
| 90 | } |
| 91 | |
Ben Bamesberger | 1db126aa | 2024-10-07 18:52:12 | [diff] [blame] | 92 | protected: |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 93 | // BrowserChildProcessObserver: |
| 94 | void BrowserChildProcessLaunchedAndConnected( |
| 95 | const ChildProcessData& data) override { |
| 96 | OnNotification(data, Notification::kLaunchedAndConnected); |
| 97 | } |
| 98 | void BrowserChildProcessHostDisconnected( |
| 99 | const ChildProcessData& data) override { |
| 100 | OnNotification(data, Notification::kDisconnected); |
| 101 | } |
| 102 | void BrowserChildProcessCrashed( |
| 103 | const ChildProcessData& data, |
| 104 | const ChildProcessTerminationInfo& info) override { |
| 105 | OnNotification(data, Notification::kCrashed); |
| 106 | } |
| 107 | void BrowserChildProcessKilled( |
| 108 | const ChildProcessData& data, |
| 109 | const ChildProcessTerminationInfo& info) override { |
| 110 | OnNotification(data, Notification::kKilled); |
| 111 | } |
| 112 | void BrowserChildProcessLaunchFailed( |
| 113 | const ChildProcessData& data, |
| 114 | const ChildProcessTerminationInfo& info) override { |
| 115 | OnNotification(data, Notification::kLaunchFailed); |
| 116 | } |
Patrick Monette | 3ded71b3 | 2022-09-28 14:53:04 | [diff] [blame] | 117 | void BrowserChildProcessExitedNormally( |
| 118 | const ChildProcessData& data, |
| 119 | const ChildProcessTerminationInfo& info) override { |
| 120 | OnNotification(data, Notification::kExitedNormally); |
| 121 | } |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 122 | |
| 123 | void OnNotification(const ChildProcessData& data, Notification notification) { |
| 124 | if (data.id == child_id_) |
| 125 | on_notification_callback_.Run(notification); |
| 126 | } |
| 127 | |
Ben Bamesberger | 1db126aa | 2024-10-07 18:52:12 | [diff] [blame] | 128 | private: |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 129 | // Every notification coming for a child with a different ID will be ignored. |
| 130 | int child_id_; |
| 131 | |
| 132 | // The callback to invoke every time a method of the observer is called. |
| 133 | OnNotificationCallback on_notification_callback_; |
| 134 | }; |
| 135 | |
| 136 | // A helper class that allows the user to wait until a specific |notification| |
| 137 | // is sent for a child process whose ID matches |child_id|. |
| 138 | class WaitForNotificationObserver { |
| 139 | public: |
| 140 | WaitForNotificationObserver(int child_id, Notification notification) |
| 141 | : inner_observer_( |
| 142 | child_id, |
| 143 | base::BindRepeating(&WaitForNotificationObserver::OnNotification, |
| 144 | base::Unretained(this))), |
| 145 | notification_(notification) {} |
| 146 | |
| 147 | ~WaitForNotificationObserver() = default; |
| 148 | |
| 149 | // Waits until the notification is received. Returns immediately if it was |
| 150 | // already received. |
| 151 | void Wait() { |
| 152 | if (notification_received_) |
| 153 | return; |
| 154 | |
| 155 | DCHECK(!run_loop_.running()); |
| 156 | run_loop_.Run(); |
| 157 | } |
| 158 | |
| 159 | private: |
| 160 | void OnNotification(Notification notification) { |
| 161 | if (notification != notification_) |
| 162 | return; |
| 163 | |
| 164 | notification_received_ = true; |
| 165 | if (run_loop_.running()) |
| 166 | run_loop_.Quit(); |
| 167 | } |
| 168 | |
| 169 | BrowserChildProcessNotificationObserver inner_observer_; |
| 170 | Notification notification_; |
| 171 | base::RunLoop run_loop_; |
| 172 | bool notification_received_ = false; |
| 173 | }; |
| 174 | |
| 175 | class TestSandboxedProcessLauncherDelegate |
| 176 | : public SandboxedProcessLauncherDelegate { |
| 177 | public: |
| 178 | explicit TestSandboxedProcessLauncherDelegate( |
| 179 | sandbox::mojom::Sandbox sandbox_type) |
| 180 | : sandbox_type_(sandbox_type) {} |
| 181 | ~TestSandboxedProcessLauncherDelegate() override = default; |
| 182 | |
| 183 | // SandboxedProcessLauncherDelegate: |
| 184 | sandbox::mojom::Sandbox GetSandboxType() override { return sandbox_type_; } |
| 185 | |
| 186 | private: |
| 187 | sandbox::mojom::Sandbox sandbox_type_; |
| 188 | }; |
| 189 | |
| 190 | // A test-specific type of process host. Self-owned. |
Tom Sepez | f3f28e1 | 2025-07-21 22:39:13 | [diff] [blame] | 191 | class TestProcessHost final : public BrowserChildProcessHostDelegate { |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 192 | public: |
| 193 | static base::WeakPtr<TestProcessHost> Create() { |
| 194 | auto* instance = new TestProcessHost(); |
| 195 | return instance->GetWeakPtr(); |
| 196 | } |
| 197 | |
| 198 | TestProcessHost() |
Tom Sepez | 7bf8900 | 2025-07-17 23:44:04 | [diff] [blame] | 199 | : process_(BrowserChildProcessHost::Create(PROCESS_TYPE_UTILITY, this)) {} |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 200 | ~TestProcessHost() override = default; |
| 201 | |
| 202 | // Returns the ID of the child process. |
Emily Andrews | d15fd76 | 2024-12-10 20:41:54 | [diff] [blame] | 203 | int GetID() { return process_->GetData().id; } |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 204 | |
| 205 | // Binds to the test service on the child process and returns the bound |
| 206 | // remote. |
| 207 | mojo::Remote<mojom::TestService> BindTestService() { |
| 208 | mojo::Remote<mojom::TestService> test_service; |
| 209 | |
| 210 | static_cast<ChildProcessHostImpl*>(process_->GetHost()) |
| 211 | ->child_process() |
| 212 | ->BindServiceInterface(test_service.BindNewPipeAndPassReceiver()); |
| 213 | |
| 214 | return test_service; |
| 215 | } |
| 216 | |
| 217 | // Returns the command line used to launch the child process. |
| 218 | std::unique_ptr<base::CommandLine> GetChildCommandLine() { |
| 219 | base::FilePath child_path = |
| 220 | ChildProcessHost::GetChildPath(ChildProcessHost::CHILD_NORMAL); |
| 221 | auto command_line = std::make_unique<base::CommandLine>(child_path); |
| 222 | |
| 223 | command_line->AppendSwitchASCII(switches::kProcessType, |
| 224 | switches::kUtilityProcess); |
| 225 | command_line->AppendSwitchASCII(switches::kUtilitySubType, |
| 226 | "Test Utility Process"); |
| 227 | sandbox::policy::SetCommandLineFlagsForSandboxType(command_line.get(), |
| 228 | sandbox_type_); |
| 229 | |
| 230 | return command_line; |
| 231 | } |
| 232 | |
Ben Bamesberger | 1db126aa | 2024-10-07 18:52:12 | [diff] [blame] | 233 | // Launches the child process using the default test launcher delegate. |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 234 | void LaunchProcess() { |
Ben Bamesberger | 1db126aa | 2024-10-07 18:52:12 | [diff] [blame] | 235 | LaunchProcessWithDelegate( |
| 236 | std::make_unique<TestSandboxedProcessLauncherDelegate>(sandbox_type_)); |
| 237 | } |
| 238 | |
| 239 | // Launches the child process using a supplied sandbox delegate. |
| 240 | void LaunchProcessWithDelegate( |
| 241 | std::unique_ptr<SandboxedProcessLauncherDelegate> |
| 242 | sandboxed_process_launcher_delegate) { |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 243 | process_->SetName(u"Test utility process"); |
| 244 | |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 245 | auto command_line = GetChildCommandLine(); |
| 246 | bool terminate_on_shutdown = true; |
| 247 | |
| 248 | process_->Launch(std::move(sandboxed_process_launcher_delegate), |
| 249 | std::move(command_line), terminate_on_shutdown); |
| 250 | |
| 251 | test_service_ = BindTestService(); |
| 252 | } |
| 253 | |
| 254 | // Requests the child process to shutdown. |
| 255 | void ForceShutdown() { process_->GetHost()->ForceShutdown(); } |
| 256 | |
| 257 | // Disconnects the bound remote from the test service. |
| 258 | void Disconnect() { test_service_.reset(); } |
| 259 | |
| 260 | // Sets the sandbox type to use for the child process. |
| 261 | void SetSandboxType(sandbox::mojom::Sandbox sandbox_type) { |
| 262 | sandbox_type_ = sandbox_type; |
| 263 | } |
| 264 | |
| 265 | mojom::TestService* service() const { return test_service_.get(); } |
| 266 | |
| 267 | base::WeakPtr<TestProcessHost> GetWeakPtr() { |
| 268 | return weak_ptr_factory_.GetWeakPtr(); |
| 269 | } |
| 270 | |
| 271 | private: |
| 272 | sandbox::mojom::Sandbox sandbox_type_ = sandbox::mojom::Sandbox::kUtility; |
| 273 | |
| 274 | std::unique_ptr<BrowserChildProcessHost> process_; |
| 275 | |
| 276 | mojo::Remote<mojom::TestService> test_service_; |
| 277 | |
| 278 | base::WeakPtrFactory<TestProcessHost> weak_ptr_factory_{this}; |
| 279 | }; |
| 280 | |
| 281 | // A helper class that exposes which notifications were sent for a specific |
| 282 | // child process. |
| 283 | class TestBrowserChildProcessObserver { |
| 284 | public: |
| 285 | explicit TestBrowserChildProcessObserver(int child_id) |
| 286 | : inner_observer_(child_id, |
| 287 | base::BindRepeating( |
| 288 | &TestBrowserChildProcessObserver::OnNotification, |
| 289 | base::Unretained(this))) {} |
| 290 | |
| 291 | ~TestBrowserChildProcessObserver() = default; |
| 292 | |
| 293 | // Returns the notifications received for |child_id|. |
| 294 | const std::vector<Notification>& notifications() const { |
| 295 | return notifications_; |
| 296 | } |
| 297 | |
| 298 | private: |
| 299 | void OnNotification(Notification notification) { |
| 300 | notifications_.push_back(notification); |
| 301 | } |
| 302 | |
| 303 | BrowserChildProcessNotificationObserver inner_observer_; |
| 304 | |
| 305 | std::vector<Notification> notifications_; |
| 306 | }; |
| 307 | |
| 308 | class BrowserChildProcessObserverBrowserTest : public ContentBrowserTest {}; |
| 309 | |
| 310 | // Tests that launching and then using ForceShutdown() results in a normal |
| 311 | // termination. |
| 312 | #if defined(ADDRESS_SANITIZER) |
Alison Gale | 770f3fc | 2024-04-27 00:39:58 | [diff] [blame] | 313 | // TODO(crbug.com/40238612): Fix ASAN failures on trybot. |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 314 | #define MAYBE_LaunchAndForceShutdown DISABLED_LaunchAndForceShutdown |
| 315 | #else |
| 316 | #define MAYBE_LaunchAndForceShutdown LaunchAndForceShutdown |
| 317 | #endif |
| 318 | IN_PROC_BROWSER_TEST_F(BrowserChildProcessObserverBrowserTest, |
| 319 | MAYBE_LaunchAndForceShutdown) { |
| 320 | base::WeakPtr<TestProcessHost> host = TestProcessHost::Create(); |
Emily Andrews | d15fd76 | 2024-12-10 20:41:54 | [diff] [blame] | 321 | int child_id = host->GetID(); |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 322 | |
| 323 | TestBrowserChildProcessObserver observer(child_id); |
| 324 | |
| 325 | { |
| 326 | WaitForNotificationObserver waiter(child_id, |
| 327 | Notification::kLaunchedAndConnected); |
| 328 | host->LaunchProcess(); |
| 329 | waiter.Wait(); |
| 330 | } |
| 331 | |
| 332 | { |
| 333 | WaitForNotificationObserver waiter(child_id, Notification::kDisconnected); |
| 334 | host->ForceShutdown(); |
| 335 | waiter.Wait(); |
| 336 | } |
| 337 | |
Patrick Monette | 3ded71b3 | 2022-09-28 14:53:04 | [diff] [blame] | 338 | Notification kExitNotification = |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 339 | #if BUILDFLAG(IS_ANDROID) |
Patrick Monette | 3ded71b3 | 2022-09-28 14:53:04 | [diff] [blame] | 340 | // TODO(pmonette): On Android, this currently causes a killed |
| 341 | // notification. Consider fixing. |
| 342 | Notification::kKilled; |
| 343 | #else |
| 344 | Notification::kExitedNormally; |
| 345 | #endif // BUILDFLAG(IS_ANDROID) |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 346 | |
| 347 | // The host should be deleted now. |
| 348 | EXPECT_FALSE(host); |
| 349 | EXPECT_FALSE(IsHostAlive(child_id)); |
| 350 | EXPECT_THAT(observer.notifications(), |
Patrick Monette | 3ded71b3 | 2022-09-28 14:53:04 | [diff] [blame] | 351 | testing::ElementsAreArray({Notification::kLaunchedAndConnected, |
| 352 | kExitNotification, |
| 353 | Notification::kDisconnected})); |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 354 | } |
| 355 | |
| 356 | // Tests that launching and then deleting the host results in a normal |
| 357 | // termination. |
| 358 | IN_PROC_BROWSER_TEST_F(BrowserChildProcessObserverBrowserTest, |
| 359 | LaunchAndDelete) { |
| 360 | base::WeakPtr<TestProcessHost> host = TestProcessHost::Create(); |
Emily Andrews | d15fd76 | 2024-12-10 20:41:54 | [diff] [blame] | 361 | int child_id = host->GetID(); |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 362 | |
| 363 | TestBrowserChildProcessObserver observer(child_id); |
| 364 | |
| 365 | { |
| 366 | WaitForNotificationObserver waiter(child_id, |
| 367 | Notification::kLaunchedAndConnected); |
| 368 | host->LaunchProcess(); |
| 369 | waiter.Wait(); |
| 370 | } |
| 371 | |
| 372 | { |
| 373 | WaitForNotificationObserver waiter(child_id, Notification::kDisconnected); |
| 374 | delete host.get(); |
| 375 | waiter.Wait(); |
| 376 | } |
| 377 | |
| 378 | // The host should be deleted now. |
| 379 | EXPECT_FALSE(host); |
| 380 | EXPECT_FALSE(IsHostAlive(child_id)); |
| 381 | EXPECT_THAT(observer.notifications(), |
| 382 | testing::ElementsAreArray({Notification::kLaunchedAndConnected, |
Patrick Monette | 3ded71b3 | 2022-09-28 14:53:04 | [diff] [blame] | 383 | Notification::kExitedNormally, |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 384 | Notification::kDisconnected})); |
| 385 | } |
| 386 | |
| 387 | // Tests that launching and then disconnecting the service channel results in a |
| 388 | // normal termination. |
| 389 | // Note: This only works for services bound using BindServiceInterface(), not |
| 390 | // BindReceiver(). |
| 391 | #if defined(ADDRESS_SANITIZER) |
Alison Gale | 770f3fc | 2024-04-27 00:39:58 | [diff] [blame] | 392 | // TODO(crbug.com/40238612): Fix ASAN failures on trybot. |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 393 | #define MAYBE_LaunchAndDisconnect DISABLED_LaunchAndDisconnect |
| 394 | #else |
| 395 | #define MAYBE_LaunchAndDisconnect LaunchAndDisconnect |
| 396 | #endif |
| 397 | IN_PROC_BROWSER_TEST_F(BrowserChildProcessObserverBrowserTest, |
| 398 | MAYBE_LaunchAndDisconnect) { |
| 399 | base::WeakPtr<TestProcessHost> host = TestProcessHost::Create(); |
Emily Andrews | d15fd76 | 2024-12-10 20:41:54 | [diff] [blame] | 400 | int child_id = host->GetID(); |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 401 | |
| 402 | TestBrowserChildProcessObserver observer(child_id); |
| 403 | |
| 404 | { |
| 405 | WaitForNotificationObserver waiter(child_id, |
| 406 | Notification::kLaunchedAndConnected); |
| 407 | host->LaunchProcess(); |
| 408 | waiter.Wait(); |
| 409 | } |
| 410 | |
| 411 | { |
| 412 | WaitForNotificationObserver waiter(child_id, Notification::kDisconnected); |
| 413 | host->Disconnect(); |
| 414 | waiter.Wait(); |
| 415 | } |
| 416 | |
Patrick Monette | 3ded71b3 | 2022-09-28 14:53:04 | [diff] [blame] | 417 | Notification kExitNotification = |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 418 | #if BUILDFLAG(IS_ANDROID) |
Patrick Monette | 3ded71b3 | 2022-09-28 14:53:04 | [diff] [blame] | 419 | // On Android, kKilled is always sent in the case of a crash. |
| 420 | Notification::kKilled; |
| 421 | #else |
| 422 | Notification::kExitedNormally; |
| 423 | #endif // BUILDFLAG(IS_ANDROID) |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 424 | |
| 425 | // The host should be deleted now. |
| 426 | EXPECT_FALSE(host); |
| 427 | EXPECT_FALSE(IsHostAlive(child_id)); |
Patrick Monette | 3ded71b3 | 2022-09-28 14:53:04 | [diff] [blame] | 428 | EXPECT_THAT(observer.notifications(), testing::ElementsAreArray({ |
| 429 | Notification::kLaunchedAndConnected, |
| 430 | kExitNotification, |
| 431 | Notification::kDisconnected, |
| 432 | })); |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 433 | } |
| 434 | |
| 435 | // Tests that launching and then causing a crash the host results in a crashed |
| 436 | // notification. |
Alison Gale | 47d1537d | 2024-04-19 21:31:46 | [diff] [blame] | 437 | // TODO(crbug.com/40868150): Times out on Android tests. |
Nohemi Fernandez | 98658b4 | 2022-09-28 11:10:48 | [diff] [blame] | 438 | #if BUILDFLAG(IS_ANDROID) |
Patrick Monette | 1ea74185 | 2022-09-27 18:39:44 | [diff] [blame] | 439 | #define MAYBE_LaunchAndCrash DISABLED_LaunchAndCrash |
| 440 | #else |
| 441 | #define MAYBE_LaunchAndCrash LaunchAndCrash |
| 442 | #endif |
| 443 | IN_PROC_BROWSER_TEST_F(BrowserChildProcessObserverBrowserTest, |
| 444 | MAYBE_LaunchAndCrash) { |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 445 | base::WeakPtr<TestProcessHost> host = TestProcessHost::Create(); |
Emily Andrews | d15fd76 | 2024-12-10 20:41:54 | [diff] [blame] | 446 | int child_id = host->GetID(); |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 447 | |
| 448 | TestBrowserChildProcessObserver observer(child_id); |
| 449 | |
| 450 | { |
| 451 | WaitForNotificationObserver waiter(child_id, |
| 452 | Notification::kLaunchedAndConnected); |
| 453 | host->LaunchProcess(); |
| 454 | waiter.Wait(); |
| 455 | } |
| 456 | |
| 457 | { |
| 458 | WaitForNotificationObserver waiter(child_id, Notification::kDisconnected); |
| 459 | host->service()->DoCrashImmediately(base::DoNothing()); |
| 460 | waiter.Wait(); |
| 461 | } |
| 462 | |
| 463 | Notification kCrashedNotification = |
| 464 | #if BUILDFLAG(IS_ANDROID) |
| 465 | // On Android, kKilled is always sent in the case of a crash. |
| 466 | Notification::kKilled; |
| 467 | #else |
| 468 | Notification::kCrashed; |
| 469 | #endif // BUILDFLAG(IS_ANDROID) |
| 470 | |
| 471 | // The host should be deleted now. |
| 472 | EXPECT_FALSE(host); |
| 473 | EXPECT_FALSE(IsHostAlive(child_id)); |
| 474 | EXPECT_THAT(observer.notifications(), |
| 475 | testing::ElementsAreArray({Notification::kLaunchedAndConnected, |
| 476 | kCrashedNotification, |
| 477 | Notification::kDisconnected})); |
| 478 | } |
| 479 | |
| 480 | // Tests that kLaunchFailed is correctly sent when the child process fails to |
| 481 | // launch. |
| 482 | // |
| 483 | // This test won't work as-is on POSIX platforms, where fork()+exec() is used to |
| 484 | // launch child processes, failure does not happen until exec(), therefore the |
| 485 | // test will see a valid child process followed by a |
| 486 | // TERMINATION_STATUS_ABNORMAL_TERMINATION of the forked process. However, |
| 487 | // posix_spawn() is used on macOS. |
| 488 | // See also ServiceProcessLauncherTest.FailToLaunchProcess. |
| 489 | #if !BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_MAC) |
| 490 | IN_PROC_BROWSER_TEST_F(BrowserChildProcessObserverBrowserTest, LaunchFailed) { |
| 491 | base::WeakPtr<TestProcessHost> host = TestProcessHost::Create(); |
Emily Andrews | d15fd76 | 2024-12-10 20:41:54 | [diff] [blame] | 492 | int child_id = host->GetID(); |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 493 | |
| 494 | #if BUILDFLAG(IS_WIN) |
| 495 | // The Windows sandbox does not like the child process being a different |
| 496 | // process, so launch unsandboxed for the purpose of this test. |
| 497 | host->SetSandboxType(sandbox::mojom::Sandbox::kNoSandbox); |
| 498 | #endif |
| 499 | |
| 500 | // Simulate a catastrophic launch failure for all child processes by |
| 501 | // making the path to the process non-existent. |
| 502 | base::CommandLine::ForCurrentProcess()->AppendSwitchPath( |
| 503 | switches::kBrowserSubprocessPath, |
| 504 | base::FilePath(FILE_PATH_LITERAL("non_existent_path"))); |
| 505 | |
| 506 | TestBrowserChildProcessObserver observer(child_id); |
| 507 | |
| 508 | { |
| 509 | WaitForNotificationObserver waiter(child_id, Notification::kLaunchFailed); |
| 510 | host->LaunchProcess(); |
| 511 | waiter.Wait(); |
| 512 | } |
| 513 | |
| 514 | // The host should be deleted now. |
| 515 | EXPECT_FALSE(host); |
| 516 | EXPECT_FALSE(IsHostAlive(child_id)); |
| 517 | EXPECT_THAT(observer.notifications(), |
| 518 | testing::ElementsAreArray({Notification::kLaunchFailed})); |
| 519 | } |
| 520 | #endif // !BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_MAC) |
| 521 | |
Ben Bamesberger | 1db126aa | 2024-10-07 18:52:12 | [diff] [blame] | 522 | #if BUILDFLAG(IS_WIN) |
| 523 | class TestPreSpawnTargetFailureSandboxedProcessLauncherDelegate |
| 524 | : public TestSandboxedProcessLauncherDelegate { |
| 525 | public: |
| 526 | using TestSandboxedProcessLauncherDelegate:: |
| 527 | TestSandboxedProcessLauncherDelegate; |
| 528 | |
| 529 | // SandboxedProcessLauncherDelegate: |
| 530 | bool PreSpawnTarget(sandbox::TargetPolicy* policy) override { |
| 531 | // Force a failure in PreSpawnTarget(). |
| 532 | return false; |
| 533 | } |
| 534 | }; |
| 535 | |
| 536 | // Override the observer to verify the error occurred in PreSpawnTarget(). |
| 537 | class TestPreSpawnTargetFailureBrowserChildProcessNotificationObserver |
| 538 | : public BrowserChildProcessNotificationObserver { |
| 539 | public: |
| 540 | using BrowserChildProcessNotificationObserver:: |
| 541 | BrowserChildProcessNotificationObserver; |
| 542 | |
| 543 | // BrowserChildProcessObserver: |
| 544 | void BrowserChildProcessLaunchFailed( |
| 545 | const ChildProcessData& data, |
| 546 | const ChildProcessTerminationInfo& info) override { |
| 547 | EXPECT_EQ(info.exit_code, sandbox::SBOX_ERROR_DELEGATE_PRE_SPAWN); |
| 548 | BrowserChildProcessNotificationObserver::OnNotification( |
| 549 | data, Notification::kLaunchFailed); |
| 550 | } |
| 551 | }; |
| 552 | |
| 553 | // Tests that a pre spawn failure results in a failed launch. |
| 554 | IN_PROC_BROWSER_TEST_F(BrowserChildProcessObserverBrowserTest, |
| 555 | LaunchPreSpawnFailed) { |
| 556 | base::WeakPtr<TestProcessHost> host = TestProcessHost::Create(); |
Emily Andrews | d15fd76 | 2024-12-10 20:41:54 | [diff] [blame] | 557 | int child_id = host->GetID(); |
Ben Bamesberger | 1db126aa | 2024-10-07 18:52:12 | [diff] [blame] | 558 | |
| 559 | TestBrowserChildProcessObserver observer(child_id); |
| 560 | |
| 561 | { |
| 562 | WaitForNotificationObserver waiter(child_id, Notification::kLaunchFailed); |
| 563 | host->LaunchProcessWithDelegate( |
| 564 | std::make_unique< |
| 565 | TestPreSpawnTargetFailureSandboxedProcessLauncherDelegate>( |
| 566 | sandbox::mojom::Sandbox::kUtility)); |
| 567 | waiter.Wait(); |
| 568 | } |
| 569 | |
| 570 | // The host should be deleted now. |
| 571 | EXPECT_FALSE(host); |
| 572 | EXPECT_FALSE(IsHostAlive(child_id)); |
| 573 | EXPECT_THAT(observer.notifications(), |
| 574 | testing::ElementsAreArray({Notification::kLaunchFailed})); |
| 575 | } |
| 576 | #endif // BUILDFLAG(IS_WIN) |
| 577 | |
Patrick Monette | 2b743d3 | 2022-09-23 23:40:37 | [diff] [blame] | 578 | } // namespace content |