diff --git a/core/src/util.rs b/core/src/util.rs index 14f570b2..b7eb9c3d 100644 --- a/core/src/util.rs +++ b/core/src/util.rs @@ -4,7 +4,9 @@ use anyhow::{Context, Result}; use std::{ env::current_dir, path::{Path, PathBuf}, + process::Command, }; +use subprocess::Exec; pub struct RemoveFile(pub PathBuf); @@ -16,6 +18,22 @@ impl Drop for RemoveFile { } } +/// Constructs a [`subprocess::Exec`] from a [`std::process::Command`]. +pub fn exec_from_command(command: &Command) -> Exec { + let mut exec = Exec::cmd(command.get_program()).args(&command.get_args().collect::>()); + for (key, val) in command.get_envs() { + if let Some(val) = val { + exec = exec.env(key, val); + } else { + exec = exec.env_remove(key); + } + } + if let Some(path) = command.get_current_dir() { + exec = exec.cwd(path); + } + exec +} + /// Strips the current directory from the given path. /// /// If the given path is not a child of the current directory, the path is diff --git a/frameworks/src/running.rs b/frameworks/src/running.rs index d7c4a8e5..5025ad89 100644 --- a/frameworks/src/running.rs +++ b/frameworks/src/running.rs @@ -1,8 +1,10 @@ -use super::{ts, utils, OutputAccessors, OutputStrippedOfAnsiScapes, RunHigh}; +use super::{ts, OutputAccessors, OutputStrippedOfAnsiScapes, RunHigh}; use anyhow::{anyhow, Error, Result}; use bstr::{io::BufReadExt, BStr}; use log::debug; -use necessist_core::{framework::Postprocess, source_warn, LightContext, Span, WarnFlags, Warning}; +use necessist_core::{ + framework::Postprocess, source_warn, util, LightContext, Span, WarnFlags, Warning, +}; use std::{cell::RefCell, path::Path, process::Command, rc::Rc}; use subprocess::{Exec, NullFile, Redirection}; @@ -80,7 +82,7 @@ impl RunHigh for RunAdapter { command.args(&context.opts.args); command.args(final_args); - let mut exec = utils::exec_from_command(&command); + let mut exec = util::exec_from_command(&command); if init_f_test.is_some() { exec = exec.stdout(Redirection::Pipe); } else { diff --git a/frameworks/src/ts/mocha/mod.rs b/frameworks/src/ts/mocha/mod.rs index 00e24cd8..1c510e8c 100644 --- a/frameworks/src/ts/mocha/mod.rs +++ b/frameworks/src/ts/mocha/mod.rs @@ -1,13 +1,13 @@ use crate::{ - utils, AbstractTypes, GenericVisitor, MaybeNamed, Named, OutputAccessors, - OutputStrippedOfAnsiScapes, ParseLow, Spanned, WalkDirResult, + AbstractTypes, GenericVisitor, MaybeNamed, Named, OutputAccessors, OutputStrippedOfAnsiScapes, + ParseLow, Spanned, WalkDirResult, }; use anyhow::{anyhow, Result}; use if_chain::if_chain; use log::debug; use necessist_core::{ - framework::Postprocess, source_warn, LightContext, LineColumn, SourceFile, Span, WarnFlags, - Warning, + framework::Postprocess, source_warn, util, LightContext, LineColumn, SourceFile, Span, + WarnFlags, Warning, }; use once_cell::sync::Lazy; use regex::Regex; @@ -156,7 +156,7 @@ impl Mocha { return Ok(None); } - let mut exec = utils::exec_from_command(command); + let mut exec = util::exec_from_command(command); exec = exec.stdout(NullFile); exec = exec.stderr(NullFile); diff --git a/frameworks/src/utils.rs b/frameworks/src/utils.rs index 0a616fa1..d1fecd85 100644 --- a/frameworks/src/utils.rs +++ b/frameworks/src/utils.rs @@ -1,22 +1,6 @@ use anyhow::{Context, Result}; use assert_cmd::output::OutputError; use std::process::{Command, ExitStatus, Output}; -use subprocess::Exec; - -pub fn exec_from_command(command: &Command) -> Exec { - let mut exec = Exec::cmd(command.get_program()).args(&command.get_args().collect::>()); - for (key, val) in command.get_envs() { - if let Some(val) = val { - exec = exec.env(key, val); - } else { - exec = exec.env_remove(key); - } - } - if let Some(path) = command.get_current_dir() { - exec = exec.cwd(path); - } - exec -} pub trait OutputStrippedOfAnsiScapes { fn output_stripped_of_ansi_escapes(&mut self) -> Result; diff --git a/necessist/tests/general.rs b/necessist/tests/general.rs index 3769f429..d5451356 100644 --- a/necessist/tests/general.rs +++ b/necessist/tests/general.rs @@ -85,13 +85,16 @@ examples/failure/tests/a.rs: Warning: dry run failed: code=101 .stdout(predicate::eq("2 candidates in 2 test files\n")); } -// smoelius: Apperently, sending a ctrl-c on Windows is non-trivial: +// smoelius: Apparently, sending a ctrl-c on Windows is non-trivial: // https://stackoverflow.com/questions/813086/can-i-send-a-ctrl-c-sigint-to-an-application-on-windows +// smoelius: Sending a ctrl-c allows the process to clean up after itself, e.g., to undo file +// rewrites. #[cfg(not(windows))] #[test] fn resume_following_ctrl_c() { use similar_asserts::SimpleDiff; - use std::{process::Stdio, thread::sleep, time::Duration}; + use std::io::{BufRead, BufReader, Read}; + use subprocess::Redirection; const ROOT: &str = "examples/basic"; @@ -105,17 +108,30 @@ fn resume_following_ctrl_c() { let _lock = BASIC_MUTEX.lock().unwrap(); - let child = command().stderr(Stdio::piped()).spawn().unwrap(); - - sleep(Duration::from_secs(1)); - - kill().arg(child.id().to_string()).assert().success(); - - let output = child.wait_with_output().unwrap(); - - let stderr = String::from_utf8(output.stderr).unwrap(); + let exec = util::exec_from_command(&command()) + .stdout(Redirection::Pipe) + .stderr(Redirection::Pipe); + let mut popen = exec.popen().unwrap(); + + let stdout = popen.stdout.as_ref().unwrap(); + let reader = BufReader::new(stdout); + let _: String = reader + .lines() + .map(Result::unwrap) + .find(|line| line == "examples/basic/src/lib.rs:4:5-4:12: `n += 1;` passed") + .unwrap(); + + let pid = popen.pid().unwrap(); + kill().arg(pid.to_string()).assert().success(); + + let mut stderr = popen.stderr.as_ref().unwrap(); + let mut buf = Vec::new(); + let _ = stderr.read_to_end(&mut buf).unwrap(); + let stderr = String::from_utf8(buf).unwrap(); assert!(stderr.ends_with("Ctrl-C detected\n"), "{stderr:?}"); + let _ = popen.wait().unwrap(); + let necessist_db = PathBuf::from("..").join(ROOT).join("necessist.db"); let _remove_file = util::RemoveFile(necessist_db);