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
/// Comprehensive tests for Windows VS Code detection issue #22
/// These tests simulate the exact failure scenarios that were occurring
/// and verify that our fix handles them correctly.
#[cfg(test)]
mod comprehensive_windows_tests {
use std::env;
use std::fs;
use std::process::Command;
use tempfile::TempDir;
/// Test that reproduces the exact Windows issue scenario:
/// - Command exists and `cmd --version` works
/// - But `which cmd` or `where cmd` fails or gives wrong result
/// - Our fix should still detect the command correctly
#[test]
fn test_reproduce_windows_issue_scenario() {
// This test simulates the exact problem reported in issue #22
// Create a temporary directory and fake VS Code executable
let temp_dir = TempDir::new().expect("Could not create temp dir");
let fake_vscode_path = temp_dir.path().join(if cfg!(windows) { "code.exe" } else { "code" });
// Create a fake VS Code that responds to --version
let script_content = if cfg!(windows) {
"@echo off\nif \"%1\"==\"--version\" echo 1.85.0\n"
} else {
"#!/bin/bash\nif [ \"$1\" = \"--version\" ]; then echo \"1.85.0\"; fi\n"
};
fs::write(&fake_vscode_path, script_content).expect("Could not write fake VS Code");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&fake_vscode_path).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&fake_vscode_path, perms).unwrap();
}
// Test 1: Direct execution should work
let direct_result = Command::new(&fake_vscode_path).arg("--version").output();
match direct_result {
Ok(output) if output.status.success() => {
println!("✓ Direct execution works: {}", String::from_utf8_lossy(&output.stdout));
}
Ok(output) => {
println!("Direct execution failed with status: {}", output.status);
return; // Skip the rest if we can't even create a working fake command
}
Err(e) => {
println!("Could not execute fake command: {e}");
return;
}
}
// Test 2: which/where will likely fail since it's not in PATH
let lookup_cmd = if cfg!(windows) { "where" } else { "which" };
let lookup_result = Command::new(lookup_cmd)
.arg("code") // Look for the real 'code' command, not our fake one
.output();
println!(
"Lookup command '{lookup_cmd} code' result: {}",
lookup_result.map(|o| o.status.success()).unwrap_or(false)
);
// Test 3: Verify our new command_exists logic would work
// This simulates what VsCodeExtension::command_exists does
let would_find_real_code = simulate_new_command_exists("code");
let would_find_fake_code = simulate_new_command_exists(fake_vscode_path.to_str().unwrap());
println!("Real 'code' command detection: {would_find_real_code}");
println!("Fake 'code' command detection: {would_find_fake_code}");
// The test passes if our logic can find at least one working command - see output for details
}
/// Simulate our new command_exists logic without importing the actual function
fn simulate_new_command_exists(cmd: &str) -> bool {
// Primary approach: try direct execution
if let Ok(output) = Command::new(cmd).arg("--version").output()
&& output.status.success()
{
return true;
}
// Fallback: platform-appropriate lookup
let lookup_cmd = if cfg!(windows) { "where" } else { "which" };
Command::new(lookup_cmd)
.arg(cmd)
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
/// Test PATH manipulation scenarios that could cause the original issue
#[test]
fn test_path_manipulation_scenarios() {
// Save original PATH
let original_path = env::var("PATH").unwrap_or_default();
// Create a temporary executable in a custom directory. Use a real working
// binary (the rumdl test binary, which responds to `--version`) rather than a
// shell/batch script: on Windows a `.exe` containing batch text is not a valid
// executable, so direct execution of it would always fail.
let temp_dir = TempDir::new().expect("Could not create temp dir");
let custom_bin_dir = temp_dir.path().join("custom_bin");
fs::create_dir_all(&custom_bin_dir).expect("Could not create custom bin dir");
let fake_cmd_path = custom_bin_dir.join(if cfg!(windows) { "testcmd.exe" } else { "testcmd" });
fs::copy(env!("CARGO_BIN_EXE_rumdl"), &fake_cmd_path).expect("Could not copy test binary");
// Test scenario 1: Command in PATH, both approaches should work
let new_path = format!(
"{}{}{}",
custom_bin_dir.display(),
if cfg!(windows) { ";" } else { ":" },
original_path
);
unsafe {
env::set_var("PATH", &new_path);
}
let direct_works_1 = simulate_new_command_exists("testcmd");
println!("With command in PATH - direct approach: {direct_works_1}");
// Test scenario 2: Simulate broken PATH for lookup but direct execution still works
// (This is tricky to test reliably, but we can at least test that direct execution works)
let direct_absolute_works = simulate_new_command_exists(fake_cmd_path.to_str().unwrap());
println!("Direct absolute path approach: {direct_absolute_works}");
// Restore original PATH
unsafe {
env::set_var("PATH", original_path);
}
// The key test: direct execution of absolute paths should work even when PATH is broken
assert!(direct_absolute_works, "Direct execution with absolute path should work");
}
/// Test Windows-specific command scenarios
#[test]
fn test_windows_specific_command_scenarios() {
if !cfg!(windows) {
println!("Skipping Windows-specific test on non-Windows platform");
return;
}
// Test common Windows commands that might behave differently
let windows_commands = [
"cmd", // Windows command prompt
"where", // Windows equivalent of 'which'
"powershell", // PowerShell (if available)
];
for cmd in &windows_commands {
let direct_result = Command::new(cmd)
.arg("/?") // Windows help flag
.output();
let version_result = Command::new(cmd).arg("--version").output();
let where_result = Command::new("where").arg(cmd).output();
println!("Command '{cmd}':");
println!(" Help (/?) works: {}", direct_result.is_ok());
println!(" Version (--version) works: {}", version_result.is_ok());
println!(
" Found by 'where': {}",
where_result.map(|o| o.status.success()).unwrap_or(false)
);
}
// This test always passes - it's diagnostic
}
/// Test command extension handling on Windows
#[test]
fn test_windows_command_extensions() {
if !cfg!(windows) {
println!("Skipping Windows extension test on non-Windows platform");
return;
}
// Create test files with different Windows executable extensions
let temp_dir = TempDir::new().expect("Could not create temp dir");
let extensions = ["exe", "bat", "cmd"];
for ext in &extensions {
let cmd_path = temp_dir.path().join(format!("testcmd.{ext}"));
let content = match *ext {
"exe" => {
// Can't easily create a real .exe, skip this one
continue;
}
"bat" | "cmd" => "@echo off\nif \"%1\"==\"--version\" echo test-version-1.0\n",
_ => continue,
};
fs::write(&cmd_path, content).expect("Could not write test file");
// Test that our approach can handle different extensions
let works = simulate_new_command_exists(cmd_path.to_str().unwrap());
println!("Command with .{ext} extension works: {works}");
}
// Windows extension test completed
}
/// Integration test that verifies our fix handles the exact VS Code scenario
#[test]
fn test_vscode_integration_scenario() {
use rumdl_lib::vscode::VsCodeExtension;
// Test the actual VsCodeExtension::new() behavior
match VsCodeExtension::new() {
Ok(extension) => {
println!("✓ VS Code extension creation succeeded - VS Code is available");
// Try to check if it's actually installed
match extension.is_installed() {
Ok(installed) => {
println!(" Extension installed: {installed}");
}
Err(e) => {
println!(" Could not check if extension is installed: {e}");
}
}
}
Err(e) => {
println!("VS Code not found: {e}");
// This is expected in most CI environments
assert!(
e.contains("VS Code (or compatible editor) not found"),
"Error message should indicate VS Code not found"
);
}
}
// Test find_all_editors to see what's available
let available_editors = VsCodeExtension::find_all_editors();
println!("Available editors found: {}", available_editors.len());
for (cmd, name) in &available_editors {
println!(" - {name} (command: {cmd})");
}
// Test passes regardless - this is diagnostic
}
}