From 7ae7f9d89e3c2f921a2c14dee732f510cbecf79b Mon Sep 17 00:00:00 2001 From: Tip ten Brink <75669206+tiptenbrink@users.noreply.github.com> Date: Wed, 1 May 2024 08:55:43 +0200 Subject: [PATCH] stdinput now tested and working --- src/next/api.rs | 10 +++--- src/next/process.rs | 74 +++++++++++++-------------------------------- src/next/run.rs | 9 ++++-- tests/run_tests.rs | 16 ++++++++++ 4 files changed, 50 insertions(+), 59 deletions(-) diff --git a/src/next/api.rs b/src/next/api.rs index d5c0bd3..9a13989 100644 --- a/src/next/api.rs +++ b/src/next/api.rs @@ -1,4 +1,4 @@ -use super::run::run_command as inner_run_command; +use super::run::run_command_input as inner_run_command; use crate::state::CliEnvState; use color_eyre::eyre::Report; use thiserror::Error as ThisError; @@ -45,7 +45,8 @@ impl Default for GlobalArguments { pub struct RunArguments { pub executable: Option, pub variables: Vec, - pub archive: Option + pub archive: Option, + pub input_bytes: Option> } impl Default for RunArguments { @@ -53,7 +54,8 @@ impl Default for RunArguments { RunArguments { executable: None, variables: Vec::new(), - archive: None + archive: None, + input_bytes: None } } } @@ -79,7 +81,7 @@ pub struct CommandError { } pub fn run_command(global_args: GlobalArguments, args: RunArguments) -> Result { - inner_run_command(global_args.into(), args.executable, args.variables, args.archive).map_err(|e| + inner_run_command(global_args.into(), args.executable, args.variables, args.archive, args.input_bytes).map_err(|e| CommandError { msg: "An error occurred in the inner application layer.".to_owned(), source: e diff --git a/src/next/process.rs b/src/next/process.rs index 71c5cb4..962df0b 100644 --- a/src/next/process.rs +++ b/src/next/process.rs @@ -1,53 +1,48 @@ -use color_eyre::eyre::{Context, ContextCompat, Report}; -use std::io::{stderr, stdout, Read, Write}; +use color_eyre::eyre::{Context, Report}; +use std::io::{stdout, Read, Write}; use std::process::ExitStatus; use std::str; use duct::cmd; -use std::thread::sleep; -use std::time::Duration; -/// This is purely application-level code, hence you would never want to reference it as a library. -/// For this reason we do not really care about the exact errors and need not match on them. use std::{ collections::HashMap, - io::{BufRead, BufReader}, - path::Path, - process::{Command as Cmd, Stdio}, + io::BufReader, + path::Path }; -use tracing::{debug, span, Level}; - -/// Read output bytes into a string and trim any whitespace at the end. -fn process_out(bytes: Vec) -> Result { - let mut output_string = String::from_utf8(bytes) - .wrap_err("Error occurred decoding process output bytes as UTF-8.")?; - // We use truncate to prevent having to copy the string, which could be quite large as it's - // the output of a whole program - let trim_len = output_string.trim_end().len(); - output_string.truncate(trim_len); - - Ok(output_string) -} +use tracing::{span, Level}; pub struct EntrypointOut { pub out: String, pub exit: ExitStatus } -/// Runs the entrypoint, sending the entrypoint's stdout and stderr to stdout +/// Runs the entrypoint, sending the entrypoint's stdout and stderr to stdout. It adds the provided envs to +/// the envs of the tidploy process. `input_bytes` is useful mostly for testing, if set to None then the +/// child process will just inherit the stdin of the tidploy process. pub(crate) fn run_entrypoint>( entrypoint_dir: P, entrypoint: &str, envs: HashMap, + input_bytes: Option> ) -> Result { println!("Running {}!", &entrypoint); let program_path = entrypoint_dir.as_ref().join(entrypoint); + // Use parent process env variables as base let mut combined_envs: HashMap<_, _> = std::env::vars().collect(); combined_envs.extend(envs); let cmd_expr = cmd(&program_path, Vec::::new()) .dir(entrypoint_dir.as_ref()) - .full_env(&combined_envs); + .full_env(&combined_envs) + .stderr_to_stdout(); + + // This is useful for testing input + let cmd_expr = if let Some(input_bytes) = input_bytes { + cmd_expr.stdin_bytes(input_bytes) + } else { + cmd_expr + }; - let reader = cmd_expr.stderr_to_stdout().reader()?; + let reader = cmd_expr.reader()?; let entry_span = span!(Level::DEBUG, "entrypoint", path = program_path.to_str()); let _enter = entry_span.enter(); @@ -79,31 +74,4 @@ pub(crate) fn run_entrypoint>( out, exit }) -} - -// #[cfg(test)] -// mod tests { -// use std::env; - -// use crate::git::git_root_dir; - -// use super::*; - -// #[test] -// fn test_run_entrypoint() { -// let current_dir = env::current_dir().unwrap(); -// let project_dir = git_root_dir(¤t_dir).unwrap(); -// let project_path = Path::new(&project_dir).join("examples").join("run"); - -// run_entrypoint(project_path, "do_echo.sh", HashMap::new()).unwrap(); -// } - -// #[test] -// fn test_spawn() { -// let current_dir = env::current_dir().unwrap(); -// let project_dir = git_root_dir(¤t_dir).unwrap(); -// let project_path = Path::new(&project_dir).join("examples").join("run"); - -// run_entrypoint(project_path, "do_echo.sh", HashMap::new()).unwrap(); -// } -// } +} \ No newline at end of file diff --git a/src/next/run.rs b/src/next/run.rs index 127da2c..3779175 100644 --- a/src/next/run.rs +++ b/src/next/run.rs @@ -7,8 +7,13 @@ use crate::{archives::extract_archive, filesystem::get_dirs, state::{create_stat use super::{process::{run_entrypoint, EntrypointOut}, state::create_state_run}; -#[instrument(name = "run", level = "debug", skip_all)] + pub(crate) fn run_command(cli_state: CliEnvState, executable: Option, variables: Vec, archive: Option) -> Result { + run_command_input(cli_state, executable, variables, archive, None) +} + +#[instrument(name = "run", level = "debug", skip_all)] +pub(crate) fn run_command_input(cli_state: CliEnvState, executable: Option, variables: Vec, archive: Option, input_bytes: Option>) -> Result { // Only loads archive if it is given, otherwise path is None // let state = if let Some(archive) = archive { // let cache_dir = get_dirs().cache.as_path(); @@ -47,5 +52,5 @@ pub(crate) fn run_command(cli_state: CliEnvState, executable: Option, va // let state = extra_envs(state); - run_entrypoint(state.deploy_dir(), &state.exe_name, state.envs) + run_entrypoint(state.deploy_dir(), &state.exe_name, state.envs, input_bytes) } \ No newline at end of file diff --git a/tests/run_tests.rs b/tests/run_tests.rs index b579c88..9141388 100644 --- a/tests/run_tests.rs +++ b/tests/run_tests.rs @@ -47,5 +47,21 @@ fn test_stdout_stderr() -> Result<(), CommandError> { assert_eq!("hello1\nhello2\nerr1\nerr2\nhello3\n", output.out); + Ok(()) +} + +#[test] +fn test_input() -> Result<(), CommandError> { + let mut global_args = GlobalArguments::default(); + let mut args = RunArguments::default(); + global_args.context = Some(StateContext::None); + args.executable = Some("examples/run/example_input.sh".to_owned()); + args.input_bytes = Some("foo".into()); + + let output = run_command(global_args, args)?; + assert!(output.exit.success()); + + assert_eq!("You entered: foo\n", output.out); + Ok(()) } \ No newline at end of file