use anyhow::Result; use assert_cmd::cargo::cargo_bin; use rexpect::session::{spawn_command, PtySession}; use std::{io, path, sync::Once}; use tempfile::TempDir; use thiserror::Error; static BUILD_DOCKER: Once = Once::new(); static mut BUILD_DOCKER_RESULT: Result<(), DockerBuildError> = Ok(()); struct TestContext { workdir: TempDir, ssh_port: u32, docker_process: PtySession, } #[derive(Error, Debug, Clone)] pub enum DockerBuildError { #[error(transparent)] StripPrefixError(#[from] path::StripPrefixError), #[error("IO Error: `{0}`")] IoError(String), #[error("Failed to build dockerfile")] CliError, } impl From for DockerBuildError { fn from(e: io::Error) -> Self { DockerBuildError::IoError(e.to_string()) } } fn build_docker_image() -> Result<(), DockerBuildError> { let build_docker = || { let mut command = std::process::Command::new("docker"); let absolute_shell_path = cargo_bin(env!("CARGO_PKG_NAME")); let relative_shell_path = absolute_shell_path.strip_prefix(std::fs::canonicalize(".")?)?; command.args([ "build", "--quiet", "-t", "shackle-server", "--build-arg", &format!("SHELL={}", relative_shell_path.display()), "./", ]); let status = command.status()?; if status.success() { Ok(()) } else { Err(DockerBuildError::CliError) } }; // Based on this example: https://doc.rust-lang.org/std/sync/struct.Once.html#examples-1 unsafe { BUILD_DOCKER.call_once(|| { BUILD_DOCKER_RESULT = build_docker(); }); BUILD_DOCKER_RESULT.clone() } } fn spawn_ssh_server() -> Result { build_docker_image()?; let workdir = tempfile::tempdir()?; let mut command = std::process::Command::new("docker"); // TODO: randomize port? command.args(["run", "-it", "-p", "2022:22", "shackle-server"]); command.current_dir(&workdir); let docker_process = spawn_command(command, Some(3000))?; Ok(TestContext { workdir, ssh_port: 2022, docker_process, }) } fn connect_to_ssh_server_interactively(c: &TestContext) -> Result { let mut command = std::process::Command::new("ssh"); command.args(["-p", &c.ssh_port.to_string(), "shukkie@localhost"]); command.current_dir(&c.workdir); let mut p = spawn_command(command, Some(3000))?; expect_prompt(&mut p)?; Ok(p) } fn expect_prompt(p: &mut PtySession) -> Result<()> { p.exp_string("> ")?; Ok(()) } #[test] fn shows_a_prompt() -> Result<()> { let c = spawn_ssh_server()?; connect_to_ssh_server_interactively(&c)?; Ok(()) }