| // Copyright 2023 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(unix)] |
| use std::os::unix::process::ExitStatusExt; |
| use std::process::Command; |
| use std::{path::PathBuf, process::ExitCode}; |
| |
| use anyhow::{Context, Result}; |
| use itertools::Itertools; |
| |
| /// Runner for dispatching antlion. |
| pub(crate) trait Runner { |
| /// Run antlion using the provided config and output directory. |
| fn run(&self, config: PathBuf) -> Result<ExitStatus>; |
| } |
| |
| /// Executes antlion as a local process. |
| pub(crate) struct ProcessRunner { |
| pub python_bin: String, |
| pub antlion_pyz: PathBuf, |
| pub test_cases: Vec<String>, |
| } |
| |
| impl Runner for ProcessRunner { |
| fn run(&self, config: PathBuf) -> Result<ExitStatus> { |
| let mut args = vec![ |
| self.antlion_pyz.clone().into_os_string().into_string().unwrap(), |
| "--config".to_string(), |
| config.into_os_string().into_string().unwrap(), |
| ]; |
| |
| for test_case in self.test_cases.iter() { |
| args.push("--test_case".to_string()); |
| args.push(test_case.clone()); |
| } |
| |
| println!( |
| "Launching antlion to run: \"{} {}\"\n", |
| &self.python_bin, |
| args.iter().format(" "), |
| ); |
| |
| let status = Command::new(&self.python_bin) |
| .args(args) |
| .status() |
| .context("Failed to execute antlion")?; |
| |
| Ok(ExitStatus::from(status)) |
| } |
| } |
| |
| /// Describes the result of a child process after it has terminated. |
| pub(crate) enum ExitStatus { |
| /// Process terminated without error. |
| Ok, |
| /// Process terminated with a non-zero status code. |
| Err(i32), |
| /// Process was interrupted by a signal. |
| Interrupt(Option<i32>), |
| } |
| |
| impl From<std::process::ExitStatus> for ExitStatus { |
| fn from(status: std::process::ExitStatus) -> Self { |
| match status.code() { |
| Some(0) => ExitStatus::Ok, |
| Some(code) => ExitStatus::Err(code), |
| None if cfg!(target_os = "unix") => ExitStatus::Interrupt(status.signal()), |
| None => ExitStatus::Interrupt(None), |
| } |
| } |
| } |
| |
| impl Into<ExitCode> for ExitStatus { |
| fn into(self) -> ExitCode { |
| match self { |
| ExitStatus::Ok => ExitCode::SUCCESS, |
| ExitStatus::Err(code) => { |
| let code = match u8::try_from(code) { |
| Ok(c) => c, |
| Err(_) => 1, |
| }; |
| ExitCode::from(code) |
| } |
| ExitStatus::Interrupt(_) => ExitCode::FAILURE, |
| } |
| } |
| } |