#[ allow( unused_imports ) ]
use super::*;
use the_module::CapturedOutput;
#[ test ]
fn captured_output_exit_ok_true_when_zero()
{
let out = CapturedOutput { exit_status : 0, stdout : vec![], stderr : vec![] };
assert!( out.exit_ok() );
}
#[ test ]
fn captured_output_exit_ok_false_when_nonzero()
{
let out = CapturedOutput { exit_status : 1, stdout : vec![], stderr : vec![] };
assert!( !out.exit_ok() );
let out_neg = CapturedOutput { exit_status : -1, stdout : vec![], stderr : vec![] };
assert!( !out_neg.exit_ok() );
}
#[ test ]
fn captured_output_stdout_str_decodes_bytes()
{
let out = CapturedOutput
{
exit_status : 0,
stdout : b"hello\n".to_vec(),
stderr : vec![],
};
assert_eq!( out.stdout_str(), "hello\n" );
}
#[ test ]
fn captured_output_stderr_str_decodes_bytes()
{
let out = CapturedOutput
{
exit_status : 1,
stdout : vec![],
stderr : b"error message\n".to_vec(),
};
assert_eq!( out.stderr_str(), "error message\n" );
}
#[ test ]
fn captured_output_stdout_eq_exact_match()
{
let out = CapturedOutput
{
exit_status : 0,
stdout : b"hello\n".to_vec(),
stderr : vec![],
};
assert!( out.stdout_eq( "hello\n" ) );
assert!( !out.stdout_eq( "hello" ) ); assert!( !out.stdout_eq( "world\n" ) ); }
#[ test ]
fn captured_output_stdout_contains_substring()
{
let out = CapturedOutput
{
exit_status : 0,
stdout : b"hello world\n".to_vec(),
stderr : vec![],
};
assert!( out.stdout_contains( "hello" ) );
assert!( out.stdout_contains( "world" ) );
assert!( !out.stdout_contains( "missing" ) );
}
#[ test ]
fn captured_output_stderr_contains_substring()
{
let out = CapturedOutput
{
exit_status : 1,
stdout : vec![],
stderr : b"error: something failed\n".to_vec(),
};
assert!( out.stderr_contains( "error" ) );
assert!( !out.stderr_contains( "success" ) );
}
#[ test ]
fn run_source_hello_world()
{
use the_module::run_source;
let output = run_source( r#"fn main() { println!( "hello" ); }"# )
.expect( "run_source failed" );
output.assert_exit_ok();
output.assert_stdout_eq( "hello\n" );
}
#[ test ]
fn run_source_compilation_error_gives_nonzero_exit()
{
use the_module::run_source;
let output = run_source( "this is not valid rust" )
.expect( "run_source must return Ok even for compilation errors" );
assert!
(
!output.exit_ok(),
"expected non-zero exit status for invalid Rust; got exit_status={}",
output.exit_status,
);
assert!
(
!output.stderr_str().is_empty(),
"expected compiler diagnostics in stderr for invalid Rust"
);
}
#[ test ]
fn run_capture_false_exits_ok_with_empty_buffers()
{
use the_module::{ run, Plan, RunOptions };
let opts = RunOptions { capture : false, ..Default::default() };
let plan = Plan::former()
.program()
.source()
.file_path( "src/main.rs".to_string() )
.data( r#"fn main() { println!( "forwarded" ); }"#.to_string() )
.end()
.end()
.run_options( opts )
.form();
let output = run( plan ).expect( "run failed in forwarding mode" );
output.assert_exit_ok();
assert!
(
output.stdout.is_empty(),
"stdout buffer must be empty in forwarding mode; got {} bytes",
output.stdout.len(),
);
assert!
(
output.stderr.is_empty(),
"stderr buffer must be empty in forwarding mode; got {} bytes",
output.stderr.len(),
);
}
#[ test ]
fn run_file_executes_source_from_disk()
{
use the_module::run_file;
use std::path::PathBuf;
let pid = std::process::id();
let tmp : PathBuf = std::env::temp_dir().join( format!( "program_tools_run_file_{pid}" ) );
std::fs::create_dir_all( &tmp ).expect( "failed to create tmp dir" );
let path = tmp.join( "hello.rs" );
std::fs::write( &path, r#"fn main() { println!( "from file" ); }"# )
.expect( "failed to write source file" );
let output = run_file( &path ).expect( "run_file failed" );
std::fs::remove_dir_all( &tmp ).ok();
output.assert_exit_ok();
output.assert_stdout_contains( "from file" );
}
#[ test ]
fn run_file_err_when_source_missing()
{
use the_module::run_file;
let result = run_file( "/nonexistent/path/does_not_exist_program_tools.rs" );
assert!( result.is_err(), "expected Err for a missing source file" );
}
#[ test ]
fn run_project_err_when_manifest_absent()
{
use the_module::{ run_project, RunOptions };
use std::path::PathBuf;
let pid = std::process::id();
let tmp : PathBuf = std::env::temp_dir()
.join( format!( "program_tools_run_project_{pid}" ) );
std::fs::create_dir_all( &tmp ).expect( "failed to create tmp dir" );
std::fs::remove_file( tmp.join( "Cargo.toml" ) ).ok();
let result = run_project( &tmp, &RunOptions::default() );
std::fs::remove_dir_all( &tmp ).ok();
assert!( result.is_err(), "expected Err when no Cargo.toml is present" );
let err_msg = result.unwrap_err().to_string();
assert!
(
err_msg.contains( "no Cargo.toml" ),
"expected 'no Cargo.toml' in error message, got: {err_msg}",
);
}
#[ test ]
fn run_timeout_fires()
{
use the_module::{ run, Plan, RunOptions };
let opts = RunOptions { timeout_ms : Some( 200 ), ..Default::default() };
let plan = Plan::former()
.program()
.source()
.file_path( "src/main.rs".to_string() )
.data( "fn main() { loop {} }".to_string() )
.end()
.end()
.run_options( opts )
.form();
let result = run( plan );
assert!( result.is_err(), "expected Err when timeout fires, got: {result:?}" );
let err_msg = result.unwrap_err().to_string();
assert!
(
err_msg.contains( "timed out" ),
"expected 'timed out' in error message, got: {err_msg}",
);
}
#[ test ]
fn run_timeout_does_not_fire()
{
use the_module::{ run, Plan, RunOptions };
let opts = RunOptions { timeout_ms : Some( 60_000 ), ..Default::default() };
let plan = Plan::former()
.program()
.source()
.file_path( "src/main.rs".to_string() )
.data( r#"fn main() { println!( "hello" ); }"#.to_string() )
.end()
.end()
.run_options( opts )
.form();
let output = run( plan ).expect( "run failed unexpectedly with generous timeout" );
output.assert_exit_ok();
output.assert_stdout_contains( "hello" );
}
#[ test ]
fn run_source_preserves_exit_code_42()
{
use the_module::run_source;
let output = run_source( "fn main() { std::process::exit( 42 ); }" )
.expect( "run_source must return Ok even for non-zero exit" );
assert_eq!
(
output.exit_status,
42,
"expected exit_status=42; got {}",
output.exit_status,
);
assert!( !output.exit_ok() );
}
#[ test ]
fn run_source_stderr_captured()
{
use the_module::run_source;
let output = run_source
(
r#"fn main() { eprintln!( "from stderr" ); println!( "from stdout" ); }"#,
)
.expect( "run_source failed" );
output.assert_exit_ok();
output.assert_stdout_contains( "from stdout" );
output.assert_stderr_contains( "from stderr" );
}
#[ test ]
fn run_source_multiline_stdout()
{
use the_module::run_source;
let output = run_source
(
r#"fn main() { println!( "line1" ); println!( "line2" ); println!( "line3" ); }"#,
)
.expect( "run_source failed" );
output.assert_exit_ok();
let stdout = output.stdout_str();
assert!( stdout.contains( "line1" ) );
assert!( stdout.contains( "line2" ) );
assert!( stdout.contains( "line3" ) );
let pos1 = stdout.find( "line1" ).unwrap();
let pos3 = stdout.find( "line3" ).unwrap();
assert!( pos1 < pos3, "lines must appear in order" );
}
#[ test ]
fn run_file_invalid_rust_gives_nonzero_exit()
{
use the_module::run_file;
let pid = std::process::id();
let tmp = std::env::temp_dir().join( format!( "program_tools_rf_invalid_{pid}" ) );
std::fs::create_dir_all( &tmp ).expect( "failed to create tmp dir" );
let path = tmp.join( "broken.rs" );
std::fs::write( &path, "fn main() { this_is_not_valid_rust }" ).expect( "write failed" );
let output = run_file( &path ).expect( "run_file must return Ok even on compile error" );
std::fs::remove_dir_all( &tmp ).ok();
assert!
(
!output.exit_ok(),
"expected non-zero exit for invalid Rust; got {}",
output.exit_status,
);
}
#[ test ]
fn run_timeout_zero_fires_immediately()
{
use the_module::{ run, Plan, RunOptions };
let opts = RunOptions { timeout_ms : Some( 0 ), ..Default::default() };
let plan = Plan::former()
.program()
.source()
.file_path( "src/main.rs".to_string() )
.data( r#"fn main() { println!( "never reached" ); }"#.to_string() )
.end()
.end()
.run_options( opts )
.form();
let result = run( plan );
assert!( result.is_err(), "timeout_ms=0 must fire immediately; got: {result:?}" );
let msg = result.unwrap_err().to_string();
assert!( msg.contains( "timed out" ), "expected 'timed out' in error; got: {msg}" );
}
#[ test ]
fn run_timeout_forwarding_mode_fires()
{
use the_module::{ run, Plan, RunOptions };
let opts = RunOptions { timeout_ms : Some( 1 ), capture : false, ..Default::default() };
let plan = Plan::former()
.program()
.source()
.file_path( "src/main.rs".to_string() )
.data( r#"fn main() { println!( "forwarded" ); }"#.to_string() )
.end()
.end()
.run_options( opts )
.form();
let result = run( plan );
assert!( result.is_err(), "forwarding mode timeout must fire; got: {result:?}" );
let msg = result.unwrap_err().to_string();
assert!( msg.contains( "timed out" ), "expected 'timed out' in error; got: {msg}" );
}
#[ test ]
fn run_env_var_no_equals_ignored()
{
use the_module::{ run, Plan, RunOptions };
let opts = RunOptions { env_vars : vec![ "NO_EQUALS_VAR".to_string() ], ..Default::default() };
let plan = Plan::former()
.program()
.source()
.file_path( "src/main.rs".to_string() )
.data( r#"fn main() { println!( "ok" ); }"#.to_string() )
.end()
.end()
.run_options( opts )
.form();
let output = run( plan ).expect( "run must succeed despite malformed env_var entry" );
output.assert_exit_ok();
output.assert_stdout_contains( "ok" );
}
#[ test ]
fn run_env_var_passed_to_script()
{
use the_module::{ run, Plan, RunOptions };
let opts = RunOptions
{
env_vars : vec![ "PT_TEST_VAR=hello_env".to_string() ],
..Default::default()
};
let plan = Plan::former()
.program()
.source()
.file_path( "src/main.rs".to_string() )
.data
(
r#"fn main() { println!( "{}", std::env::var( "PT_TEST_VAR" ).unwrap_or_default() ); }"#
.to_string()
)
.end()
.end()
.run_options( opts )
.form();
let output = run( plan ).expect( "run failed" );
output.assert_exit_ok();
output.assert_stdout_contains( "hello_env" );
}
#[ test ]
fn run_env_var_equals_in_value_preserved()
{
use the_module::{ run, Plan, RunOptions };
let opts = RunOptions
{
env_vars : vec![ "PT_EQ_VAR=hello=world".to_string() ],
..Default::default()
};
let plan = Plan::former()
.program()
.source()
.file_path( "src/main.rs".to_string() )
.data
(
r#"fn main() { println!( "{}", std::env::var( "PT_EQ_VAR" ).unwrap_or_default() ); }"#
.to_string()
)
.end()
.end()
.run_options( opts )
.form();
let output = run( plan ).expect( "run failed" );
output.assert_exit_ok();
output.assert_stdout_contains( "hello=world" );
}
#[ test ]
fn run_cleanup_false_leaves_workspace()
{
use the_module::{ run, Plan, RunOptions };
use std::collections::HashSet;
let temp_dir = std::env::temp_dir();
let pid = std::process::id();
let tid = the_module::thread_id_str();
let thread_prefix = format!( "program_tools_{pid}_{tid}_" );
let snapshot_dirs = | | -> HashSet< String >
{
std::fs::read_dir( &temp_dir )
.expect( "failed to read temp dir" )
.filter_map( core::result::Result::ok )
.filter_map( | e | e.file_name().into_string().ok() )
.filter( | name | name.starts_with( &thread_prefix ) )
.collect()
};
let before = snapshot_dirs();
let opts = RunOptions { cleanup : false, timeout_ms : Some( 0 ), ..Default::default() };
let plan = Plan::former()
.program()
.source()
.file_path( "src/main.rs".to_string() )
.data( "fn main() {}".to_string() )
.end()
.end()
.run_options( opts )
.form();
let _ = run( plan );
let after = snapshot_dirs();
let new_dirs : Vec< String > = after.difference( &before ).cloned().collect();
for name in &new_dirs
{
std::fs::remove_dir_all( temp_dir.join( name ) ).ok();
}
assert!
(
!new_dirs.is_empty(),
"expected at least one workspace directory to remain when cleanup=false; none found",
);
}