-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathrunning_binary_container.rs
More file actions
165 lines (149 loc) · 5.47 KB
/
Copy pathrunning_binary_container.rs
File metadata and controls
165 lines (149 loc) · 5.47 KB
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//! Running container with an installed binary
use std::path::Path;
use std::process::Command;
use testcontainers::{ContainerAsync, GenericImage};
use super::command_output::CommandOutput;
use super::container_id::ContainerId;
/// Exit code returned by a command execution
pub type ExitCode = i32;
/// A running Ubuntu container with a binary installed and ready to execute
///
/// This struct provides methods for executing commands and managing a running
/// Ubuntu container that has been prepared with a binary. It handles the container
/// lifecycle, ensuring the container stays alive while tests run, and provides
/// convenient methods for command execution and file operations.
pub struct RunningBinaryContainer {
// Keep a reference to the container so it stays alive
#[allow(dead_code)]
container: ContainerAsync<GenericImage>,
container_id: ContainerId,
}
impl RunningBinaryContainer {
/// Create a new running binary container
///
/// # Arguments
///
/// * `container` - The running Docker container
/// * `container_id` - The validated container ID
pub(super) fn new(container: ContainerAsync<GenericImage>, container_id: ContainerId) -> Self {
Self {
container,
container_id,
}
}
/// Execute a command in the container and return the output
///
/// # Arguments
///
/// * `command` - Command and arguments to execute
///
/// # Returns
///
/// A `CommandOutput` containing both stdout and stderr streams
///
/// # Note
///
/// The CLI uses tracing which writes logs to stderr, while user-facing messages
/// go to stdout. The `CommandOutput` type allows tests to inspect either stream
/// individually or combined.
pub fn exec(&self, command: &[&str]) -> CommandOutput {
let output = Command::new("docker")
.arg("exec")
.arg(&self.container_id)
.args(command)
.output()
.expect("Failed to execute docker exec command");
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
CommandOutput::new(stdout, stderr)
}
/// Execute a command and return the exit code
///
/// # Arguments
///
/// * `command` - Command and arguments to execute
///
/// # Returns
///
/// The exit code of the command, or 1 if the process was terminated by a signal
///
/// # Note
///
/// If the process was terminated by a signal (returns None from `code()`), we return 1
/// to indicate failure rather than 0, which would incorrectly suggest success.
pub fn exec_with_exit_code(&self, command: &[&str]) -> ExitCode {
let status = Command::new("docker")
.arg("exec")
.arg(&self.container_id)
.args(command)
.status()
.expect("Failed to execute docker exec command");
// Return 1 (failure) if terminated by signal, otherwise use actual exit code
status.code().unwrap_or(1)
}
/// Execute a command and return the exit code, suppressing stderr output
///
/// This is useful for tests that intentionally trigger errors and want to
/// keep test output clean. The command's stderr is redirected to /dev/null
/// within the container.
///
/// # Arguments
///
/// * `command` - Command and arguments to execute
///
/// # Returns
///
/// The exit code of the command, or 1 if the process was terminated by a signal
///
/// # Example
///
/// ```rust
/// // Test that expects an error but doesn't want stderr noise in test output
/// let exit_code = container.exec_with_exit_code_silent(&[
/// "dependency-installer",
/// "install",
/// "--dependency",
/// "invalid-name",
/// ]);
/// assert_ne!(exit_code, 0);
/// ```
#[allow(dead_code)] // Used by install_command tests but not check_command tests
pub fn exec_with_exit_code_silent(&self, command: &[&str]) -> ExitCode {
// Build the command with stderr redirected to /dev/null
let command_str = format!("{} 2>/dev/null", command.join(" "));
let status = Command::new("docker")
.arg("exec")
.arg(&self.container_id)
.arg("sh")
.arg("-c")
.arg(&command_str)
.status()
.expect("Failed to execute docker exec command");
// Return 1 (failure) if terminated by signal, otherwise use actual exit code
status.code().unwrap_or(1)
}
/// Copy a file from the host into this running container
///
/// This method uses Docker CLI to copy files into the running container.
///
/// # Arguments
///
/// * `source_path` - Path to the file on the host system
/// * `dest_path` - Destination path inside the container
///
/// # Panics
///
/// Panics if the Docker copy command fails
pub(super) fn copy_file_to_container(&self, source_path: &Path, dest_path: &str) {
let output = Command::new("docker")
.arg("cp")
.arg(source_path)
.arg(format!("{}:{dest_path}", self.container_id))
.output()
.expect("Failed to execute docker cp command");
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
panic!("Failed to copy file to container: {stderr}");
}
}
}