-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathprocess_runner.rs
More file actions
160 lines (141 loc) · 4.79 KB
/
Copy pathprocess_runner.rs
File metadata and controls
160 lines (141 loc) · 4.79 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
//! External Process Execution
//!
//! Provides utilities for running the production application as an external
//! process for black-box testing.
use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
/// Runs the production application as an external process
///
/// This struct provides methods for executing the application binary
/// with different command-line arguments for black-box testing.
pub struct ProcessRunner {
working_dir: Option<PathBuf>,
}
impl ProcessRunner {
/// Create a new process runner
#[must_use]
pub fn new() -> Self {
Self { working_dir: None }
}
/// Set the working directory for the test process (not the app working dir)
///
/// This is the directory where the test command will be executed from,
/// typically a temporary directory for test isolation.
#[must_use]
pub fn working_dir<P: AsRef<Path>>(mut self, dir: P) -> Self {
self.working_dir = Some(dir.as_ref().to_path_buf());
self
}
/// Run the create command with the production binary
///
/// This method runs `cargo run -- create environment --env-file <config_file>` with
/// optional working directory for the application itself via `--working-dir`.
///
/// # Errors
///
/// Returns an error if the command fails to execute.
pub fn run_create_command(&self, config_file: &str) -> Result<ProcessResult> {
let mut cmd = Command::new("cargo");
// If working directory is specified, we need to:
// 1. Make the config file path absolute (cargo runs from project root)
// 2. Pass --working-dir to tell the app where to store data
if let Some(working_dir) = &self.working_dir {
// Convert config file to absolute path
let absolute_config = if config_file.starts_with("./") {
working_dir.join(config_file.trim_start_matches("./"))
} else {
working_dir.join(config_file)
};
// Build command with absolute paths
cmd.args([
"run",
"--",
"create",
"environment",
"--env-file",
absolute_config.to_str().unwrap(),
"--working-dir",
working_dir.to_str().unwrap(),
]);
} else {
// No working directory, use relative paths
cmd.args([
"run",
"--",
"create",
"environment",
"--env-file",
config_file,
]);
}
let output = cmd.output().context("Failed to execute create command")?;
Ok(ProcessResult::new(output))
}
/// Run the destroy command with the production binary
///
/// This method runs `cargo run -- destroy <environment_name>` with
/// optional working directory for the application itself via `--working-dir`.
///
/// # Errors
///
/// Returns an error if the command fails to execute.
#[allow(dead_code)]
pub fn run_destroy_command(&self, environment_name: &str) -> Result<ProcessResult> {
let mut cmd = Command::new("cargo");
if let Some(working_dir) = &self.working_dir {
// Build command with working directory
cmd.args([
"run",
"--",
"destroy",
environment_name,
"--working-dir",
working_dir.to_str().unwrap(),
]);
} else {
// No working directory, use relative paths
cmd.args(["run", "--", "destroy", environment_name]);
}
let output = cmd.output().context("Failed to execute destroy command")?;
Ok(ProcessResult::new(output))
}
}
impl Default for ProcessRunner {
fn default() -> Self {
Self::new()
}
}
/// Wrapper around process execution results
///
/// Provides convenient access to process output, exit status, and other
/// execution results.
pub struct ProcessResult {
output: Output,
}
impl ProcessResult {
fn new(output: Output) -> Self {
Self { output }
}
/// Check if the process completed successfully
#[must_use]
pub fn success(&self) -> bool {
self.output.status.success()
}
/// Get the process stdout as a string
#[must_use]
#[allow(dead_code)]
pub fn stdout(&self) -> String {
String::from_utf8_lossy(&self.output.stdout).to_string()
}
/// Get the process stderr as a string
#[must_use]
pub fn stderr(&self) -> String {
String::from_utf8_lossy(&self.output.stderr).to_string()
}
/// Get the process exit code
#[must_use]
pub fn exit_code(&self) -> Option<i32> {
self.output.status.code()
}
}