#![cfg_attr(coverage_nightly, coverage(off))]
use std::io;
use std::path::Path;
use std::process::{Command, Output};
use std::time::Duration;
pub const DEFAULT_TIMEOUT_SECS: u64 = 30;
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn run_with_timeout(
program: &str,
args: &[&str],
current_dir: &Path,
timeout_secs: Option<u64>,
) -> io::Result<Option<Output>> {
let timeout = Duration::from_secs(timeout_secs.unwrap_or(DEFAULT_TIMEOUT_SECS));
let mut child = Command::new(program)
.args(args)
.current_dir(current_dir)
.spawn()?;
let start = std::time::Instant::now();
loop {
match child.try_wait()? {
Some(status) => {
let output = Output {
status,
stdout: Vec::new(), stderr: Vec::new(),
};
return Ok(Some(output));
}
None => {
if start.elapsed() > timeout {
let _ = child.kill();
let _ = child.wait();
return Ok(None); }
std::thread::sleep(Duration::from_millis(100));
}
}
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn run_clippy(project_path: &Path, _timeout_secs: Option<u64>) -> io::Result<Option<Output>> {
let mut cmd = Command::new("cargo");
cmd.arg("clippy")
.arg("--all-targets")
.arg("--")
.arg("-D")
.arg("warnings")
.current_dir(project_path);
match cmd.output() {
Ok(output) => Ok(Some(output)),
Err(e) => Err(e),
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn run_rustfmt_check(
project_path: &Path,
_timeout_secs: Option<u64>,
) -> io::Result<Option<Output>> {
let mut cmd = Command::new("cargo");
cmd.arg("fmt")
.arg("--")
.arg("--check")
.current_dir(project_path);
match cmd.output() {
Ok(output) => Ok(Some(output)),
Err(e) => Err(e),
}
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "path_exists")]
pub fn run_cargo_audit(
project_path: &Path,
_timeout_secs: Option<u64>,
) -> io::Result<Option<Output>> {
let mut cmd = Command::new("cargo");
cmd.arg("audit").arg("--json").current_dir(project_path);
match cmd.output() {
Ok(output) => Ok(Some(output)),
Err(e) => Err(e),
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_command_runner_exists() {
assert_eq!(DEFAULT_TIMEOUT_SECS, 30);
}
#[test]
fn test_default_timeout_value() {
assert_eq!(DEFAULT_TIMEOUT_SECS, 30);
}
#[test]
fn test_run_with_timeout_success() {
let temp_dir = TempDir::new().unwrap();
let result = run_with_timeout("true", &[], temp_dir.path(), Some(5));
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.is_some());
let output = output.unwrap();
assert!(output.status.success());
}
#[test]
fn test_run_with_timeout_failure() {
let temp_dir = TempDir::new().unwrap();
let result = run_with_timeout("false", &[], temp_dir.path(), Some(5));
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.is_some());
let output = output.unwrap();
assert!(!output.status.success());
}
#[test]
fn test_run_with_timeout_nonexistent_command() {
let temp_dir = TempDir::new().unwrap();
let result = run_with_timeout(
"this_command_does_not_exist_12345",
&[],
temp_dir.path(),
Some(1),
);
assert!(result.is_err());
}
#[test]
fn test_run_with_timeout_with_args() {
let temp_dir = TempDir::new().unwrap();
fs::write(temp_dir.path().join("test.txt"), "hello").unwrap();
let result = run_with_timeout("ls", &["-la"], temp_dir.path(), Some(5));
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.is_some());
}
#[test]
fn test_run_with_timeout_default_timeout() {
let temp_dir = TempDir::new().unwrap();
let result = run_with_timeout("true", &[], temp_dir.path(), None);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.is_some());
}
#[test]
fn test_run_clippy_returns_result() {
let temp_dir = TempDir::new().unwrap();
let result = run_clippy(temp_dir.path(), None);
assert!(result.is_ok());
}
#[test]
fn test_run_rustfmt_check_returns_result() {
let temp_dir = TempDir::new().unwrap();
let result = run_rustfmt_check(temp_dir.path(), None);
assert!(result.is_ok());
}
#[test]
fn test_run_cargo_audit_returns_result() {
let temp_dir = TempDir::new().unwrap();
let result = run_cargo_audit(temp_dir.path(), None);
assert!(result.is_ok());
}
#[test]
fn test_run_with_timeout_working_directory() {
let temp_dir = TempDir::new().unwrap();
let subdir = temp_dir.path().join("subdir");
fs::create_dir_all(&subdir).unwrap();
fs::write(subdir.join("marker.txt"), "test").unwrap();
let result = run_with_timeout("pwd", &[], &subdir, Some(5));
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.is_some());
assert!(output.unwrap().status.success());
}
#[test]
#[ignore = "Times out - runs full clippy on project"]
fn test_run_clippy_with_valid_project() {
let project_path = std::env::current_dir().unwrap();
if !project_path.join("Cargo.toml").exists() {
return;
}
let result = run_clippy(&project_path, Some(60));
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.is_some());
}
#[test]
#[ignore = "Times out - runs full rustfmt on project"]
fn test_run_rustfmt_with_valid_project() {
let project_path = std::env::current_dir().unwrap();
if !project_path.join("Cargo.toml").exists() {
return;
}
let result = run_rustfmt_check(&project_path, Some(30));
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.is_some());
}
#[test]
fn test_run_with_timeout_timeout_behavior() {
let temp_dir = TempDir::new().unwrap();
let result = run_with_timeout("sleep", &["0.1"], temp_dir.path(), Some(5));
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.is_some());
}
#[test]
fn test_run_with_timeout_very_short_timeout() {
let temp_dir = TempDir::new().unwrap();
let result = run_with_timeout("true", &[], temp_dir.path(), Some(1));
assert!(result.is_ok());
}
#[test]
fn test_command_runner_output_structure() {
let temp_dir = TempDir::new().unwrap();
let result = run_with_timeout("true", &[], temp_dir.path(), Some(5));
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.is_some());
let output = output.unwrap();
assert!(output.status.success());
assert!(output.stdout.is_empty());
assert!(output.stderr.is_empty());
}
#[test]
fn test_run_with_multiple_args() {
let temp_dir = TempDir::new().unwrap();
let result = run_with_timeout(
"echo",
&["hello", "world", "test"],
temp_dir.path(),
Some(5),
);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.is_some());
}
}