//! Command-line interface integration test. use std::{ process::{Child, Stdio}, time::Duration, }; use assert_cmd::{Command, prelude::*}; use assert_fs::TempDir; use predicates::prelude::*; use testresult::TestResult; #[test] fn short_help() -> TestResult { Command::cargo_bin("temp-postgres")? .arg("-h") .assert() .success() .stdout(predicate::str::contains("Usage")) .stderr(predicate::str::is_empty()); Ok(()) } #[test] fn long_help() -> TestResult { Command::cargo_bin("temp-postgres")? .arg("--help") .assert() .success() .stdout(predicate::str::contains("Usage")) .stderr(predicate::str::is_empty()); Ok(()) } #[test] fn short_version() -> TestResult { Command::cargo_bin("temp-postgres")? .arg("-V") .assert() .success() .stdout(predicate::str::is_match( r#"^temp-postgres [0-9]+\.[0-9]+\.[0-9]+\n$"#, )?) .stderr(predicate::str::is_empty()); Ok(()) } #[test] fn long_version() -> TestResult { Command::cargo_bin("temp-postgres")? .arg("--version") .assert() .success() .stdout(predicate::str::is_match( r#"^temp-postgres [0-9]+\.[0-9]+\.[0-9]+-[0-9a-f]{7}(-dirty)?\n$"#, )?) .stderr(predicate::str::is_empty()); Ok(()) } #[test] fn wrapped_true() -> TestResult { Command::cargo_bin("temp-postgres")? .arg("--") .arg("true") .assert() .success() .stdout(predicate::str::is_empty()) .stderr(predicate::str::contains("PGHOST")); Ok(()) } #[test] fn wrapped_false() -> TestResult { Command::cargo_bin("temp-postgres")? .arg("--") .arg("false") .assert() .failure() .stdout(predicate::str::is_empty()) .stderr(predicate::str::contains("Wrapped command exited error")); Ok(()) } #[test] fn log_level_off() -> TestResult { Command::cargo_bin("temp-postgres")? .arg("--log-level") .arg("off") .arg("--") .arg("true") .assert() .success() .stdout(predicate::str::is_empty()) .stderr(predicate::str::is_empty()); Ok(()) } #[test] fn no_such_flag() -> TestResult { Command::cargo_bin("temp-postgres")? .arg("--foo") .assert() .code(2) .stdout(predicate::str::is_empty()) .stderr(predicate::str::contains("unexpected argument")); Ok(()) } #[test] fn unexpected_argument() -> TestResult { Command::cargo_bin("temp-postgres")? .arg("foo") .assert() .code(2) .stdout(predicate::str::is_empty()) .stderr(predicate::str::contains("unexpected argument")); Ok(()) } #[test] fn missing_username_argument() -> TestResult { Command::cargo_bin("temp-postgres")? .arg("--username") .assert() .code(2) .stdout(predicate::str::is_empty()) .stderr(predicate::str::contains("--username")); Ok(()) } #[test] fn empty_username_argument() -> TestResult { Command::cargo_bin("temp-postgres")? .arg("--username") .arg("") .assert() .code(2) .stdout(predicate::str::is_empty()) .stderr(predicate::str::contains("--username")); Ok(()) } #[test] fn symlink() -> TestResult { let tmp_dir = TempDir::new()?; let symlink = tmp_dir.join("db"); let child = std::process::Command::cargo_bin("temp-postgres")? .arg("--username") .arg("alex") .arg("--dbname") .arg("myproject") .arg("--symlink") .arg(&symlink) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn()?; for i in (0..100).rev() { std::thread::sleep(Duration::from_millis(200)); if symlink.exists() { break; } else if i > 0 { continue; } else { panic!("symlink not created in time"); } } Command::new("psql") .arg("--no-psqlrc") .arg("--no-align") .arg("--tuples-only") .arg("--host") .arg(&symlink) .arg("--dbname") .arg("myproject") .arg("--username") .arg("alex") .write_stdin("SELECT current_database(), usename FROM pg_user;") .assert() .success() .stdout(predicate::str::is_match(r#"^myproject|alex\n$"#)?) .stderr(predicate::str::is_empty()); interrupt(&child); child.wait_with_output()?.assert().code(130); Ok(()) } #[test] fn interrupted() -> TestResult { let tmp_dir = TempDir::new()?; let symlink = tmp_dir.join("db"); let child = std::process::Command::cargo_bin("temp-postgres")? .arg("--symlink") .arg(&symlink) .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn()?; for i in (0..100).rev() { std::thread::sleep(Duration::from_millis(200)); if symlink.exists() { break; } else if i > 0 { continue; } else { panic!("symlink not created in time"); } } interrupt(&child); child.wait_with_output()?.assert().code(130); Ok(()) } #[test] fn interrupted_empty_command() -> TestResult { let tmp_dir = TempDir::new()?; let symlink = tmp_dir.join("db"); let child = std::process::Command::cargo_bin("temp-postgres")? .arg("--symlink") .arg(&symlink) .arg("--") .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn()?; for i in (0..100).rev() { std::thread::sleep(Duration::from_millis(200)); if symlink.exists() { break; } else if i > 0 { continue; } else { panic!("symlink not created in time"); } } interrupt(&child); child.wait_with_output()?.assert().code(130); Ok(()) } #[test] fn interrupted_sleep_command() -> TestResult { let tmp_dir = TempDir::new()?; let symlink = tmp_dir.join("db"); let child = std::process::Command::cargo_bin("temp-postgres")? .arg("--symlink") .arg(&symlink) .arg("--") .arg("sleep") .arg("30") .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn()?; for i in (0..100).rev() { std::thread::sleep(Duration::from_millis(200)); if symlink.exists() { break; } else if i > 0 { continue; } else { panic!("symlink not created in time"); } } interrupt(&child); child.wait_with_output()?.assert().code(130); Ok(()) } #[test] fn wrapped_cat() -> TestResult { assert_cmd::Command::cargo_bin("temp-postgres")? .arg("--") .arg("cat") .write_stdin("foo") .assert() .success() .stdout(predicate::str::is_match(r#"^foo$"#)?) .stderr(predicate::str::contains("PGHOST")); Ok(()) } #[test] fn wrapped_psql_with_username() -> TestResult { assert_cmd::Command::cargo_bin("temp-postgres")? .arg("--username") .arg("alex") .arg("--") .arg("psql") .arg("--no-psqlrc") .arg("--no-align") .arg("--tuples-only") .write_stdin("SELECT current_database(), usename FROM pg_user;") .assert() .success() .stdout(predicate::str::is_match(r#"^alex|alex\n$"#)?) .stderr(predicate::str::contains("PGHOST")); Ok(()) } #[test] fn wrapped_psql_with_username_and_dbname() -> TestResult { assert_cmd::Command::cargo_bin("temp-postgres")? .arg("--username") .arg("alex") .arg("--dbname") .arg("myproject") .arg("--") .arg("psql") .arg("--no-psqlrc") .arg("--no-align") .arg("--tuples-only") .write_stdin("SELECT current_database(), usename FROM pg_user;") .assert() .success() .stdout(predicate::str::is_match(r#"^myproject|alex\n$"#)?) .stderr(predicate::str::contains("PGHOST")); Ok(()) } #[test] fn wrapped_psql_with_database_url() -> TestResult { assert_cmd::Command::cargo_bin("temp-postgres")? .arg("--username") .arg("alex") .arg("--dbname") .arg("myproject") .arg("--") .arg("env") .arg("--unset") .arg("PGHOST") .arg("--unset") .arg("PGDATABASE") .arg("--unset") .arg("PGUSER") .arg("--") .arg("bash") .arg("-c") .arg("psql --no-psqlrc --no-align --tuples-only \"$DATABASE_URL\"") .write_stdin("SELECT current_database(), usename FROM pg_user;") .assert() .success() .stdout(predicate::str::is_match(r#"^myproject|alex\n$"#)?) .stderr(predicate::str::contains("PGHOST")); Ok(()) } fn interrupt(child: &Child) { // std::os::unix::process::ChildExt::send_signal is nightly-only experimental nix::sys::signal::kill( nix::unistd::Pid::from_raw(child.id() as i32), nix::sys::signal::Signal::SIGINT, ) .unwrap(); }