mirror of
https://github.com/zhom/banderole.git
synced 2026-06-06 06:23:53 +02:00
feat: better handling of concurrent execution on first launch
This commit is contained in:
Generated
+80
-2
@@ -149,6 +149,7 @@ dependencies = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serial_test",
|
||||
"sha2",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
@@ -478,6 +479,21 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
@@ -494,6 +510,17 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
@@ -529,6 +556,7 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
@@ -729,7 +757,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"socket2 0.6.0",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
@@ -1419,6 +1447,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scc"
|
||||
version = "2.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22b2d775fb28f245817589471dd49c5edf64237f4a19d10ce9a92ff4651a27f4"
|
||||
dependencies = [
|
||||
"sdd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.27"
|
||||
@@ -1434,6 +1471,12 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "sdd"
|
||||
version = "3.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
@@ -1501,6 +1544,31 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_test"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"log",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"scc",
|
||||
"serial_test_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_test_derive"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
@@ -1566,6 +1634,16 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
@@ -1713,7 +1791,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"socket2",
|
||||
"socket2 0.5.10",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
@@ -40,6 +40,10 @@ harness = true
|
||||
name = "workspace_integration_test"
|
||||
harness = true
|
||||
|
||||
[[test]]
|
||||
name = "concurrent_execution_integration_test"
|
||||
harness = true
|
||||
|
||||
# Run tests sequentially to avoid resource conflicts
|
||||
[profile.test]
|
||||
opt-level = 0
|
||||
|
||||
+527
-201
File diff suppressed because it is too large
Load Diff
+10
-2
@@ -29,6 +29,9 @@ enum Commands {
|
||||
/// Custom name for the executable (optional)
|
||||
#[arg(short, long)]
|
||||
name: Option<String>,
|
||||
/// Disable compression for faster bundling (useful for testing)
|
||||
#[arg(long)]
|
||||
no_compression: bool,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -37,8 +40,13 @@ async fn main() -> anyhow::Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
Commands::Bundle { path, output, name } => {
|
||||
bundler::bundle_project(path, output, name).await?;
|
||||
Commands::Bundle {
|
||||
path,
|
||||
output,
|
||||
name,
|
||||
no_compression,
|
||||
} => {
|
||||
bundler::bundle_project(path, output, name, no_compression).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-20
@@ -1,12 +1,12 @@
|
||||
use crate::platform::Platform;
|
||||
use anyhow::{Context, Result};
|
||||
use futures_util::StreamExt;
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
use tokio::fs;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
static ref NODE_VERSION_CACHE: Mutex<HashMap<String, PathBuf>> = Mutex::new(HashMap::new());
|
||||
@@ -19,15 +19,6 @@ pub struct NodeDownloader {
|
||||
}
|
||||
|
||||
impl NodeDownloader {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(cache_dir: PathBuf, node_version: String) -> Self {
|
||||
Self {
|
||||
platform: Platform::current(),
|
||||
cache_dir,
|
||||
node_version,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_persistent_cache(node_version: String) -> Result<Self> {
|
||||
let cache_dir = Self::get_persistent_cache_dir()?;
|
||||
Ok(Self {
|
||||
@@ -57,28 +48,32 @@ impl NodeDownloader {
|
||||
pub async fn ensure_node_binary(&self) -> Result<PathBuf> {
|
||||
// Create cache key for this version and platform
|
||||
let cache_key = format!("{}:{}", self.node_version, self.platform);
|
||||
|
||||
|
||||
// Check in-memory cache first
|
||||
{
|
||||
let cache = NODE_VERSION_CACHE.lock().map_err(|e| anyhow::anyhow!("Failed to acquire cache lock: {}", e))?;
|
||||
let cache = NODE_VERSION_CACHE
|
||||
.lock()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to acquire cache lock: {}", e))?;
|
||||
if let Some(cached_path) = cache.get(&cache_key) {
|
||||
if cached_path.exists() {
|
||||
return Ok(cached_path.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check disk cache
|
||||
let node_dir = self.cache_dir
|
||||
let node_dir = self
|
||||
.cache_dir
|
||||
.join("node")
|
||||
.join(&self.node_version)
|
||||
.join(self.platform.to_string());
|
||||
|
||||
|
||||
let node_executable = node_dir.join(self.platform.node_executable_path());
|
||||
|
||||
if node_executable.exists() {
|
||||
// Update in-memory cache
|
||||
let mut cache = NODE_VERSION_CACHE.lock()
|
||||
let mut cache = NODE_VERSION_CACHE
|
||||
.lock()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to acquire cache lock: {}", e))?;
|
||||
cache.insert(cache_key, node_executable.clone());
|
||||
return Ok(node_executable);
|
||||
@@ -159,14 +154,15 @@ impl NodeDownloader {
|
||||
fs::remove_file(&archive_path)
|
||||
.await
|
||||
.context("Failed to remove archive file")?;
|
||||
|
||||
|
||||
// Update in-memory cache with the path to the node executable
|
||||
let node_executable_path = target_dir.join(self.platform.node_executable_path());
|
||||
let mut cache = NODE_VERSION_CACHE.lock()
|
||||
let mut cache = NODE_VERSION_CACHE
|
||||
.lock()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to acquire cache lock: {}", e))?;
|
||||
cache.insert(
|
||||
format!("{}:{}", self.node_version, self.platform),
|
||||
node_executable_path.clone()
|
||||
node_executable_path.clone(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
|
||||
+132
-33
@@ -1,9 +1,11 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use anyhow::Result;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
use tempfile::TempDir;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use anyhow::Result;
|
||||
|
||||
/// Represents different project types for testing
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -47,12 +49,14 @@ impl TestProject {
|
||||
}
|
||||
|
||||
pub fn with_dependency(mut self, name: &str, version: &str) -> Self {
|
||||
self.dependencies.push((name.to_string(), version.to_string()));
|
||||
self.dependencies
|
||||
.push((name.to_string(), version.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_dev_dependency(mut self, name: &str, version: &str) -> Self {
|
||||
self.dev_dependencies.push((name.to_string(), version.to_string()));
|
||||
self.dev_dependencies
|
||||
.push((name.to_string(), version.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -67,7 +71,9 @@ impl TestProject {
|
||||
}
|
||||
|
||||
pub fn typescript(mut self, out_dir: &str) -> Self {
|
||||
self.project_type = ProjectType::TypeScript { out_dir: out_dir.to_string() };
|
||||
self.project_type = ProjectType::TypeScript {
|
||||
out_dir: out_dir.to_string(),
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
@@ -264,7 +270,8 @@ process.exit(0);"#;
|
||||
|
||||
fs::write(self.project_path.join("package.json"), package_json)?;
|
||||
|
||||
let tsconfig_json = format!(r#"{{
|
||||
let tsconfig_json = format!(
|
||||
r#"{{
|
||||
"compilerOptions": {{
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
@@ -272,7 +279,8 @@ process.exit(0);"#;
|
||||
"rootDir": "./src",
|
||||
"strict": true
|
||||
}}
|
||||
}}"#);
|
||||
}}"#
|
||||
);
|
||||
|
||||
fs::write(self.project_path.join("tsconfig.json"), tsconfig_json)?;
|
||||
|
||||
@@ -301,7 +309,10 @@ try {
|
||||
console.log("Marker file not found");
|
||||
}"#;
|
||||
|
||||
fs::write(self.project_path.join(out_dir).join("index.js"), compiled_index_js)?;
|
||||
fs::write(
|
||||
self.project_path.join(out_dir).join("index.js"),
|
||||
compiled_index_js,
|
||||
)?;
|
||||
|
||||
// Create a marker file to verify correct source directory is used
|
||||
let marker_js = format!(r#"module.exports = {{ source: "{}" }};"#, out_dir);
|
||||
@@ -316,7 +327,8 @@ try {
|
||||
fs::create_dir_all(&self.project_path)?;
|
||||
|
||||
// Create workspace root package.json
|
||||
let workspace_package_json = format!(r#"{{
|
||||
let workspace_package_json = format!(
|
||||
r#"{{
|
||||
"name": "test-workspace",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
@@ -326,7 +338,10 @@ try {
|
||||
"dependencies": {{
|
||||
{}
|
||||
}}
|
||||
}}"#, config.name, self.format_dependencies(&config.dependencies));
|
||||
}}"#,
|
||||
config.name.replace("/", "-"), // Replace slashes to make valid package name
|
||||
self.format_dependencies(&config.dependencies)
|
||||
);
|
||||
|
||||
fs::write(workspace_root.join("package.json"), workspace_package_json)?;
|
||||
|
||||
@@ -374,21 +389,27 @@ process.exit(0);"#;
|
||||
fs::create_dir_all(&self.project_path)?;
|
||||
|
||||
// Create pnpm-workspace.yaml
|
||||
let pnpm_workspace = format!(r#"packages:
|
||||
let pnpm_workspace = format!(
|
||||
r#"packages:
|
||||
- '{}'
|
||||
"#, config.name);
|
||||
"#,
|
||||
config.name
|
||||
);
|
||||
|
||||
fs::write(workspace_root.join("pnpm-workspace.yaml"), pnpm_workspace)?;
|
||||
|
||||
// Create workspace root package.json
|
||||
let workspace_package_json = format!(r#"{{
|
||||
let workspace_package_json = format!(
|
||||
r#"{{
|
||||
"name": "test-pnpm-workspace",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"dependencies": {{
|
||||
{}
|
||||
}}
|
||||
}}"#, self.format_dependencies(&config.dependencies));
|
||||
}}"#,
|
||||
self.format_dependencies(&config.dependencies)
|
||||
);
|
||||
|
||||
fs::write(workspace_root.join("package.json"), workspace_package_json)?;
|
||||
|
||||
@@ -434,7 +455,8 @@ process.exit(0);"#;
|
||||
let deps = self.format_dependencies(&config.dependencies);
|
||||
let dev_deps = self.format_dependencies(&config.dev_dependencies);
|
||||
|
||||
let package_json = format!(r#"{{
|
||||
let package_json = format!(
|
||||
r#"{{
|
||||
"name": "{}",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
@@ -443,8 +465,16 @@ process.exit(0);"#;
|
||||
}}{}{}
|
||||
}}"#,
|
||||
config.name,
|
||||
if deps.is_empty() { String::new() } else { format!(",\n \"dependencies\": {{\n{}\n }}", deps) },
|
||||
if dev_deps.is_empty() { String::new() } else { format!(",\n \"devDependencies\": {{\n{}\n }}", dev_deps) }
|
||||
if deps.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(",\n \"dependencies\": {{\n{}\n }}", deps)
|
||||
},
|
||||
if dev_deps.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(",\n \"devDependencies\": {{\n{}\n }}", dev_deps)
|
||||
}
|
||||
);
|
||||
|
||||
Ok(package_json)
|
||||
@@ -466,13 +496,11 @@ impl BundlerTestHelper {
|
||||
pub fn get_bundler_path() -> Result<PathBuf> {
|
||||
let target_dir = std::env::current_dir()?.join("target");
|
||||
let bundler_path = target_dir.join("debug/banderole");
|
||||
|
||||
|
||||
if !bundler_path.exists() {
|
||||
// Build the bundler if it doesn't exist
|
||||
println!("Building banderole...");
|
||||
let output = Command::new("cargo")
|
||||
.args(["build"])
|
||||
.output()?;
|
||||
let output = Command::new("cargo").args(["build"]).output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
anyhow::bail!(
|
||||
@@ -490,9 +518,19 @@ impl BundlerTestHelper {
|
||||
project_path: &Path,
|
||||
output_dir: &Path,
|
||||
custom_name: Option<&str>,
|
||||
) -> Result<PathBuf> {
|
||||
Self::bundle_project_with_compression(project_path, output_dir, custom_name, true)
|
||||
}
|
||||
|
||||
/// Bundle a project with compression control and return the path to the created executable
|
||||
pub fn bundle_project_with_compression(
|
||||
project_path: &Path,
|
||||
output_dir: &Path,
|
||||
custom_name: Option<&str>,
|
||||
enable_compression: bool,
|
||||
) -> Result<PathBuf> {
|
||||
let bundler_path = Self::get_bundler_path()?;
|
||||
|
||||
|
||||
let mut cmd = Command::new(&bundler_path);
|
||||
cmd.args(["bundle", project_path.to_str().unwrap()])
|
||||
.current_dir(output_dir);
|
||||
@@ -501,6 +539,10 @@ impl BundlerTestHelper {
|
||||
cmd.args(["--name", name]);
|
||||
}
|
||||
|
||||
if !enable_compression {
|
||||
cmd.arg("--no-compression");
|
||||
}
|
||||
|
||||
let bundle_output = Self::run_with_timeout(&mut cmd, Duration::from_secs(300))?;
|
||||
|
||||
if !bundle_output.status.success() {
|
||||
@@ -526,14 +568,17 @@ impl BundlerTestHelper {
|
||||
} else {
|
||||
format!("{}-bundle", executable_name)
|
||||
});
|
||||
|
||||
|
||||
if bundle_executable_path.exists() {
|
||||
return Ok(bundle_executable_path);
|
||||
}
|
||||
}
|
||||
|
||||
if !executable_path.exists() {
|
||||
anyhow::bail!("Executable was not created at {}", executable_path.display());
|
||||
anyhow::bail!(
|
||||
"Executable was not created at {}",
|
||||
executable_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(executable_path)
|
||||
@@ -557,7 +602,7 @@ impl BundlerTestHelper {
|
||||
|
||||
let mut cmd = Command::new(executable_path);
|
||||
cmd.args(args);
|
||||
|
||||
|
||||
for (key, value) in env_vars {
|
||||
cmd.env(key, value);
|
||||
}
|
||||
@@ -605,6 +650,63 @@ impl BundlerTestHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/// Test cache management utilities
|
||||
pub struct TestCacheManager;
|
||||
|
||||
impl TestCacheManager {
|
||||
/// Clear application cache for testing
|
||||
pub fn clear_application_cache() -> Result<()> {
|
||||
// Determine cache directory based on platform
|
||||
let cache_dir = if cfg!(windows) {
|
||||
if let Some(local_app_data) = std::env::var_os("LOCALAPPDATA") {
|
||||
std::path::PathBuf::from(local_app_data).join("banderole")
|
||||
} else {
|
||||
return Ok(()); // Can't determine cache dir, skip cleanup
|
||||
}
|
||||
} else {
|
||||
if let Some(xdg_cache) = std::env::var_os("XDG_CACHE_HOME") {
|
||||
std::path::PathBuf::from(xdg_cache).join("banderole")
|
||||
} else if let Some(home) = std::env::var_os("HOME") {
|
||||
std::path::PathBuf::from(home)
|
||||
.join(".cache")
|
||||
.join("banderole")
|
||||
} else {
|
||||
std::path::PathBuf::from("/tmp").join("banderole-cache")
|
||||
}
|
||||
};
|
||||
|
||||
if cache_dir.exists() {
|
||||
println!("Clearing application cache at: {}", cache_dir.display());
|
||||
|
||||
// Only remove application cache directories, not the Node.js cache
|
||||
if let Ok(entries) = std::fs::read_dir(&cache_dir) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
let dir_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
|
||||
|
||||
// Only remove directories that look like UUIDs (application cache)
|
||||
// Keep "node" directory (Node.js binaries cache)
|
||||
if dir_name != "node" && dir_name.len() > 10 {
|
||||
if let Err(e) = std::fs::remove_dir_all(&path) {
|
||||
println!(
|
||||
"Warning: Failed to remove cache directory {}: {}",
|
||||
path.display(),
|
||||
e
|
||||
);
|
||||
} else {
|
||||
println!("Removed cache directory: {}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Assertion helpers for test verification
|
||||
pub struct TestAssertions;
|
||||
|
||||
@@ -617,7 +719,7 @@ impl TestAssertions {
|
||||
args: &[&str],
|
||||
) -> Result<()> {
|
||||
let output = BundlerTestHelper::run_executable(executable_path, args, env_vars)?;
|
||||
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
@@ -644,12 +746,9 @@ impl TestAssertions {
|
||||
}
|
||||
|
||||
/// Assert that dependency tests pass in the bundled executable
|
||||
pub fn assert_dependency_test_passes(
|
||||
executable_path: &Path,
|
||||
test_marker: &str,
|
||||
) -> Result<()> {
|
||||
pub fn assert_dependency_test_passes(executable_path: &Path, test_marker: &str) -> Result<()> {
|
||||
let output = BundlerTestHelper::run_executable(executable_path, &[], &[])?;
|
||||
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
|
||||
@@ -672,4 +771,4 @@ impl TestAssertions {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,427 @@
|
||||
mod common;
|
||||
|
||||
use anyhow::Result;
|
||||
use common::{BundlerTestHelper, TestCacheManager, TestProject, TestProjectManager};
|
||||
use serial_test::serial;
|
||||
use std::process::Command;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Test concurrent execution during first launch
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_concurrent_first_launch() -> Result<()> {
|
||||
println!("Testing concurrent execution during first launch...");
|
||||
|
||||
// Create a simple test project
|
||||
let project = TestProject::new("concurrent-test-app").with_dependency("uuid", "^9.0.1");
|
||||
|
||||
let manager = TestProjectManager::create(project)?;
|
||||
manager.install_dependencies()?;
|
||||
|
||||
// Bundle the project
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("concurrent-test"),
|
||||
false, // No compression for faster testing
|
||||
)?;
|
||||
|
||||
// Clear any existing cache to ensure we test first launch
|
||||
TestCacheManager::clear_application_cache()?;
|
||||
|
||||
// Number of concurrent executions to test
|
||||
const NUM_CONCURRENT: usize = 5;
|
||||
|
||||
// Use a barrier to synchronize the start of all threads
|
||||
let barrier = Arc::new(Barrier::new(NUM_CONCURRENT));
|
||||
let executable_path = Arc::new(executable_path);
|
||||
|
||||
let mut handles = Vec::new();
|
||||
let start_time = Instant::now();
|
||||
|
||||
// Spawn multiple threads that will execute the binary concurrently
|
||||
for i in 0..NUM_CONCURRENT {
|
||||
let barrier = Arc::clone(&barrier);
|
||||
let executable_path = Arc::clone(&executable_path);
|
||||
|
||||
let handle = thread::spawn(move || -> Result<(usize, Duration, String)> {
|
||||
// Wait for all threads to be ready
|
||||
barrier.wait();
|
||||
|
||||
let thread_start = Instant::now();
|
||||
|
||||
// Execute the binary
|
||||
let output = Command::new(executable_path.as_ref())
|
||||
.env("TEST_VAR", format!("thread_{}", i))
|
||||
.args(&[format!("--thread-id={}", i)])
|
||||
.output()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to execute binary: {}", e))?;
|
||||
|
||||
let duration = thread_start.elapsed();
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Thread {} failed with exit code {:?}. Stderr: {}",
|
||||
i,
|
||||
output.status.code(),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
|
||||
Ok((i, duration, stdout))
|
||||
});
|
||||
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
// Wait for all threads to complete and collect results
|
||||
let mut results = Vec::new();
|
||||
for handle in handles {
|
||||
let result = handle
|
||||
.join()
|
||||
.map_err(|e| anyhow::anyhow!("Thread panicked: {:?}", e))??;
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
let total_time = start_time.elapsed();
|
||||
println!("Total concurrent execution time: {:?}", total_time);
|
||||
|
||||
// Verify all executions succeeded
|
||||
assert_eq!(
|
||||
results.len(),
|
||||
NUM_CONCURRENT,
|
||||
"Not all threads completed successfully"
|
||||
);
|
||||
|
||||
// Verify each execution produced expected output
|
||||
for (thread_id, duration, stdout) in &results {
|
||||
println!("Thread {} completed in {:?}", thread_id, duration);
|
||||
|
||||
// Check for expected output
|
||||
assert!(
|
||||
stdout.contains("Hello from test project!"),
|
||||
"Thread {} missing expected greeting in output: {}",
|
||||
thread_id,
|
||||
stdout
|
||||
);
|
||||
|
||||
assert!(
|
||||
stdout.contains(&format!("thread_{}", thread_id)),
|
||||
"Thread {} missing environment variable in output: {}",
|
||||
thread_id,
|
||||
stdout
|
||||
);
|
||||
|
||||
assert!(
|
||||
stdout.contains(&format!("--thread-id={}", thread_id)),
|
||||
"Thread {} missing argument in output: {}",
|
||||
thread_id,
|
||||
stdout
|
||||
);
|
||||
}
|
||||
|
||||
// Verify that the execution was properly queued (no thread should have taken too long)
|
||||
let max_duration = results
|
||||
.iter()
|
||||
.map(|(_, duration, _)| *duration)
|
||||
.max()
|
||||
.unwrap();
|
||||
let min_duration = results
|
||||
.iter()
|
||||
.map(|(_, duration, _)| *duration)
|
||||
.min()
|
||||
.unwrap();
|
||||
|
||||
println!("Duration range: {:?} - {:?}", min_duration, max_duration);
|
||||
|
||||
// The difference shouldn't be too extreme if queueing is working properly
|
||||
// Allow up to 30 seconds difference for extraction + queue processing
|
||||
assert!(
|
||||
max_duration - min_duration < Duration::from_secs(30),
|
||||
"Duration difference too large: {:?}, suggesting queue is not working properly",
|
||||
max_duration - min_duration
|
||||
);
|
||||
|
||||
println!("✅ Concurrent first launch test passed!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that subsequent executions after cache is populated are fast
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_cached_concurrent_execution() -> Result<()> {
|
||||
println!("Testing concurrent execution with populated cache...");
|
||||
|
||||
// Create a simple test project
|
||||
let project = TestProject::new("cached-concurrent-app").with_dependency("lodash", "^4.17.21");
|
||||
|
||||
let manager = TestProjectManager::create(project)?;
|
||||
manager.install_dependencies()?;
|
||||
|
||||
// Bundle the project
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("cached-concurrent-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Clear cache and run once to populate it
|
||||
TestCacheManager::clear_application_cache()?;
|
||||
|
||||
println!("Populating cache with initial run...");
|
||||
let initial_output = Command::new(&executable_path)
|
||||
.env("TEST_VAR", "initial")
|
||||
.output()?;
|
||||
|
||||
assert!(
|
||||
initial_output.status.success(),
|
||||
"Initial run failed: {}",
|
||||
String::from_utf8_lossy(&initial_output.stderr)
|
||||
);
|
||||
|
||||
// Now test concurrent execution with populated cache
|
||||
const NUM_CONCURRENT: usize = 8;
|
||||
let barrier = Arc::new(Barrier::new(NUM_CONCURRENT));
|
||||
let executable_path = Arc::new(executable_path);
|
||||
|
||||
let mut handles = Vec::new();
|
||||
let start_time = Instant::now();
|
||||
|
||||
for i in 0..NUM_CONCURRENT {
|
||||
let barrier = Arc::clone(&barrier);
|
||||
let executable_path = Arc::clone(&executable_path);
|
||||
|
||||
let handle = thread::spawn(move || -> Result<(usize, Duration)> {
|
||||
barrier.wait();
|
||||
|
||||
let thread_start = Instant::now();
|
||||
|
||||
let output = Command::new(executable_path.as_ref())
|
||||
.env("TEST_VAR", format!("cached_{}", i))
|
||||
.output()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to execute binary: {}", e))?;
|
||||
|
||||
let duration = thread_start.elapsed();
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Cached thread {} failed: {}",
|
||||
i,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
|
||||
Ok((i, duration))
|
||||
});
|
||||
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
let mut results = Vec::new();
|
||||
for handle in handles {
|
||||
let result = handle
|
||||
.join()
|
||||
.map_err(|e| anyhow::anyhow!("Thread panicked: {:?}", e))??;
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
let total_time = start_time.elapsed();
|
||||
println!("Total cached concurrent execution time: {:?}", total_time);
|
||||
|
||||
// Verify all executions succeeded
|
||||
assert_eq!(results.len(), NUM_CONCURRENT);
|
||||
|
||||
// With cache populated, all executions should be relatively fast
|
||||
for (thread_id, duration) in &results {
|
||||
println!("Cached thread {} completed in {:?}", thread_id, duration);
|
||||
|
||||
// Each execution should be fast since cache is populated
|
||||
assert!(
|
||||
*duration < Duration::from_secs(10),
|
||||
"Cached execution took too long: {:?} for thread {}",
|
||||
duration,
|
||||
thread_id
|
||||
);
|
||||
}
|
||||
|
||||
println!("✅ Cached concurrent execution test passed!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test queue ordering - verify that processes are executed in the order they were queued
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_queue_ordering() -> Result<()> {
|
||||
println!("Testing queue ordering...");
|
||||
|
||||
// Create a test project that takes a bit of time to execute
|
||||
let project = TestProject::new("queue-order-app");
|
||||
let manager = TestProjectManager::create(project)?;
|
||||
|
||||
// Create a custom index.js that logs timing information
|
||||
let index_js = r#"
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Get thread ID from arguments
|
||||
const threadId = process.argv.find(arg => arg.startsWith('--thread-id='))?.split('=')[1] || 'unknown';
|
||||
const startTime = Date.now();
|
||||
|
||||
console.log(`Thread ${threadId} started at ${startTime}`);
|
||||
console.log("Hello from test project!");
|
||||
console.log("Node version:", process.version);
|
||||
|
||||
// Simulate some work
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < 100) {
|
||||
// Busy wait for 100ms to simulate work
|
||||
}
|
||||
|
||||
console.log(`Thread ${threadId} completed at ${Date.now()}`);
|
||||
console.log("All tests completed!");
|
||||
process.exit(0);
|
||||
"#;
|
||||
|
||||
std::fs::write(manager.project_path().join("index.js"), index_js)?;
|
||||
|
||||
// Bundle the project
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("queue-order-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Clear cache to ensure we test first launch queueing
|
||||
TestCacheManager::clear_application_cache()?;
|
||||
|
||||
const NUM_THREADS: usize = 4;
|
||||
let barrier = Arc::new(Barrier::new(NUM_THREADS));
|
||||
let executable_path = Arc::new(executable_path);
|
||||
|
||||
let mut handles = Vec::new();
|
||||
|
||||
for i in 0..NUM_THREADS {
|
||||
let barrier = Arc::clone(&barrier);
|
||||
let executable_path = Arc::clone(&executable_path);
|
||||
|
||||
let handle = thread::spawn(move || -> Result<(usize, String)> {
|
||||
barrier.wait();
|
||||
|
||||
// Add a small delay to ensure threads start in order
|
||||
thread::sleep(Duration::from_millis(i as u64 * 10));
|
||||
|
||||
let output = Command::new(executable_path.as_ref())
|
||||
.args(&[format!("--thread-id={}", i)])
|
||||
.output()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to execute binary: {}", e))?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Queue order thread {} failed: {}",
|
||||
i,
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
));
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||
Ok((i, stdout))
|
||||
});
|
||||
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
let mut results = Vec::new();
|
||||
for handle in handles {
|
||||
let result = handle
|
||||
.join()
|
||||
.map_err(|e| anyhow::anyhow!("Thread panicked: {:?}", e))??;
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
// Verify all executions succeeded
|
||||
assert_eq!(results.len(), NUM_THREADS);
|
||||
|
||||
for (thread_id, stdout) in &results {
|
||||
println!(
|
||||
"Queue order thread {} output: {}",
|
||||
thread_id,
|
||||
stdout.lines().next().unwrap_or("")
|
||||
);
|
||||
|
||||
assert!(
|
||||
stdout.contains(&format!("Thread {} started", thread_id)),
|
||||
"Thread {} missing start message",
|
||||
thread_id
|
||||
);
|
||||
|
||||
assert!(
|
||||
stdout.contains(&format!("Thread {} completed", thread_id)),
|
||||
"Thread {} missing completion message",
|
||||
thread_id
|
||||
);
|
||||
}
|
||||
|
||||
println!("✅ Queue ordering test passed!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test recovery from failed extraction
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_extraction_failure_recovery() -> Result<()> {
|
||||
println!("Testing recovery from extraction failure...");
|
||||
|
||||
// Create a simple test project
|
||||
let project = TestProject::new("recovery-test-app");
|
||||
let manager = TestProjectManager::create(project)?;
|
||||
|
||||
// Bundle the project
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("recovery-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Clear cache
|
||||
TestCacheManager::clear_application_cache()?;
|
||||
|
||||
// Test that after clearing cache, the binary still works
|
||||
let output = Command::new(&executable_path)
|
||||
.env("TEST_VAR", "recovery_test")
|
||||
.output()?;
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"Recovery test failed: {}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(
|
||||
stdout.contains("Hello from test project!"),
|
||||
"Recovery test missing expected output: {}",
|
||||
stdout
|
||||
);
|
||||
|
||||
println!("✅ Extraction failure recovery test passed!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cleanup function to be called after all tests
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_zzz_cleanup_cache() -> Result<()> {
|
||||
println!("Cleaning up application cache after all tests...");
|
||||
|
||||
// This test runs last due to the "zzz" prefix, ensuring cleanup happens after other tests
|
||||
TestCacheManager::clear_application_cache()?;
|
||||
|
||||
println!("✅ Cache cleanup completed!");
|
||||
Ok(())
|
||||
}
|
||||
+64
-24
@@ -4,7 +4,7 @@ use std::process::Command;
|
||||
use std::time::Duration;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_bundle_and_run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
@@ -95,12 +95,16 @@ process.exit(0);"#;
|
||||
);
|
||||
}
|
||||
|
||||
// Bundle the test app
|
||||
// Bundle the test app)
|
||||
println!("Bundling test app...");
|
||||
|
||||
let mut bundle_cmd = Command::new(&banderole_path);
|
||||
bundle_cmd
|
||||
.args(["bundle", test_app_path.to_str().unwrap()])
|
||||
.args([
|
||||
"bundle",
|
||||
test_app_path.to_str().unwrap(),
|
||||
"--no-compression",
|
||||
])
|
||||
.current_dir(temp_dir.path());
|
||||
|
||||
let bundle_output = bundle_cmd.output()?;
|
||||
@@ -204,7 +208,7 @@ process.exit(0);"#;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_node_version_detection() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
@@ -246,7 +250,7 @@ process.exit(0);"#;
|
||||
.unwrap()
|
||||
.join("target/release/banderole");
|
||||
|
||||
// Bundle the test app
|
||||
// Bundle the test app (keep compression for this test to verify it works)
|
||||
println!("Bundling test app with .nvmrc...");
|
||||
let mut bundle_cmd = Command::new(&banderole_path);
|
||||
bundle_cmd
|
||||
@@ -343,7 +347,7 @@ process.exit(0);"#;
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_output_path_collision_handling() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
@@ -383,12 +387,16 @@ async fn test_output_path_collision_handling() -> Result<(), Box<dyn std::error:
|
||||
);
|
||||
}
|
||||
|
||||
// Bundle the test app
|
||||
// Bundle the test app)
|
||||
println!("Testing output path collision handling...");
|
||||
|
||||
let mut bundle_cmd = Command::new(&banderole_path);
|
||||
bundle_cmd
|
||||
.args(["bundle", test_app_path.to_str().unwrap()])
|
||||
.args([
|
||||
"bundle",
|
||||
test_app_path.to_str().unwrap(),
|
||||
"--no-compression",
|
||||
])
|
||||
.current_dir(temp_dir.path());
|
||||
|
||||
let bundle_output = run_with_timeout(&mut bundle_cmd, Duration::from_secs(300))?;
|
||||
@@ -438,7 +446,7 @@ async fn test_output_path_collision_handling() -> Result<(), Box<dyn std::error:
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_typescript_project_with_dist() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
@@ -509,12 +517,16 @@ try {
|
||||
);
|
||||
}
|
||||
|
||||
// Bundle the TypeScript project
|
||||
// Bundle the TypeScript project)
|
||||
println!("Testing TypeScript project bundling...");
|
||||
|
||||
let mut bundle_cmd = Command::new(&banderole_path);
|
||||
bundle_cmd
|
||||
.args(["bundle", test_app_path.to_str().unwrap()])
|
||||
.args([
|
||||
"bundle",
|
||||
test_app_path.to_str().unwrap(),
|
||||
"--no-compression",
|
||||
])
|
||||
.current_dir(temp_dir.path());
|
||||
|
||||
let bundle_output = run_with_timeout(&mut bundle_cmd, Duration::from_secs(300))?;
|
||||
@@ -594,7 +606,7 @@ try {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_typescript_project_with_tsconfig_outdir() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
@@ -653,12 +665,16 @@ try {
|
||||
);
|
||||
}
|
||||
|
||||
// Bundle the project
|
||||
// Bundle the project)
|
||||
println!("Testing TypeScript project with custom outDir...");
|
||||
|
||||
let mut bundle_cmd = Command::new(&banderole_path);
|
||||
bundle_cmd
|
||||
.args(["bundle", test_app_path.to_str().unwrap()])
|
||||
.args([
|
||||
"bundle",
|
||||
test_app_path.to_str().unwrap(),
|
||||
"--no-compression",
|
||||
])
|
||||
.current_dir(temp_dir.path());
|
||||
|
||||
let bundle_output = bundle_cmd.output()?;
|
||||
@@ -730,7 +746,7 @@ try {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_typescript_project_with_extends() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
@@ -796,12 +812,16 @@ try {
|
||||
);
|
||||
}
|
||||
|
||||
// Bundle the project
|
||||
// Bundle the project)
|
||||
println!("Testing TypeScript project with extends...");
|
||||
|
||||
let mut bundle_cmd = Command::new(&banderole_path);
|
||||
bundle_cmd
|
||||
.args(["bundle", test_app_path.to_str().unwrap()])
|
||||
.args([
|
||||
"bundle",
|
||||
test_app_path.to_str().unwrap(),
|
||||
"--no-compression",
|
||||
])
|
||||
.current_dir(temp_dir.path());
|
||||
|
||||
let bundle_output = bundle_cmd.output()?;
|
||||
@@ -876,7 +896,7 @@ try {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_pnpm_dependencies_bundling() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let temp_dir = TempDir::new()?;
|
||||
@@ -1005,12 +1025,16 @@ packages:
|
||||
);
|
||||
}
|
||||
|
||||
// Bundle the pnpm project
|
||||
// Bundle the pnpm project)
|
||||
println!("Testing pnpm dependency bundling...");
|
||||
|
||||
let mut bundle_cmd = Command::new(&banderole_path);
|
||||
bundle_cmd
|
||||
.args(["bundle", test_app_path.to_str().unwrap()])
|
||||
.args([
|
||||
"bundle",
|
||||
test_app_path.to_str().unwrap(),
|
||||
"--no-compression",
|
||||
])
|
||||
.current_dir(temp_dir.path());
|
||||
|
||||
let bundle_output = run_with_timeout(&mut bundle_cmd, Duration::from_secs(300))?;
|
||||
@@ -1115,7 +1139,7 @@ packages:
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_bundle_simple_project() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
@@ -1157,7 +1181,7 @@ async fn test_bundle_simple_project() {
|
||||
|
||||
assert!(npm_install.status.success(), "npm install failed");
|
||||
|
||||
// Bundle the project (we'll use the CLI instead)
|
||||
// Bundle the project (we'll use the CLI instead, no compression for speed)
|
||||
let cargo_bin = env!("CARGO_BIN_EXE_banderole");
|
||||
let bundle_output = Command::new(cargo_bin)
|
||||
.arg("bundle")
|
||||
@@ -1166,6 +1190,7 @@ async fn test_bundle_simple_project() {
|
||||
.arg(temp_dir.path().join("test-bundle"))
|
||||
.arg("--name")
|
||||
.arg("test-bundle")
|
||||
.arg("--no-compression")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
@@ -1183,7 +1208,7 @@ async fn test_bundle_simple_project() {
|
||||
// which is complex in a test environment
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_pnpm_project_bundling() {
|
||||
// This test demonstrates that pnpm projects can be bundled
|
||||
@@ -1228,7 +1253,7 @@ packages:
|
||||
|
||||
fs::write(project_path.join("pnpm-lock.yaml"), pnpm_lock).unwrap();
|
||||
|
||||
// The bundling should handle the pnpm structure gracefully
|
||||
// The bundling should handle the pnpm structure gracefully)
|
||||
let cargo_bin = env!("CARGO_BIN_EXE_banderole");
|
||||
let result = Command::new(cargo_bin)
|
||||
.arg("bundle")
|
||||
@@ -1237,6 +1262,7 @@ packages:
|
||||
.arg(temp_dir.path().join("pnpm-bundle"))
|
||||
.arg("--name")
|
||||
.arg("pnpm-bundle")
|
||||
.arg("--no-compression")
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
@@ -1291,3 +1317,17 @@ fn run_with_timeout(cmd: &mut Command, timeout: Duration) -> std::io::Result<std
|
||||
}
|
||||
}
|
||||
}
|
||||
mod common;
|
||||
use common::TestCacheManager;
|
||||
|
||||
/// Cleanup function to be called after all integration tests
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_zzz_cleanup_integration_cache() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Cleaning up application cache after integration tests...");
|
||||
|
||||
TestCacheManager::clear_application_cache()?;
|
||||
|
||||
println!("✅ Integration cache cleanup completed!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
mod common;
|
||||
|
||||
use anyhow::Result;
|
||||
use common::{BundlerTestHelper, TestAssertions, TestProject, TestProjectManager};
|
||||
use common::{
|
||||
BundlerTestHelper, TestAssertions, TestCacheManager, TestProject, TestProjectManager,
|
||||
};
|
||||
use serial_test::serial;
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_npm_workspace_dependency_bundling() -> Result<()> {
|
||||
println!("Testing npm workspace dependency bundling...");
|
||||
@@ -31,11 +33,12 @@ async fn test_npm_workspace_dependency_bundling() -> Result<()> {
|
||||
"commander should be installed in workspace root"
|
||||
);
|
||||
|
||||
// Bundle the workspace project
|
||||
let executable_path = BundlerTestHelper::bundle_project(
|
||||
// Bundle the workspace project)
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("workspace-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Test the bundled executable
|
||||
@@ -57,7 +60,7 @@ async fn test_npm_workspace_dependency_bundling() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_pnpm_workspace_dependency_bundling() -> Result<()> {
|
||||
println!("Testing pnpm workspace dependency bundling...");
|
||||
@@ -81,11 +84,12 @@ async fn test_pnpm_workspace_dependency_bundling() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Bundle the pnpm workspace project
|
||||
let executable_path = BundlerTestHelper::bundle_project(
|
||||
// Bundle the pnpm workspace project)
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("pnpm-workspace-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Test the bundled executable
|
||||
@@ -105,7 +109,7 @@ async fn test_pnpm_workspace_dependency_bundling() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_workspace_with_typescript_project() -> Result<()> {
|
||||
println!("Testing workspace with TypeScript project...");
|
||||
@@ -122,11 +126,12 @@ async fn test_workspace_with_typescript_project() -> Result<()> {
|
||||
// Install dependencies
|
||||
manager.install_workspace_dependencies()?;
|
||||
|
||||
// Bundle the TypeScript workspace project
|
||||
let executable_path = BundlerTestHelper::bundle_project(
|
||||
// Bundle the TypeScript workspace project)
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("workspace-ts-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Test the bundled executable
|
||||
@@ -145,7 +150,7 @@ async fn test_workspace_with_typescript_project() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_workspace_nested_dependencies() -> Result<()> {
|
||||
println!("Testing workspace with nested dependencies...");
|
||||
@@ -190,11 +195,12 @@ process.exit(0);"#;
|
||||
|
||||
std::fs::write(manager.project_path().join("index.js"), complex_index_js)?;
|
||||
|
||||
// Bundle the project
|
||||
let executable_path = BundlerTestHelper::bundle_project(
|
||||
// Bundle the project)
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("nested-deps-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Test the bundled executable
|
||||
@@ -207,7 +213,7 @@ process.exit(0);"#;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_workspace_with_bin_scripts() -> Result<()> {
|
||||
println!("Testing workspace with bin scripts...");
|
||||
@@ -230,11 +236,12 @@ async fn test_workspace_with_bin_scripts() -> Result<()> {
|
||||
".bin directory should exist in workspace node_modules"
|
||||
);
|
||||
|
||||
// Bundle the project
|
||||
let executable_path = BundlerTestHelper::bundle_project(
|
||||
// Bundle the project)
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("bin-scripts-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Test the bundled executable
|
||||
@@ -253,7 +260,7 @@ async fn test_workspace_with_bin_scripts() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_workspace_project_without_own_node_modules() -> Result<()> {
|
||||
println!("Testing workspace project without its own node_modules...");
|
||||
@@ -282,11 +289,12 @@ async fn test_workspace_project_without_own_node_modules() -> Result<()> {
|
||||
"minimist should be in workspace node_modules"
|
||||
);
|
||||
|
||||
// Bundle the project
|
||||
let executable_path = BundlerTestHelper::bundle_project(
|
||||
// Bundle the project)
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("no-local-deps-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Test the bundled executable
|
||||
@@ -305,7 +313,7 @@ async fn test_workspace_project_without_own_node_modules() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_workspace_with_peer_dependencies() -> Result<()> {
|
||||
println!("Testing workspace with peer dependencies...");
|
||||
@@ -343,11 +351,12 @@ async fn test_workspace_with_peer_dependencies() -> Result<()> {
|
||||
// Install dependencies
|
||||
manager.install_workspace_dependencies()?;
|
||||
|
||||
// Bundle the project
|
||||
let executable_path = BundlerTestHelper::bundle_project(
|
||||
// Bundle the project)
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("peer-deps-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Test the bundled executable
|
||||
@@ -366,13 +375,13 @@ async fn test_workspace_with_peer_dependencies() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_deep_workspace_nesting() -> Result<()> {
|
||||
println!("Testing deep workspace nesting...");
|
||||
|
||||
// Create a deeply nested workspace structure
|
||||
let project = TestProject::new("apps/frontend/client")
|
||||
let project = TestProject::new("deep-nested-client")
|
||||
.workspace()
|
||||
.with_dependency("uuid", "^9.0.1")
|
||||
.with_dependency("date-fns", "^2.30.0");
|
||||
@@ -382,11 +391,12 @@ async fn test_deep_workspace_nesting() -> Result<()> {
|
||||
// Install dependencies in workspace root
|
||||
manager.install_workspace_dependencies()?;
|
||||
|
||||
// Bundle the deeply nested project
|
||||
let executable_path = BundlerTestHelper::bundle_project(
|
||||
// Bundle the deeply nested project)
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("deep-nested-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Test the bundled executable
|
||||
@@ -405,7 +415,7 @@ async fn test_deep_workspace_nesting() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_workspace_collision_handling() -> Result<()> {
|
||||
println!("Testing workspace collision handling...");
|
||||
@@ -423,11 +433,12 @@ async fn test_workspace_collision_handling() -> Result<()> {
|
||||
// Install dependencies
|
||||
manager.install_workspace_dependencies()?;
|
||||
|
||||
// Bundle the project (should handle collision automatically)
|
||||
let executable_path = BundlerTestHelper::bundle_project(
|
||||
// Bundle the project (should handle collision automatically, no compression for speed)
|
||||
let executable_path = BundlerTestHelper::bundle_project_with_compression(
|
||||
manager.project_path(),
|
||||
manager.temp_dir(),
|
||||
Some("collision-test"),
|
||||
false,
|
||||
)?;
|
||||
|
||||
// The executable should exist with collision avoidance
|
||||
@@ -451,3 +462,14 @@ async fn test_workspace_collision_handling() -> Result<()> {
|
||||
println!("✅ workspace collision handling test passed!");
|
||||
Ok(())
|
||||
}
|
||||
/// Cleanup function to be called after all workspace tests
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[serial]
|
||||
async fn test_zzz_cleanup_workspace_cache() -> Result<()> {
|
||||
println!("Cleaning up application cache after workspace tests...");
|
||||
|
||||
TestCacheManager::clear_application_cache()?;
|
||||
|
||||
println!("✅ Workspace cache cleanup completed!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user