diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 76b008e..282f482 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -495,7 +495,11 @@ impl BundlerTestHelper { /// Get the path to the banderole binary pub fn get_bundler_path() -> Result { let target_dir = std::env::current_dir()?.join("target"); - let bundler_path = target_dir.join("debug/banderole"); + let bundler_path = if cfg!(windows) { + target_dir.join("debug/banderole.exe") + } else { + target_dir.join("debug/banderole") + }; if !bundler_path.exists() { // Build the bundler if it doesn't exist @@ -553,6 +557,18 @@ impl BundlerTestHelper { ); } + // Debug: Print bundler output + println!( + "Bundler stdout: {}", + String::from_utf8_lossy(&bundle_output.stdout) + ); + if !bundle_output.stderr.is_empty() { + println!( + "Bundler stderr: {}", + String::from_utf8_lossy(&bundle_output.stderr) + ); + } + // Find the created executable let executable_name = custom_name.unwrap_or("test-project"); let executable_path = output_dir.join(if cfg!(windows) { @@ -575,9 +591,31 @@ impl BundlerTestHelper { } if !executable_path.exists() { + // List directory contents for debugging + let dir_contents = fs::read_dir(output_dir) + .map(|entries| { + entries + .filter_map(|e| e.ok()) + .map(|entry| { + let path = entry.path(); + let metadata = fs::metadata(&path).ok(); + format!( + "{} (size: {}, is_file: {})", + entry.file_name().to_string_lossy(), + metadata.as_ref().map(|m| m.len()).unwrap_or(0), + metadata.as_ref().map(|m| m.is_file()).unwrap_or(false) + ) + }) + .collect::>() + }) + .unwrap_or_else(|e| vec![format!("Error reading directory: {}", e)]); + anyhow::bail!( - "Executable was not created at {}", - executable_path.display() + "Executable was not created at {}\nExpected name: {}\nOutput directory: {}\nOutput directory contents: {:?}", + executable_path.display(), + executable_name, + output_dir.display(), + dir_contents ); } @@ -590,39 +628,40 @@ impl BundlerTestHelper { args: &[&str], env_vars: &[(&str, &str)], ) -> Result { - // Windows-specific debugging for "program not found" errors - #[cfg(windows)] - { - if !executable_path.exists() { - anyhow::bail!( - "Windows: Executable does not exist at path: {}\nParent directory exists: {}\nParent directory contents: {:?}", - executable_path.display(), - executable_path.parent().map(|p| p.exists()).unwrap_or(false), - executable_path.parent() - .and_then(|p| fs::read_dir(p).ok()) - .map(|entries| entries.filter_map(|e| e.ok()).map(|e| e.file_name().to_string_lossy().to_string()).collect::>()) - .unwrap_or_else(|| vec!["Could not read directory".to_string()]) - ); - } + // Verify executable exists and is accessible + if !executable_path.exists() { + let parent_contents = executable_path + .parent() + .and_then(|p| fs::read_dir(p).ok()) + .map(|entries| { + entries + .filter_map(|e| e.ok()) + .map(|e| e.file_name().to_string_lossy().to_string()) + .collect::>() + }) + .unwrap_or_else(|| vec!["Could not read directory".to_string()]); - if let Ok(metadata) = fs::metadata(executable_path) { - if !metadata.is_file() { - anyhow::bail!( - "Windows: Path exists but is not a file: {} (is_dir: {})", - executable_path.display(), - metadata.is_dir() - ); - } - println!( - "Windows debug: Executable found, size: {} bytes", - metadata.len() - ); - } else { + anyhow::bail!( + "Executable does not exist at path: {}\nParent directory exists: {}\nParent directory contents: {:?}", + executable_path.display(), + executable_path.parent().map(|p| p.exists()).unwrap_or(false), + parent_contents + ); + } + + if let Ok(metadata) = fs::metadata(executable_path) { + if !metadata.is_file() { anyhow::bail!( - "Windows: Cannot read metadata for executable: {}", - executable_path.display() + "Path exists but is not a file: {} (is_dir: {})", + executable_path.display(), + metadata.is_dir() ); } + } else { + anyhow::bail!( + "Cannot read metadata for executable: {}", + executable_path.display() + ); } // Make executable on Unix @@ -635,13 +674,33 @@ impl BundlerTestHelper { fs::set_permissions(executable_path, perms)?; } - let mut cmd = Command::new(executable_path); + // On Windows, ensure we're using the full path and handle potential issues + let mut cmd = if cfg!(windows) { + // Use the full canonical path on Windows to avoid "program not found" issues + let canonical_path = executable_path.canonicalize().with_context(|| { + format!("Failed to canonicalize path: {}", executable_path.display()) + })?; + println!( + "Windows: Using canonical path: {}", + canonical_path.display() + ); + Command::new(canonical_path) + } else { + Command::new(executable_path) + }; + cmd.args(args); for (key, value) in env_vars { cmd.env(key, value); } + println!( + "Executing: {} with args: {:?}", + executable_path.display(), + args + ); + let output = cmd.output().with_context(|| { format!( "Failed to execute command: {}\nArgs: {:?}\nEnv vars: {:?}\nWorking directory: {:?}", @@ -674,14 +733,11 @@ impl BundlerTestHelper { Ok(result) => result.map_err(|e| anyhow::anyhow!("Command execution failed: {}", e)), Err(_) => { // Timeout occurred, kill the process - #[cfg(unix)] - { + if cfg!(unix) { let _ = std::process::Command::new("kill") .args(["-9", &child_id.to_string()]) .output(); - } - #[cfg(windows)] - { + } else if cfg!(windows) { let _ = std::process::Command::new("taskkill") .args(["/F", "/PID", &child_id.to_string()]) .output(); diff --git a/tests/concurrent_execution_integration_test.rs b/tests/concurrent_execution_integration_test.rs index 3350687..f98d90c 100644 --- a/tests/concurrent_execution_integration_test.rs +++ b/tests/concurrent_execution_integration_test.rs @@ -28,41 +28,40 @@ async fn test_concurrent_first_launch() -> Result<()> { false, // No compression for faster testing )?; - // Windows-specific debugging for executable creation - #[cfg(windows)] - { - eprintln!( - "Windows debug: Executable path created: {}", - executable_path.display() - ); - eprintln!( - "Windows debug: Executable exists: {}", - executable_path.exists() - ); - if executable_path.exists() { - if let Ok(metadata) = std::fs::metadata(&executable_path) { - eprintln!("Windows debug: Executable size: {} bytes", metadata.len()); - eprintln!("Windows debug: Executable is file: {}", metadata.is_file()); - } else { - eprintln!("Windows debug: Cannot read executable metadata"); - } + // Debug executable creation + eprintln!( + "Debug: Executable path created: {}", + executable_path.display() + ); + eprintln!("Debug: Executable exists: {}", executable_path.exists()); + if executable_path.exists() { + if let Ok(metadata) = std::fs::metadata(&executable_path) { + eprintln!("Debug: Executable size: {} bytes", metadata.len()); + eprintln!("Debug: Executable is file: {}", metadata.is_file()); } else { - eprintln!("Windows debug: Executable does not exist!"); - if let Some(parent) = executable_path.parent() { - eprintln!("Windows debug: Parent directory: {}", parent.display()); - eprintln!("Windows debug: Parent exists: {}", parent.exists()); - if let Ok(entries) = std::fs::read_dir(parent) { - eprintln!("Windows debug: Parent directory contents:"); - for entry in entries.flatten() { - eprintln!(" - {}", entry.file_name().to_string_lossy()); - } - } else { - eprintln!("Windows debug: Cannot read parent directory"); + eprintln!("Debug: Cannot read executable metadata"); + } + } else { + eprintln!("Debug: Executable does not exist!"); + if let Some(parent) = executable_path.parent() { + eprintln!("Debug: Parent directory: {}", parent.display()); + eprintln!("Debug: Parent exists: {}", parent.exists()); + if let Ok(entries) = std::fs::read_dir(parent) { + eprintln!("Debug: Parent directory contents:"); + for entry in entries.flatten() { + eprintln!(" - {}", entry.file_name().to_string_lossy()); } + } else { + eprintln!("Debug: Cannot read parent directory"); } } } + // Give the filesystem a moment to settle on Windows + if cfg!(windows) { + std::thread::sleep(std::time::Duration::from_millis(100)); + } + // Clear any existing cache to ensure we test first launch TestCacheManager::clear_application_cache()?; @@ -85,33 +84,54 @@ async fn test_concurrent_first_launch() -> Result<()> { // Wait for all threads to be ready barrier.wait(); + // Add a small staggered delay to reduce race conditions on Windows + std::thread::sleep(std::time::Duration::from_millis(i as u64 * 10)); + let thread_start = Instant::now(); - // Execute the binary using the test helper - let output = BundlerTestHelper::run_executable( - executable_path.as_ref(), - &[&format!("--thread-id={i}")], - &[("TEST_VAR", &format!("thread_{i}"))], - ) - .map_err(|e| { - #[cfg(windows)] - { - eprintln!( - "Windows debug - Thread {}: Failed to execute binary at {}", - i, - executable_path.as_ref().display() - ); - eprintln!("Windows debug - Thread {i}: Error details: {e:?}"); - eprintln!("Windows debug - Thread {i}: Error chain:"); - let mut source = e.source(); - let mut level = 0; - while let Some(err) = source { - eprintln!("Windows debug - Thread {i}: Level {level}: {err}"); - source = err.source(); - level += 1; + // Execute the binary using the test helper with retry logic for Windows + let mut last_error = None; + let mut output = None; + + for attempt in 1..=3 { + match BundlerTestHelper::run_executable( + executable_path.as_ref(), + &[&format!("--thread-id={i}")], + &[("TEST_VAR", &format!("thread_{i}"))], + ) { + Ok(result) => { + output = Some(result); + break; + } + Err(e) => { + eprintln!("Thread {i}: Attempt {attempt} failed: {e}"); + last_error = Some(e); + if attempt < 3 { + std::thread::sleep(std::time::Duration::from_millis( + 100 * attempt as u64, + )); + } } } - anyhow::anyhow!("Failed to execute binary: {e}") + } + + let output = output.ok_or_else(|| { + let e = last_error.unwrap(); + eprintln!( + "Thread {}: Failed to execute binary at {} after 3 attempts", + i, + executable_path.as_ref().display() + ); + eprintln!("Thread {i}: Final error details: {e:?}"); + eprintln!("Thread {i}: Error chain:"); + let mut source = e.source(); + let mut level = 0; + while let Some(err) = source { + eprintln!("Thread {i}: Level {level}: {err}"); + source = err.source(); + level += 1; + } + anyhow::anyhow!("Failed to execute binary after retries: {e}") })?; let duration = thread_start.elapsed(); @@ -218,50 +238,43 @@ async fn test_cached_concurrent_execution() -> Result<()> { false, )?; - // Windows-specific debugging for executable creation - #[cfg(windows)] - { - eprintln!( - "Windows debug (cached): Executable path created: {}", - executable_path.display() - ); - eprintln!( - "Windows debug (cached): Executable exists: {}", - executable_path.exists() - ); - if executable_path.exists() { - if let Ok(metadata) = std::fs::metadata(&executable_path) { - eprintln!( - "Windows debug (cached): Executable size: {} bytes", - metadata.len() - ); - eprintln!( - "Windows debug (cached): Executable is file: {}", - metadata.is_file() - ); - } else { - eprintln!("Windows debug (cached): Cannot read executable metadata"); - } + // Debug executable creation + eprintln!( + "Debug (cached): Executable path created: {}", + executable_path.display() + ); + eprintln!( + "Debug (cached): Executable exists: {}", + executable_path.exists() + ); + if executable_path.exists() { + if let Ok(metadata) = std::fs::metadata(&executable_path) { + eprintln!("Debug (cached): Executable size: {} bytes", metadata.len()); + eprintln!("Debug (cached): Executable is file: {}", metadata.is_file()); } else { - eprintln!("Windows debug (cached): Executable does not exist!"); - if let Some(parent) = executable_path.parent() { - eprintln!( - "Windows debug (cached): Parent directory: {}", - parent.display() - ); - eprintln!("Windows debug (cached): Parent exists: {}", parent.exists()); - if let Ok(entries) = std::fs::read_dir(parent) { - eprintln!("Windows debug (cached): Parent directory contents:"); - for entry in entries.flatten() { - eprintln!(" - {}", entry.file_name().to_string_lossy()); - } - } else { - eprintln!("Windows debug (cached): Cannot read parent directory"); + eprintln!("Debug (cached): Cannot read executable metadata"); + } + } else { + eprintln!("Debug (cached): Executable does not exist!"); + if let Some(parent) = executable_path.parent() { + eprintln!("Debug (cached): Parent directory: {}", parent.display()); + eprintln!("Debug (cached): Parent exists: {}", parent.exists()); + if let Ok(entries) = std::fs::read_dir(parent) { + eprintln!("Debug (cached): Parent directory contents:"); + for entry in entries.flatten() { + eprintln!(" - {}", entry.file_name().to_string_lossy()); } + } else { + eprintln!("Debug (cached): Cannot read parent directory"); } } } + // Give the filesystem a moment to settle on Windows + if cfg!(windows) { + std::thread::sleep(std::time::Duration::from_millis(100)); + } + // Clear cache and run once to populate it TestCacheManager::clear_application_cache()?; @@ -290,32 +303,54 @@ async fn test_cached_concurrent_execution() -> Result<()> { let handle = thread::spawn(move || -> Result<(usize, Duration)> { barrier.wait(); + // Add a small staggered delay to reduce race conditions on Windows + std::thread::sleep(std::time::Duration::from_millis(i as u64 * 10)); + let thread_start = Instant::now(); - let output = BundlerTestHelper::run_executable( - executable_path.as_ref(), - &[], - &[("TEST_VAR", &format!("cached_{i}"))], - ) - .map_err(|e| { - #[cfg(windows)] - { - eprintln!( - "Windows debug - Cached thread {}: Failed to execute binary at {}", - i, - executable_path.as_ref().display() - ); - eprintln!("Windows debug - Cached thread {e}: Error details: {e:?}"); - eprintln!("Windows debug - Cached thread {i}: Error chain:"); - let mut source = e.source(); - let mut level = 0; - while let Some(err) = source { - eprintln!("Windows debug - Cached thread {i}: Level {level}: {err}"); - source = err.source(); - level += 1; + // Execute the binary using the test helper with retry logic for Windows + let mut last_error = None; + let mut output = None; + + for attempt in 1..=3 { + match BundlerTestHelper::run_executable( + executable_path.as_ref(), + &[], + &[("TEST_VAR", &format!("cached_{i}"))], + ) { + Ok(result) => { + output = Some(result); + break; + } + Err(e) => { + eprintln!("Cached thread {i}: Attempt {attempt} failed: {e}"); + last_error = Some(e); + if attempt < 3 { + std::thread::sleep(std::time::Duration::from_millis( + 100 * attempt as u64, + )); + } } } - anyhow::anyhow!("Failed to execute binary: {}", e) + } + + let output = output.ok_or_else(|| { + let e = last_error.unwrap(); + eprintln!( + "Cached thread {}: Failed to execute binary at {} after 3 attempts", + i, + executable_path.as_ref().display() + ); + eprintln!("Cached thread {i}: Final error details: {e:?}"); + eprintln!("Cached thread {i}: Error chain:"); + let mut source = e.source(); + let mut level = 0; + while let Some(err) = source { + eprintln!("Cached thread {i}: Level {level}: {err}"); + source = err.source(); + level += 1; + } + anyhow::anyhow!("Failed to execute binary after retries: {}", e) })?; let duration = thread_start.elapsed();