1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
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<io::Error> 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
// Could be replaced by https://doc.rust-lang.org/std/cell/struct.LazyCell.html once that is in stable rust
unsafe {
BUILD_DOCKER.call_once(|| {
BUILD_DOCKER_RESULT = build_docker();
});
BUILD_DOCKER_RESULT.clone()
}
}
fn spawn_ssh_server() -> Result<TestContext> {
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<PtySession> {
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(())
}
|