mod cli_binary_test_helpers;
use cli_binary_test_helpers::{ run_cli, run_cli_with_env, stderr_str, stdout_str };
#[ cfg( unix ) ]
use cli_binary_test_helpers::{
fake_claude_binary_dir, run_clr_ps, spawn_fake_claude, spawn_print_claude,
};
#[ test ]
fn it_01_no_sessions_shows_message()
{
let empty_proc = tempfile::TempDir::new().expect( "create empty proc dir" );
let proc_dir = empty_proc.path().to_str().expect( "proc dir UTF-8" );
let out = run_cli_with_env( &[ "ps" ], &[ ( "CLR_PROC_DIR", proc_dir ) ] );
let stdout = stdout_str( &out );
assert!( out.status.success(), "exit code must be 0, got {:?}", out.status.code() );
assert!(
stdout.contains( "No active Claude Code sessions." ),
"stdout must contain the no-sessions message, got: {stdout}"
);
}
#[ cfg( unix ) ]
#[ test ]
fn it_02_sessions_present_plain_style()
{
let ( _dir, path_val ) = fake_claude_binary_dir();
let mut bg = spawn_fake_claude( &path_val );
let out = run_clr_ps( &path_val );
let _ = bg.kill();
let _ = bg.wait();
let stdout = stdout_str( &out );
assert!( out.status.success(), "exit code must be 0, got {:?}", out.status.code() );
assert!(
stdout.contains( "PID" ),
"stdout must contain PID header, got: {stdout}"
);
let first_line = stdout.lines().find( |l| !l.trim().is_empty() ).unwrap_or( "" );
assert!(
!first_line.contains( '\u{250C}' ), "table caption must use plain style — no ┌ border, got first line: {first_line}"
);
}
#[ test ]
fn it_03_help_lists_ps()
{
let out = run_cli( &[ "--help" ] );
let stdout = stdout_str( &out );
assert!( out.status.success(), "exit 0 expected, got {:?}", out.status.code() );
assert!(
stdout.contains( "ps" ),
"help output must mention ps, got: {stdout}"
);
}
#[ test ]
fn it_04_typo_clr_p()
{
let out = run_cli( &[ "p" ] );
let stderr = stderr_str( &out );
assert!( !out.status.success(), "expected non-zero exit" );
assert!(
stderr.contains( "Did you mean" ),
"stderr must contain 'Did you mean', got: {stderr}"
);
}
#[ cfg( unix ) ]
#[ test ]
fn it_05_table_headers_present()
{
let ( _dir, path_val ) = fake_claude_binary_dir();
let mut bg = spawn_fake_claude( &path_val );
let out = run_clr_ps( &path_val );
let _ = bg.kill();
let _ = bg.wait();
let stdout = stdout_str( &out );
assert!( out.status.success(), "exit 0 expected, got {:?}", out.status.code() );
assert!( stdout.contains( "PID" ), "missing PID header: {stdout}" );
assert!( stdout.contains( "Elapsed" ), "missing Elapsed header: {stdout}" );
assert!( stdout.contains( "Absolute Path" ), "missing Absolute Path header: {stdout}" );
assert!( stdout.contains( "Task" ), "missing Task header: {stdout}" );
}
#[ test ]
fn it_06_typo_clr_pss()
{
let out = run_cli( &[ "pss" ] );
let stderr = stderr_str( &out );
assert!( !out.status.success(), "expected non-zero exit" );
assert!(
stderr.contains( "Did you mean" ),
"stderr must contain 'Did you mean', got: {stderr}"
);
}
#[ cfg( unix ) ]
#[ test ]
fn it_07_self_exclusion()
{
let ( _dir, path_val ) = fake_claude_binary_dir();
let mut bg = spawn_fake_claude( &path_val );
let out = run_clr_ps( &path_val );
let _ = bg.kill();
let _ = bg.wait();
let stdout = stdout_str( &out );
assert!( out.status.success(), "exit 0 expected, got {:?}", out.status.code() );
let self_pid = std::process::id().to_string();
assert!(
!stdout.contains( &self_pid ),
"test-runner PID {self_pid} must not appear in ps output: {stdout}"
);
}
#[ test ]
fn it_08_unknown_flag()
{
let out = run_cli( &[ "ps", "--unknown" ] );
let stderr = stderr_str( &out );
assert!( !out.status.success(), "expected non-zero exit" );
assert!(
stderr.contains( "unexpected argument" ),
"stderr must mention unexpected argument, got: {stderr}"
);
}
#[ cfg( unix ) ]
#[ test ]
fn it_09_pro_prefix_shortened_in_path_column()
{
let pro_dir = tempfile::TempDir::new().expect( "create tmp PRO dir" );
let sub_dir = pro_dir.path().join( "my" ).join( "project" );
std::fs::create_dir_all( &sub_dir ).expect( "create project subdir" );
let pro_str = pro_dir.path().to_str().expect( "PRO path is UTF-8" );
let ( _bin_dir, path_val ) = fake_claude_binary_dir();
let mut bg = std::process::Command::new( "claude" )
.env( "PATH", &path_val )
.arg( "30" )
.current_dir( &sub_dir )
.stdout( std::process::Stdio::null() )
.stderr( std::process::Stdio::null() )
.spawn()
.expect( "spawn fake claude in sub_dir" );
std::thread::sleep( core::time::Duration::from_millis( 200 ) );
let bin = env!( "CARGO_BIN_EXE_clr" );
let out = std::process::Command::new( bin )
.arg( "ps" )
.env( "PATH", &path_val )
.env( "PRO", pro_str )
.output()
.expect( "run clr ps with PRO set" );
let _ = bg.kill();
let _ = bg.wait();
let stdout = stdout_str( &out );
assert!( out.status.success(), "exit 0 expected, got {:?}", out.status.code() );
assert!(
stdout.contains( "$PRO" ),
"IT-9: path must be shortened to $PRO/… when PRO env var is set. Got:\n{stdout}"
);
assert!(
!stdout.contains( pro_str ),
"IT-9: full PRO prefix must not appear in the table. Got:\n{stdout}"
);
}
#[ test ]
fn it_10_gate_file_present_shows_queued_table()
{
let gate_dir = tempfile::TempDir::new().expect( "create gate temp dir" );
let gate_dir_path = gate_dir.path().to_str().expect( "gate dir UTF-8" );
let live_pid = std::process::id();
let gate_file = gate_dir.path().join( format!( "{live_pid}.json" ) );
std::fs::write(
&gate_file,
r#"{"cwd":"/tmp/test-project","since":1720000000,"attempt":3,"message":"waiting for session slot"}"#,
).expect( "write gate file" );
let out = run_cli_with_env( &[ "ps" ], &[ ( "CLR_GATE_DIR", gate_dir_path ) ] );
let stdout = stdout_str( &out );
assert!( out.status.success(), "exit 0 expected, got {:?}", out.status.code() );
assert!( stdout.contains( "PID" ), "missing PID header in queued table: {stdout}" );
assert!( stdout.contains( "CWD" ), "missing CWD header in queued table: {stdout}" );
assert!( stdout.contains( "Waiting" ), "missing Waiting header in queued table: {stdout}" );
}
#[ test ]
fn it_11_no_gate_files_no_queued_table()
{
let gate_dir = tempfile::TempDir::new().expect( "create gate temp dir" );
let gate_dir_path = gate_dir.path().to_str().expect( "gate dir UTF-8" );
let empty_proc = tempfile::TempDir::new().expect( "create empty proc dir" );
let proc_dir = empty_proc.path().to_str().expect( "proc dir UTF-8" );
let out = run_cli_with_env(
&[ "ps" ],
&[ ( "CLR_GATE_DIR", gate_dir_path ), ( "CLR_PROC_DIR", proc_dir ) ],
);
let stdout = stdout_str( &out );
assert!( out.status.success(), "exit 0 expected, got {:?}", out.status.code() );
assert!(
stdout.contains( "No active Claude Code sessions." ),
"must show no-sessions message with empty gate dir: {stdout}"
);
assert!(
!stdout.contains( "Waiting" ),
"must not contain Waiting header when no gate files: {stdout}"
);
assert!(
!stdout.contains( "Attempt" ),
"must not contain Attempt header when no gate files: {stdout}"
);
}
#[ cfg( unix ) ]
#[ test ]
fn it_12_active_table_has_caption()
{
let ( _dir, path_val ) = fake_claude_binary_dir();
let mut bg = spawn_fake_claude( &path_val );
let out = run_clr_ps( &path_val );
let _ = bg.kill();
let _ = bg.wait();
let stdout = stdout_str( &out );
assert!( out.status.success(), "exit 0 expected, got {:?}", out.status.code() );
assert!(
stdout.contains( "Active Sessions" ),
"IT-12: active table caption must contain 'Active Sessions', got: {stdout}"
);
assert!(
stdout.contains( "running" ),
"IT-12: active table caption must contain 'running' count suffix, got: {stdout}"
);
}
#[ test ]
fn it_13_orphaned_gate_file_filtered_out()
{
let gate_dir = tempfile::TempDir::new().expect( "create gate temp dir" );
let gate_dir_path = gate_dir.path().to_str().expect( "gate dir UTF-8" );
let orphan_file = gate_dir.path().join( "99999999.json" );
std::fs::write(
&orphan_file,
r#"{"cwd":"/tmp/dead-process","since":1,"attempt":1,"message":"waiting for session slot"}"#,
).expect( "write orphan gate file" );
let out = run_cli_with_env( &[ "ps" ], &[ ( "CLR_GATE_DIR", gate_dir_path ) ] );
let stdout = stdout_str( &out );
assert!( out.status.success(), "exit 0 expected, got {:?}", out.status.code() );
assert!(
!stdout.contains( "Queued" ),
"IT-13 (BUG-293): orphaned gate file must not produce a queued table. Got:\n{stdout}"
);
assert!(
!stdout.contains( "99999999" ),
"IT-13 (BUG-293): orphaned PID must not appear in output. Got:\n{stdout}"
);
assert!(
!orphan_file.exists(),
"IT-13 (BUG-293): orphaned gate file must be deleted by self-healing cleanup"
);
}
#[ test ]
fn it_14_ps_help_flag()
{
let out = run_cli( &[ "ps", "--help" ] );
let stdout = stdout_str( &out );
assert!( out.status.success(), "IT-14: exit 0 expected, got {:?}", out.status.code() );
assert!(
!stdout.is_empty(),
"IT-14: stdout must contain help text, got empty output"
);
}
#[ test ]
fn it_15_ps_h_flag()
{
let out = run_cli( &[ "ps", "-h" ] );
let stdout = stdout_str( &out );
assert!( out.status.success(), "IT-15: exit 0 expected, got {:?}", out.status.code() );
assert!(
!stdout.is_empty(),
"IT-15: stdout must contain help text, got empty output"
);
}
#[ cfg( unix ) ]
#[ test ]
fn it_16_task_column_form_a()
{
let ( _bin_dir, path_val ) = fake_claude_binary_dir();
let proj_tmp = tempfile::TempDir::new().expect( "create project tmp" );
let cwd = proj_tmp.path().join( "wip_core" ).join( "proj" );
std::fs::create_dir_all( &cwd ).expect( "create CWD with underscores" );
let cwd_str = cwd.to_str().expect( "CWD UTF-8" );
let mut bg = std::process::Command::new( "claude" )
.arg( "30" )
.env( "PATH", &path_val )
.current_dir( &cwd )
.stdout( std::process::Stdio::null() )
.stderr( std::process::Stdio::null() )
.spawn()
.expect( "spawn fake claude" );
std::thread::sleep( core::time::Duration::from_millis( 200 ) );
let encoded = cwd_str.replace( ['/', '_'], "-" );
let home_tmp = tempfile::TempDir::new().expect( "create temp HOME" );
let project_path = home_tmp.path()
.join( ".claude" ).join( "projects" ).join( &encoded );
std::fs::create_dir_all( &project_path ).expect( "create project path" );
std::fs::write(
project_path.join( "session.jsonl" ),
r#"{"type":"user","message":{"role":"user","content":"fix the auth module"}}"#,
).expect( "write JSONL" );
let bin = env!( "CARGO_BIN_EXE_clr" );
let out = std::process::Command::new( bin )
.arg( "ps" )
.env( "PATH", &path_val )
.env( "HOME", home_tmp.path() )
.output()
.expect( "run clr ps" );
let _ = bg.kill();
let _ = bg.wait();
let stdout = stdout_str( &out );
assert!( out.status.success(), "exit 0 expected, got {:?}", out.status.code() );
assert!(
stdout.contains( "fix the auth module" ),
"IT-16 (BUG-295/296/297): Task column must show Form A content. Got:\n{stdout}"
);
}
#[ cfg( unix ) ]
#[ test ]
fn it_17_task_column_form_a_over_form_b()
{
let ( _bin_dir, path_val ) = fake_claude_binary_dir();
let proj_tmp = tempfile::TempDir::new().expect( "create project tmp" );
let cwd = proj_tmp.path().join( "wip_core" ).join( "proj" );
std::fs::create_dir_all( &cwd ).expect( "create CWD" );
let cwd_str = cwd.to_str().expect( "CWD UTF-8" );
let mut bg = std::process::Command::new( "claude" )
.arg( "30" )
.env( "PATH", &path_val )
.current_dir( &cwd )
.stdout( std::process::Stdio::null() )
.stderr( std::process::Stdio::null() )
.spawn()
.expect( "spawn fake claude" );
std::thread::sleep( core::time::Duration::from_millis( 200 ) );
let encoded = cwd_str.replace( ['/', '_'], "-" );
let home_tmp = tempfile::TempDir::new().expect( "create temp HOME" );
let project_path = home_tmp.path()
.join( ".claude" ).join( "projects" ).join( &encoded );
std::fs::create_dir_all( &project_path ).expect( "create project path" );
let jsonl = "{\
\"type\":\"user\",\
\"message\":{\"role\":\"user\",\"content\":\"the actual task\"}}\n\
{\"type\":\"user\",\
\"message\":{\"role\":\"user\",\"content\":[\
{\"type\":\"tool_result\",\"tool_use_id\":\"tu_abc\",\
\"content\":[{\"type\":\"text\",\"text\":\"claude command::some_skill\"}]}]}}";
std::fs::write( project_path.join( "session.jsonl" ), jsonl )
.expect( "write JSONL" );
let bin = env!( "CARGO_BIN_EXE_clr" );
let out = std::process::Command::new( bin )
.arg( "ps" )
.env( "PATH", &path_val )
.env( "HOME", home_tmp.path() )
.output()
.expect( "run clr ps" );
let _ = bg.kill();
let _ = bg.wait();
let stdout = stdout_str( &out );
assert!( out.status.success(), "exit 0 expected, got {:?}", out.status.code() );
assert!(
stdout.contains( "the actual task" ),
"IT-17 (BUG-297): Task must show Form A content. Got:\n{stdout}"
);
assert!(
!stdout.contains( "some_skill" ),
"IT-17 (BUG-297): Form B tool_result text must not appear. Got:\n{stdout}"
);
}
#[ test ]
fn it_18_ps_help_positional()
{
let out = run_cli( &[ "ps", "help" ] );
let stdout = stdout_str( &out );
assert!( out.status.success(), "IT-18: exit 0 expected, got {:?}", out.status.code() );
assert!(
!stdout.is_empty(),
"IT-18: stdout must contain help text, got empty output"
);
}
#[ cfg( unix ) ]
#[ test ]
fn it_19_task_column_no_underscores()
{
let ( _bin_dir, path_val ) = fake_claude_binary_dir();
let proj_tmp = tempfile::TempDir::new().expect( "create project tmp" );
let cwd = proj_tmp.path().join( "work" ).join( "proj" );
std::fs::create_dir_all( &cwd ).expect( "create CWD without underscores" );
let cwd_str = cwd.to_str().expect( "CWD UTF-8" );
let mut bg = std::process::Command::new( "claude" )
.arg( "30" )
.env( "PATH", &path_val )
.current_dir( &cwd )
.stdout( std::process::Stdio::null() )
.stderr( std::process::Stdio::null() )
.spawn()
.expect( "spawn fake claude" );
std::thread::sleep( core::time::Duration::from_millis( 200 ) );
let encoded = cwd_str.replace( '/', "-" );
let home_tmp = tempfile::TempDir::new().expect( "create temp HOME" );
let project_path = home_tmp.path()
.join( ".claude" ).join( "projects" ).join( &encoded );
std::fs::create_dir_all( &project_path ).expect( "create project path" );
std::fs::write(
project_path.join( "session.jsonl" ),
r#"{"type":"user","message":{"role":"user","content":"no underscores task"}}"#,
).expect( "write JSONL" );
let bin = env!( "CARGO_BIN_EXE_clr" );
let out = std::process::Command::new( bin )
.arg( "ps" )
.env( "PATH", &path_val )
.env( "HOME", home_tmp.path() )
.output()
.expect( "run clr ps" );
let _ = bg.kill();
let _ = bg.wait();
let stdout = stdout_str( &out );
assert!( out.status.success(), "IT-19: exit 0 expected, got {:?}", out.status.code() );
assert!(
stdout.contains( "no underscores task" ),
"IT-19 (BUG-295 regression): Task column must show Form A content for underscore-free CWD. Got:\n{stdout}"
);
}
#[ cfg( target_os = "linux" ) ]
#[ test ]
fn it_20_active_sessions_sorted_by_age()
{
let ( _bin_dir, path_val ) = fake_claude_binary_dir();
let mut bg_a = std::process::Command::new( "claude" )
.arg( "30" )
.env( "PATH", &path_val )
.stdout( std::process::Stdio::null() )
.stderr( std::process::Stdio::null() )
.spawn()
.expect( "spawn fake claude A" );
let pid_a = bg_a.id();
std::thread::sleep( core::time::Duration::from_secs( 1 ) );
let mut bg_b = std::process::Command::new( "claude" )
.arg( "30" )
.env( "PATH", &path_val )
.stdout( std::process::Stdio::null() )
.stderr( std::process::Stdio::null() )
.spawn()
.expect( "spawn fake claude B" );
let pid_b = bg_b.id();
std::thread::sleep( core::time::Duration::from_millis( 200 ) );
let out = run_clr_ps( &path_val );
let _ = bg_a.kill();
let _ = bg_a.wait();
let _ = bg_b.kill();
let _ = bg_b.wait();
let stdout = stdout_str( &out );
assert!(
out.status.success(),
"IT-20: exit 0 expected, got {:?}", out.status.code()
);
let older_pid = pid_a.to_string();
let newer_pid = pid_b.to_string();
let row_a = stdout.lines().position( |l| l.contains( &older_pid ) );
let row_b = stdout.lines().position( |l| l.contains( &newer_pid ) );
assert!(
row_a.is_some() && row_b.is_some(),
"IT-20 (BUG-301): both PIDs must appear in output. A={pid_a}, B={pid_b}\n{stdout}"
);
assert!(
row_a.unwrap() < row_b.unwrap(),
"IT-20 (BUG-301): oldest session (PID {pid_a}) must appear before newest (PID {pid_b}).\n{stdout}"
);
}
#[ cfg( unix ) ]
#[ test ]
fn it_21_mode_print_shows_only_print_sessions()
{
let ( _dir, path_val ) = fake_claude_binary_dir();
let mut bg_interactive = spawn_fake_claude( &path_val );
let pid_interactive = bg_interactive.id();
let mut bg_print = spawn_print_claude( &path_val );
let pid_print = bg_print.id();
let bin = env!( "CARGO_BIN_EXE_clr" );
let out = std::process::Command::new( bin )
.args( [ "ps", "--mode", "print" ] )
.env( "PATH", &path_val )
.output()
.expect( "run clr ps --mode print" );
let _ = bg_interactive.kill();
let _ = bg_interactive.wait();
let _ = bg_print.kill();
let _ = bg_print.wait();
let stdout = stdout_str( &out );
assert!( out.status.success(), "IT-21: exit 0 expected, got {:?}", out.status.code() );
assert!(
stdout.contains( &pid_print.to_string() ),
"IT-21: print-mode PID {pid_print} must appear with --mode print. Got:\n{stdout}"
);
assert!(
!stdout.contains( &pid_interactive.to_string() ),
"IT-21: interactive PID {pid_interactive} must NOT appear with --mode print. Got:\n{stdout}"
);
}
#[ cfg( unix ) ]
#[ test ]
fn it_22_mode_interactive_shows_only_interactive_sessions()
{
let ( _dir, path_val ) = fake_claude_binary_dir();
let mut bg_interactive = spawn_fake_claude( &path_val );
let pid_interactive = bg_interactive.id();
let mut bg_print = spawn_print_claude( &path_val );
let pid_print = bg_print.id();
let bin = env!( "CARGO_BIN_EXE_clr" );
let out = std::process::Command::new( bin )
.args( [ "ps", "--mode", "interactive" ] )
.env( "PATH", &path_val )
.output()
.expect( "run clr ps --mode interactive" );
let _ = bg_interactive.kill();
let _ = bg_interactive.wait();
let _ = bg_print.kill();
let _ = bg_print.wait();
let stdout = stdout_str( &out );
assert!( out.status.success(), "IT-22: exit 0 expected, got {:?}", out.status.code() );
assert!(
stdout.contains( &pid_interactive.to_string() ),
"IT-22: interactive PID {pid_interactive} must appear with --mode interactive. Got:\n{stdout}"
);
assert!(
!stdout.contains( &pid_print.to_string() ),
"IT-22: print-mode PID {pid_print} must NOT appear with --mode interactive. Got:\n{stdout}"
);
}
#[ test ]
fn it_23_mode_bogus_exits_1()
{
let out = run_cli( &[ "ps", "--mode", "bogus" ] );
let stderr = stderr_str( &out );
assert!( !out.status.success(), "IT-23: exit 1 expected, got {:?}", out.status.code() );
assert!(
stderr.contains( "interactive" ) && stderr.contains( "print" ),
"IT-23: stderr must list valid mode values (interactive, print). Got: {stderr}"
);
}
#[ cfg( unix ) ]
#[ test ]
fn it_24_columns_custom_subset()
{
let ( _dir, path_val ) = fake_claude_binary_dir();
let mut bg = spawn_fake_claude( &path_val );
let bin = env!( "CARGO_BIN_EXE_clr" );
let out = std::process::Command::new( bin )
.args( [ "ps", "--columns", "pid,path,task" ] )
.env( "PATH", &path_val )
.output()
.expect( "run clr ps --columns" );
let _ = bg.kill();
let _ = bg.wait();
let stdout = stdout_str( &out );
assert!( out.status.success(), "IT-24: exit 0 expected, got {:?}", out.status.code() );
assert!( stdout.contains( "PID" ), "IT-24: PID must be present: {stdout}" );
assert!( stdout.contains( "Absolute Path" ), "IT-24: Absolute Path must be present: {stdout}" );
assert!( stdout.contains( "Task" ), "IT-24: Task must be present: {stdout}" );
assert!( !stdout.contains( "CPU%" ), "IT-24: CPU% must be absent: {stdout}" );
assert!( !stdout.contains( "RAM" ), "IT-24: RAM must be absent: {stdout}" );
assert!( !stdout.contains( "Elapsed" ), "IT-24: Elapsed must be absent: {stdout}" );
assert!( !stdout.contains( "State" ), "IT-24: State must be absent: {stdout}" );
}
#[ test ]
fn it_25_columns_bogus_exits_1()
{
let out = run_cli( &[ "ps", "--columns", "bogus" ] );
let stderr = stderr_str( &out );
assert!( !out.status.success(), "IT-25: exit 1 expected, got {:?}", out.status.code() );
assert!(
stderr.contains( "bogus" ) && ( stderr.contains( "pid" ) || stderr.contains( "idx" ) ),
"IT-25: stderr must contain the unknown key and list valid keys. Got: {stderr}"
);
}
#[ cfg( unix ) ]
#[ test ]
fn it_26_wide_shows_all_columns()
{
let ( _dir, path_val ) = fake_claude_binary_dir();
let mut bg = spawn_fake_claude( &path_val );
let bin = env!( "CARGO_BIN_EXE_clr" );
let out = std::process::Command::new( bin )
.args( [ "ps", "--wide" ] )
.env( "PATH", &path_val )
.output()
.expect( "run clr ps --wide" );
let _ = bg.kill();
let _ = bg.wait();
let stdout = stdout_str( &out );
assert!( out.status.success(), "IT-26: exit 0 expected, got {:?}", out.status.code() );
assert!( stdout.contains( "Mode" ), "IT-26: Mode header must be present: {stdout}" );
assert!( stdout.contains( "Command" ), "IT-26: Command header must be present: {stdout}" );
assert!( stdout.contains( "Binary" ), "IT-26: Binary header must be present: {stdout}" );
}
#[ cfg( unix ) ]
#[ test ]
fn it_27_columns_wins_over_wide()
{
let ( _dir, path_val ) = fake_claude_binary_dir();
let mut bg = spawn_fake_claude( &path_val );
let bin = env!( "CARGO_BIN_EXE_clr" );
let out = std::process::Command::new( bin )
.args( [ "ps", "--wide", "--columns", "pid,task" ] )
.env( "PATH", &path_val )
.output()
.expect( "run clr ps --wide --columns" );
let _ = bg.kill();
let _ = bg.wait();
let stdout = stdout_str( &out );
assert!( out.status.success(), "IT-27: exit 0 expected, got {:?}", out.status.code() );
assert!( stdout.contains( "PID" ), "IT-27: PID must be present: {stdout}" );
assert!( stdout.contains( "Task" ), "IT-27: Task must be present: {stdout}" );
assert!( !stdout.contains( "Mode" ), "IT-27: Mode must be absent when --columns wins: {stdout}" );
assert!( !stdout.contains( "Command" ), "IT-27: Command must be absent when --columns wins: {stdout}" );
assert!( !stdout.contains( "Binary" ), "IT-27: Binary must be absent when --columns wins: {stdout}" );
}
#[ cfg( unix ) ]
#[ test ]
fn it_28_clr_ps_mode_env_var()
{
let ( _dir, path_val ) = fake_claude_binary_dir();
let mut bg_interactive = spawn_fake_claude( &path_val );
let pid_interactive = bg_interactive.id();
let mut bg_print = spawn_print_claude( &path_val );
let pid_print = bg_print.id();
let bin = env!( "CARGO_BIN_EXE_clr" );
let out = std::process::Command::new( bin )
.arg( "ps" )
.env( "PATH", &path_val )
.env( "CLR_PS_MODE", "print" )
.output()
.expect( "run clr ps with CLR_PS_MODE=print" );
let _ = bg_interactive.kill();
let _ = bg_interactive.wait();
let _ = bg_print.kill();
let _ = bg_print.wait();
let stdout = stdout_str( &out );
assert!( out.status.success(), "IT-28: exit 0 expected, got {:?}", out.status.code() );
assert!(
stdout.contains( &pid_print.to_string() ),
"IT-28: print PID {pid_print} must appear with CLR_PS_MODE=print. Got:\n{stdout}"
);
assert!(
!stdout.contains( &pid_interactive.to_string() ),
"IT-28: interactive PID {pid_interactive} must NOT appear with CLR_PS_MODE=print. Got:\n{stdout}"
);
}
#[ cfg( unix ) ]
#[ test ]
fn it_29_clr_ps_columns_env_var()
{
let ( _dir, path_val ) = fake_claude_binary_dir();
let mut bg = spawn_fake_claude( &path_val );
let bin = env!( "CARGO_BIN_EXE_clr" );
let out = std::process::Command::new( bin )
.arg( "ps" )
.env( "PATH", &path_val )
.env( "CLR_PS_COLUMNS", "pid,elapsed" )
.output()
.expect( "run clr ps with CLR_PS_COLUMNS=pid,elapsed" );
let _ = bg.kill();
let _ = bg.wait();
let stdout = stdout_str( &out );
assert!( out.status.success(), "IT-29: exit 0 expected, got {:?}", out.status.code() );
assert!( stdout.contains( "PID" ), "IT-29: PID must be present: {stdout}" );
assert!( stdout.contains( "Elapsed" ), "IT-29: Elapsed must be present: {stdout}" );
assert!( !stdout.contains( "CPU%" ), "IT-29: CPU% must be absent: {stdout}" );
assert!( !stdout.contains( "RAM" ), "IT-29: RAM must be absent: {stdout}" );
assert!( !stdout.contains( "Task" ), "IT-29: Task must be absent: {stdout}" );
assert!( !stdout.contains( "Absolute Path" ), "IT-29: Absolute Path must be absent: {stdout}" );
}