use gitbaby::command::cmd::Cmd; use gitbaby::command::context::GitPipeContext; use gitbaby::command::pipe::Pipe; use std::path::PathBuf; use std::time::Duration; fn make_cmd(caller: &str, prog: &str, arg: &str) -> Cmd { Cmd::new( caller, prog, vec![arg.to_string()], vec![], PathBuf::from("."), vec![], None, ) } fn must_some(opt: Option, msg: &str) -> T { match opt { Some(v) => v, None => panic!("{msg}"), } } #[test] fn pipe_builder_sets_initial_fields() { let pipe = Pipe::new("demo", PathBuf::from("/tmp/work")); assert_eq!(pipe.name, "demo"); assert_eq!(pipe.dir, PathBuf::from("/tmp/work")); assert_eq!(pipe.len(), 0); assert!(pipe.is_empty()); assert!(!pipe.is_cancelled()); assert_eq!(pipe.timeout, None); assert!(pipe.first().is_none()); assert!(pipe.last().is_none()); } #[test] fn pipe_push_grows_cmds_and_chains() { let pipe = Pipe::new("chain", PathBuf::from(".")) .push(make_cmd("c1", "echo", "a")) .push(make_cmd("c2", "echo", "b")); assert_eq!(pipe.len(), 2); assert!(!pipe.is_empty()); assert_eq!( must_some(pipe.first(), "first must be Some").caller_info, "c1" ); assert_eq!( must_some(pipe.last(), "last must be Some").caller_info, "c2" ); } #[test] fn pipe_extend_accepts_iterator() { let cmds = vec![ make_cmd("a", "echo", "1"), make_cmd("b", "echo", "2"), make_cmd("c", "echo", "3"), ]; let pipe = Pipe::new("ext", PathBuf::from(".")).extend(cmds); assert_eq!(pipe.len(), 3); assert_eq!(must_some(pipe.last(), "last must be Some").caller_info, "c"); } #[test] fn pipe_with_dir_overrides_directory() { let pipe = Pipe::new("d", PathBuf::from("/old")).with_dir(PathBuf::from("/new")); assert_eq!(pipe.dir, PathBuf::from("/new")); } #[test] fn pipe_with_timeout_sets_value() { let pipe = Pipe::new("t", PathBuf::from(".")).with_timeout(5_000); assert_eq!(pipe.timeout, Some(5_000)); } #[test] fn pipe_display_shows_name_and_joined_commands() { let pipe = Pipe::new("pipe-name", PathBuf::from(".")) .push(make_cmd("c1", "git", "log")) .push(make_cmd("c2", "git", "show")); let rendered = format!("{pipe}"); assert_eq!(rendered, "pipe-name: git log | git show"); } #[test] fn pipe_display_when_empty() { let pipe = Pipe::new("empty-pipe", PathBuf::from(".")); assert_eq!(format!("{pipe}"), "empty-pipe:"); } #[test] fn pipe_debug_includes_field_summary() { let pipe = Pipe::new("dbg", PathBuf::from(".")) .with_timeout(42) .push(make_cmd("c", "echo", "x")); let rendered = format!("{pipe:?}"); assert!(rendered.contains("Pipe")); assert!(rendered.contains("dbg")); assert!(rendered.contains("42")); assert!(rendered.contains("cancelled: false")); } #[test] fn pipe_current_index_unset_when_empty() { let pipe = Pipe::new("e", PathBuf::from(".")); assert_eq!(pipe.current_index(), None); } #[test] fn pipe_current_index_after_push() { let pipe = Pipe::new("e", PathBuf::from(".")) .push(make_cmd("c1", "echo", "a")) .push(make_cmd("c2", "echo", "b")); assert_eq!(pipe.current_index(), Some(1)); } #[test] fn pipe_cancel_sets_flag_and_drops_index() { let mut pipe = Pipe::new("c", PathBuf::from(".")) .push(make_cmd("c1", "echo", "a")) .push(make_cmd("c2", "echo", "b")); assert!(!pipe.is_cancelled()); assert_eq!(pipe.current_index(), Some(1)); pipe.cancel_pipe(); assert!(pipe.is_cancelled()); assert_eq!(pipe.current_index(), None); } #[tokio::test] async fn pipe_cancel_kills_running_children() { let mut pipe = Pipe::new("kill", PathBuf::from(".")).push(Cmd::new( "sleeper", "sleep", vec!["30".to_string()], vec![], PathBuf::from("."), vec![], None, )); if let Err(e) = pipe.cmds[0].spawn().await { panic!("spawn failed: {:?}", e); } pipe.cancel_pipe(); assert!(pipe.is_cancelled()); assert!(!pipe.spawn_allowed()); tokio::time::sleep(Duration::from_millis(200)).await; // The child handle is handed to the reaper task; kill must be rejected // for new spawns and the stale handle must no longer be owned by the Cmd. assert!( pipe.cmds[0].cmd.is_none(), "cancel_pipe should take over the child handle" ); } #[tokio::test] async fn pipe_cancel_is_safe_when_no_children_running() { let mut pipe = Pipe::new("safe", PathBuf::from(".")).push(make_cmd("c", "echo", "x")); pipe.cancel_pipe(); assert!(pipe.is_cancelled()); assert_eq!(pipe.current_index(), None); }