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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
//! Test command - test installed formulas
//!
//! Runs basic smoke tests on installed packages by verifying binaries
//! can execute with --version or --help flags.
use anyhow::{bail, Context, Result};
use stout_state::{InstalledPackages, Paths};
use clap::Args as ClapArgs;
use console::style;
use std::process::Command;
use std::time::Instant;
#[derive(ClapArgs)]
pub struct Args {
/// Formulas to test
pub formulas: Vec<String>,
/// Show verbose output
#[arg(long, short)]
pub verbose: bool,
/// Test all installed packages
#[arg(long)]
pub all: bool,
}
pub async fn run(args: Args) -> Result<()> {
let start = Instant::now();
let paths = Paths::default();
let installed = InstalledPackages::load(&paths)?;
// Get list of packages to test
let packages: Vec<String> = if args.all {
installed.names().cloned().collect()
} else if args.formulas.is_empty() {
bail!("No formulas specified. Use --all to test all installed packages.");
} else {
args.formulas.clone()
};
if packages.is_empty() {
println!("{}", style("No packages to test.").dim());
return Ok(());
}
println!(
"\n{} {} packages...\n",
style("Testing").cyan().bold(),
packages.len()
);
let mut success_count = 0;
let mut failure_count = 0;
let mut skipped_count = 0;
for name in &packages {
// Check if formula is installed
if !installed.is_installed(name) {
println!(
" {} {} {}",
style("○").dim(),
name,
style("(not installed)").dim()
);
skipped_count += 1;
continue;
}
let pkg_info = installed.get(name)
.with_context(|| format!("package '{}' is installed but not found in state", name))?;
let install_path = paths
.cellar
.join(name)
.join(&pkg_info.version);
if !install_path.exists() {
println!(
" {} {} {}",
style("○").dim(),
name,
style("(installation not found)").dim()
);
skipped_count += 1;
continue;
}
// Find binaries to test
let bin_dir = install_path.join("bin");
if !bin_dir.exists() {
if args.verbose {
println!(
" {} {} {}",
style("○").dim(),
name,
style("(no binaries)").dim()
);
}
skipped_count += 1;
continue;
}
let mut tested = false;
let mut all_passed = true;
// Test each binary
if let Ok(entries) = std::fs::read_dir(&bin_dir) {
for entry in entries.flatten() {
let path = entry.path();
if !path.is_file() {
continue;
}
let binary_name = path.file_name().unwrap().to_string_lossy().to_string();
// Skip common non-executable files
if binary_name.ends_with(".sh")
|| binary_name.ends_with(".py")
|| binary_name.ends_with(".rb")
{
continue;
}
// Try running with --version
let result = test_binary(&paths.prefix.join("bin").join(&binary_name), args.verbose);
if result.is_ok() {
tested = true;
if args.verbose {
println!(
" {} {}",
style("✓").green(),
binary_name
);
}
} else {
tested = true;
all_passed = false;
if args.verbose {
println!(
" {} {} - {}",
style("✗").red(),
binary_name,
result.unwrap_err()
);
}
}
}
}
if !tested {
skipped_count += 1;
if args.verbose {
println!(
" {} {} {}",
style("○").dim(),
name,
style("(no testable binaries)").dim()
);
}
} else if all_passed {
success_count += 1;
println!(
" {} {} {}",
style("✓").green(),
name,
style(&pkg_info.version).dim()
);
} else {
failure_count += 1;
println!(
" {} {} {}",
style("✗").red(),
name,
style(&pkg_info.version).dim()
);
}
}
let elapsed = start.elapsed();
println!();
if failure_count == 0 {
println!(
"{} {} passed, {} skipped in {:.1}s",
style("✓").green().bold(),
success_count,
skipped_count,
elapsed.as_secs_f64()
);
} else {
println!(
"{} {} passed, {} failed, {} skipped in {:.1}s",
style("!").yellow().bold(),
success_count,
failure_count,
skipped_count,
elapsed.as_secs_f64()
);
}
if failure_count > 0 {
std::process::exit(1);
}
Ok(())
}
fn test_binary(path: &std::path::Path, verbose: bool) -> Result<(), String> {
// Check if the binary exists
if !path.exists() {
return Err("binary not found".to_string());
}
// Try --version first
let version_result = Command::new(path)
.arg("--version")
.output();
if let Ok(output) = version_result {
if output.status.success() {
if verbose {
let stdout = String::from_utf8_lossy(&output.stdout);
if !stdout.is_empty() {
// Just show first line
if let Some(line) = stdout.lines().next() {
return Ok(());
}
}
}
return Ok(());
}
}
// Try --help as fallback
let help_result = Command::new(path)
.arg("--help")
.output();
if let Ok(output) = help_result {
if output.status.success() || output.status.code() == Some(0) || output.status.code() == Some(1) {
// Some programs exit with 1 for --help
return Ok(());
}
}
// Try just running it with no args (for simple utilities)
let bare_result = Command::new(path)
.output();
if let Ok(output) = bare_result {
// Consider it success if it runs without crashing
if output.status.success() || output.status.code().is_some() {
return Ok(());
}
}
Err("failed to execute".to_string())
}