mod cli_binary_test_helpers;
use cli_binary_test_helpers::{ run_cli, run_cli_with_env };
use std::process::Command;
#[ cfg( unix ) ]
use std::os::unix::fs::PermissionsExt;
#[ test ]
fn ec1_timeout_help_listed()
{
let out = run_cli( &[ "--help" ] );
assert!( out.status.success(), "clr --help must exit 0" );
let stdout = String::from_utf8_lossy( &out.stdout );
assert!(
stdout.contains( "--timeout" ),
"`clr --help` must list --timeout for run/ask. Got:\n{stdout}"
);
}
#[ test ]
fn ec2_timeout_zero_dry_run()
{
let out = run_cli( &[ "--timeout", "0", "--dry-run", "task" ] );
assert!(
out.status.success(),
"exit must be 0. stderr: {}",
String::from_utf8_lossy( &out.stderr )
);
}
#[ test ]
fn ec3_timeout_nonzero_dry_run()
{
let out = run_cli( &[ "--timeout", "30", "--dry-run", "task" ] );
assert!(
out.status.success(),
"exit must be 0. stderr: {}",
String::from_utf8_lossy( &out.stderr )
);
}
#[ test ]
fn ec4_clr_timeout_env_var_accepted()
{
let out = run_cli_with_env(
&[ "--dry-run", "task" ],
&[ ( "CLR_TIMEOUT", "10" ) ],
);
assert!(
out.status.success(),
"CLR_TIMEOUT env var must be accepted. stderr: {}",
String::from_utf8_lossy( &out.stderr )
);
}
#[ test ]
fn ec5_timeout_cli_wins_over_env()
{
let out = run_cli_with_env(
&[ "--timeout", "60", "--dry-run", "task" ],
&[ ( "CLR_TIMEOUT", "5" ) ],
);
assert!(
out.status.success(),
"CLI value must win over env var. stderr: {}",
String::from_utf8_lossy( &out.stderr )
);
}
#[ test ]
fn ec6_clr_timeout_invalid_ignored()
{
let out = run_cli_with_env(
&[ "--dry-run", "task" ],
&[ ( "CLR_TIMEOUT", "abc" ) ],
);
assert!(
out.status.success(),
"invalid CLR_TIMEOUT must be silently ignored. stderr: {}",
String::from_utf8_lossy( &out.stderr )
);
}
#[ cfg( unix ) ]
#[ test ]
fn ec7_timeout_fires_kills_subprocess()
{
let tmp = tempfile::tempdir().expect( "create temp dir" );
let fake = tmp.path().join( "claude" );
std::fs::write( &fake, b"#!/bin/sh\nsleep 30\n" ).expect( "write fake claude" );
std::fs::set_permissions( &fake, std::fs::Permissions::from_mode( 0o755 ) )
.expect( "chmod fake claude" );
let old_path = std::env::var( "PATH" ).unwrap_or_default();
let new_path = format!( "{}:{old_path}", tmp.path().display() );
let bin = env!( "CARGO_BIN_EXE_clr" );
let start = std::time::Instant::now();
let out = Command::new( bin )
.args( [ "-p", "--timeout", "1", "--max-sessions", "0", "x" ] )
.env( "PATH", &new_path )
.output()
.expect( "invoke clr" );
let elapsed = start.elapsed();
assert_eq!(
out.status.code(),
Some( 2 ),
"exit must be 2 on timeout. Got: {:?}", out.status.code()
);
assert!(
elapsed.as_secs() < 5,
"watchdog must fire within ~2s; elapsed {elapsed:?} suggests timeout not working"
);
let stderr = String::from_utf8_lossy( &out.stderr );
assert!(
stderr.to_lowercase().contains( "timeout" ),
"stderr must contain 'timeout'. Got:\n{stderr}"
);
}
#[ cfg( unix ) ]
#[ test ]
fn ec8_no_timeout_when_subprocess_exits_fast()
{
let tmp = tempfile::tempdir().expect( "create temp dir" );
let fake = tmp.path().join( "claude" );
std::fs::write( &fake, b"#!/bin/sh\nprintf 'done'\nexit 0\n" ).expect( "write fake claude" );
std::fs::set_permissions( &fake, std::fs::Permissions::from_mode( 0o755 ) )
.expect( "chmod fake claude" );
let old_path = std::env::var( "PATH" ).unwrap_or_default();
let new_path = format!( "{}:{old_path}", tmp.path().display() );
let bin = env!( "CARGO_BIN_EXE_clr" );
let out = Command::new( bin )
.args( [ "-p", "--timeout", "30", "--max-sessions", "0", "x" ] )
.env( "PATH", &new_path )
.output()
.expect( "invoke clr" );
assert!(
out.status.success(),
"exit must be 0. exit={:?} stderr={}",
out.status.code(),
String::from_utf8_lossy( &out.stderr )
);
let stderr = String::from_utf8_lossy( &out.stderr );
assert!(
!stderr.to_lowercase().contains( "timeout" ),
"no timeout message when subprocess exits before deadline. Got:\n{stderr}"
);
}