| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #![cfg(test)] |
| |
| use cstr::cstr; |
| use fdio::{self, SpawnOptions}; |
| use fuchsia_runtime as runtime; |
| use fuchsia_zircon::{self as zx, AsHandleRef, Task}; |
| use std::ffi::{CStr, CString}; |
| use std::mem::size_of_val; |
| |
| #[test] |
| fn zx_singlethreaded() { |
| run_test_with_args(&["--spawn"], true); |
| run_test_with_args(&["--spawn", "--job"], true); |
| run_test_with_args(&["--spawn", "--kill"], true); |
| run_test_with_args(&["--spawn", "--job", "--kill"], true); |
| } |
| |
| #[test] |
| fn zx_singlethreaded_with_panic() { |
| run_test_with_args(&["--spawn", "--panic"], false); |
| run_test_with_args(&["--spawn", "--panic", "--job"], false); |
| run_test_with_args(&["--spawn", "--panic", "--kill"], false); |
| run_test_with_args(&["--spawn", "--panic", "--job", "--kill"], false); |
| } |
| |
| #[test] |
| fn zx_multithreaded() { |
| run_test_with_args(&["--spawn", "--spawn-on-thread"], true); |
| run_test_with_args(&["--spawn", "--spawn-on-thread", "--job"], true); |
| run_test_with_args(&["--spawn", "--spawn-on-thread", "--kill"], true); |
| run_test_with_args(&["--spawn", "--spawn-on-thread", "--job", "--kill"], true); |
| run_test_with_args(&["--spawn", "--spawn-on-thread", "--wait"], true); |
| run_test_with_args(&["--spawn", "--spawn-on-thread", "--wait", "--job"], true); |
| run_test_with_args(&["--spawn", "--spawn-on-thread", "--wait", "--kill"], true); |
| run_test_with_args(&["--spawn", "--spawn-on-thread", "--wait", "--job", "--kill"], true); |
| } |
| |
| #[test] |
| fn zx_multithreaded_with_panic() { |
| run_test_with_args(&["--spawn", "--spawn-on-thread", "--panic"], false); |
| run_test_with_args(&["--spawn", "--spawn-on-thread", "--panic", "--job"], false); |
| run_test_with_args(&["--spawn", "--spawn-on-thread", "--panic", "--kill"], false); |
| run_test_with_args(&["--spawn", "--spawn-on-thread", "--panic", "--job", "--kill"], false); |
| run_test_with_args(&["--spawn", "--spawn-on-thread", "--wait", "--panic"], false); |
| run_test_with_args(&["--spawn", "--spawn-on-thread", "--wait", "--panic", "--job"], false); |
| run_test_with_args(&["--spawn", "--spawn-on-thread", "--wait", "--panic", "--kill"], false); |
| run_test_with_args( |
| &["--spawn", "--spawn-on-thread", "--wait", "--panic", "--job", "--kill"], |
| false, |
| ); |
| } |
| |
| #[test] |
| #[should_panic(expected = "did not exit")] |
| fn zx_singlethreaded_unscoped() { |
| run_test_with_args(&["--spawn", "--unscoped"], true); |
| } |
| |
| #[test] |
| #[should_panic(expected = "did not exit")] |
| fn zx_singlethreaded_unscoped_with_panic() { |
| run_test_with_args(&["--spawn", "--unscoped", "--panic"], false); |
| } |
| |
| fn run_test_with_args(args: &[&str], expect_success: bool) { |
| let args = args.iter().map(|s| CString::new(*s).unwrap()).collect::<Vec<_>>(); |
| let args = args.iter().map(|s| s.as_c_str()).collect::<Vec<_>>(); |
| run_test_with_c_args(&args, expect_success); |
| } |
| |
| fn run_test_with_c_args(args: &[&CStr], expect_success: bool) { |
| println!("running spawner with args: {:?}", args); |
| |
| let child_job = runtime::job_default().create_child_job().unwrap(); |
| let bin = cstr!("/pkg/bin/scoped_task_test_spawner"); |
| let args: Vec<&CStr> = Some(bin).iter().chain(args.into_iter()).copied().collect(); |
| |
| let process = fdio::spawn( |
| &child_job, |
| // Avoid cloning stdio to prevent hangs in the case of a test failure. |
| SpawnOptions::CLONE_ALL, // - SpawnOptions::CLONE_STDIO, |
| bin, |
| &args, |
| ) |
| .expect("could not spawn"); |
| process |
| .wait_handle(zx::Signals::PROCESS_TERMINATED, zx::Time::INFINITE) |
| .expect("could not wait"); |
| let info = process.info().unwrap(); |
| if expect_success { |
| assert_eq!(0, info.return_code); |
| } else { |
| assert_ne!(0, info.return_code); |
| } |
| check_all_processes_terminated(&child_job); |
| } |
| |
| fn check_all_processes_terminated(job: &zx::Job) { |
| use fuchsia_zircon::sys::*; |
| |
| let mut koids: [zx_koid_t; 32] = Default::default(); |
| let mut actual: usize = 0; |
| let mut avail: usize = 0; |
| let status = unsafe { |
| zx_object_get_info( |
| job.raw_handle(), |
| ZX_INFO_JOB_PROCESSES, |
| koids.as_mut_ptr() as *mut u8, |
| size_of_val(&koids), |
| &mut actual as *mut usize, |
| &mut avail as *mut usize, |
| ) |
| }; |
| assert_eq!(zx::Status::OK, zx::Status::from_raw(status)); |
| assert_eq!(actual, avail, "too many child processes"); |
| |
| println!("process_koids={:?}", &koids[0..actual]); |
| for koid in &koids[0..actual] { |
| let process: zx::Process = unsafe { |
| let mut handle: zx_handle_t = Default::default(); |
| let status = zx::Status::from_raw(zx_object_get_child( |
| job.raw_handle(), |
| *koid, |
| ZX_RIGHT_SAME_RIGHTS, |
| &mut handle as *mut zx_handle_t, |
| )); |
| if status == zx::Status::NOT_FOUND { |
| // The process object can get cleaned up before we create a handle to it. |
| println!("object for process koid {} not found, continuing", koid); |
| continue; |
| } |
| assert_eq!(zx::Status::OK, status); |
| zx::Handle::from_raw(handle) |
| } |
| .into(); |
| let info = process.info().unwrap(); |
| if !zx::ProcessInfoFlags::from_bits(info.flags) |
| .unwrap() |
| .contains(zx::ProcessInfoFlags::EXITED) |
| { |
| process.kill().unwrap(); |
| } |
| assert!( |
| zx::ProcessInfoFlags::from_bits(info.flags) |
| .unwrap() |
| .contains(zx::ProcessInfoFlags::EXITED), |
| "process koid {} did not exit", |
| koid |
| ); |
| } |
| |
| // Recurse into sub-jobs. |
| let status = unsafe { |
| zx_object_get_info( |
| job.raw_handle(), |
| ZX_INFO_JOB_CHILDREN, |
| koids.as_mut_ptr() as *mut u8, |
| size_of_val(&koids), |
| &mut actual as *mut usize, |
| &mut avail as *mut usize, |
| ) |
| }; |
| assert_eq!(zx::Status::OK, zx::Status::from_raw(status)); |
| assert_eq!(actual, avail, "too many child jobs"); |
| |
| println!("job_koids={:?}", &koids[0..actual]); |
| for koid in &koids[0..actual] { |
| let job: zx::Job = unsafe { |
| let mut handle: zx_handle_t = Default::default(); |
| let status = zx::Status::from_raw(zx_object_get_child( |
| job.raw_handle(), |
| *koid, |
| ZX_RIGHT_SAME_RIGHTS, |
| &mut handle as *mut zx_handle_t, |
| )); |
| if status == zx::Status::NOT_FOUND { |
| // The job object can get cleaned up before we create a handle to it. |
| println!("object for job koid {} not found, continuing", koid); |
| continue; |
| } |
| assert_eq!(zx::Status::OK, status); |
| zx::Handle::from_raw(handle) |
| } |
| .into(); |
| check_all_processes_terminated(&job); |
| } |
| } |