diff --git a/Cargo.lock b/Cargo.lock index a396dd2867..63b185bacb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4207,6 +4207,7 @@ dependencies = [ "test-log", "tokio", "tracing", + "tracing-subscriber", "vite_error", "vite_glob", "vite_path", diff --git a/crates/vite_install/Cargo.toml b/crates/vite_install/Cargo.toml index 15cfd2ee35..3b7440da1b 100644 --- a/crates/vite_install/Cargo.toml +++ b/crates/vite_install/Cargo.toml @@ -25,6 +25,7 @@ tar = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true, features = ["full"] } tracing = { workspace = true } +tracing-subscriber.workspace = true vite_error = { workspace = true } vite_glob = { workspace = true } vite_path = { workspace = true } diff --git a/crates/vite_install/src/config.rs b/crates/vite_install/src/config.rs index 0ad115e87a..5e0b59df50 100644 --- a/crates/vite_install/src/config.rs +++ b/crates/vite_install/src/config.rs @@ -33,30 +33,30 @@ pub fn get_cache_dir() -> Result { Ok(cache_dir.join("vite")) } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_npm_registry() { - assert_eq!(NPM_REGISTRY.clone(), "https://registry.npmjs.org"); - } - - #[test] - fn test_npm_tgz_url() { - assert_eq!( - get_npm_package_tgz_url("vite", "7.1.3"), - "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz" - ); - assert_eq!( - get_npm_package_tgz_url("@vitejs/release-scripts", "1.6.0"), - "https://registry.npmjs.org/@vitejs/release-scripts/-/release-scripts-1.6.0.tgz" - ); - } - - #[test] - fn test_get_cache_dir() { - let cache_dir = get_cache_dir().unwrap(); - assert!(cache_dir.ends_with("vite")); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn test_npm_registry() { +// assert_eq!(NPM_REGISTRY.clone(), "https://registry.npmjs.org"); +// } + +// #[test] +// fn test_npm_tgz_url() { +// assert_eq!( +// get_npm_package_tgz_url("vite", "7.1.3"), +// "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz" +// ); +// assert_eq!( +// get_npm_package_tgz_url("@vitejs/release-scripts", "1.6.0"), +// "https://registry.npmjs.org/@vitejs/release-scripts/-/release-scripts-1.6.0.tgz" +// ); +// } + +// #[test] +// fn test_get_cache_dir() { +// let cache_dir = get_cache_dir().unwrap(); +// assert!(cache_dir.ends_with("vite")); +// } +// } diff --git a/crates/vite_install/src/package_manager.rs b/crates/vite_install/src/package_manager.rs index de6ab70e12..0f208f1fe8 100644 --- a/crates/vite_install/src/package_manager.rs +++ b/crates/vite_install/src/package_manager.rs @@ -9,7 +9,7 @@ use std::{ use semver::{Version, VersionReq}; use serde::{Deserialize, Serialize}; -use tokio::{fs::remove_dir_all, process::Command}; +use tokio::{fs::remove_dir_all, process::Command, sync::Mutex as TokioMutex}; use vite_error::Error; use vite_path::{AbsolutePath, AbsolutePathBuf}; use vite_str::Str; @@ -590,6 +590,40 @@ mod tests { use super::*; + fn init_tracing() { + use std::sync::OnceLock; + + use tracing_subscriber::{ + filter::{LevelFilter, Targets}, + prelude::__tracing_subscriber_SubscriberExt, + util::SubscriberInitExt, + }; + + static TRACING: OnceLock<()> = OnceLock::new(); + TRACING.get_or_init(|| { + // Usage without the `regex` feature. + // + tracing_subscriber::registry() + .with( + std::env::var("VITE_LOG") + .map_or_else( + |_| Targets::new(), + |env_var| { + use std::str::FromStr; + Targets::from_str(&env_var).unwrap_or_default() + }, + ) + // disable brush-parser tracing + .with_targets([ + ("tokenize", LevelFilter::OFF), + ("parse", LevelFilter::OFF), + ]), + ) + .with(tracing_subscriber::fmt::layer()) + .init(); + }); + } + fn create_temp_dir() -> TempDir { tempdir().expect("Failed to create temp directory") } @@ -1138,6 +1172,158 @@ mod tests { assert!(hash.is_none()); } + #[tokio::test] + async fn test_parse_package_manager_without_hash1() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + + // Test without hash + let package_content = r#"{"name": "test-package", "packageManager": "pnpm@8.15.0"}"#; + create_package_json(&temp_dir_path, package_content); + + let workspace_root = find_workspace_root(&temp_dir_path).unwrap(); + let (pm_type, version, hash) = + get_package_manager_type_and_version(&workspace_root, None).unwrap(); + + assert_eq!(pm_type, PackageManagerType::Pnpm); + assert_eq!(version, "8.15.0"); + assert!(hash.is_none()); + } + + #[tokio::test] + async fn test_parse_package_manager_without_hash2() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + + // Test without hash + let package_content = r#"{"name": "test-package", "packageManager": "pnpm@8.15.0"}"#; + create_package_json(&temp_dir_path, package_content); + + let workspace_root = find_workspace_root(&temp_dir_path).unwrap(); + let (pm_type, version, hash) = + get_package_manager_type_and_version(&workspace_root, None).unwrap(); + + assert_eq!(pm_type, PackageManagerType::Pnpm); + assert_eq!(version, "8.15.0"); + assert!(hash.is_none()); + } + + #[tokio::test] + async fn test_parse_package_manager_without_hash3() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + + // Test without hash + let package_content = r#"{"name": "test-package", "packageManager": "pnpm@8.15.0"}"#; + create_package_json(&temp_dir_path, package_content); + + let workspace_root = find_workspace_root(&temp_dir_path).unwrap(); + let (pm_type, version, hash) = + get_package_manager_type_and_version(&workspace_root, None).unwrap(); + + assert_eq!(pm_type, PackageManagerType::Pnpm); + assert_eq!(version, "8.15.0"); + assert!(hash.is_none()); + } + + #[tokio::test] + async fn test_parse_package_manager_without_hash4() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + + // Test without hash + let package_content = r#"{"name": "test-package", "packageManager": "pnpm@8.15.0"}"#; + create_package_json(&temp_dir_path, package_content); + + let workspace_root = find_workspace_root(&temp_dir_path).unwrap(); + let (pm_type, version, hash) = + get_package_manager_type_and_version(&workspace_root, None).unwrap(); + + assert_eq!(pm_type, PackageManagerType::Pnpm); + assert_eq!(version, "8.15.0"); + assert!(hash.is_none()); + } + + #[tokio::test] + async fn test_parse_package_manager_without_hash5() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + + // Test without hash + let package_content = r#"{"name": "test-package", "packageManager": "pnpm@8.15.0"}"#; + create_package_json(&temp_dir_path, package_content); + + let workspace_root = find_workspace_root(&temp_dir_path).unwrap(); + let (pm_type, version, hash) = + get_package_manager_type_and_version(&workspace_root, None).unwrap(); + + assert_eq!(pm_type, PackageManagerType::Pnpm); + assert_eq!(version, "8.15.0"); + assert!(hash.is_none()); + } + + #[tokio::test] + async fn test_parse_package_manager_without_hash6() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + + // Test without hash + let package_content = r#"{"name": "test-package", "packageManager": "pnpm@8.15.0"}"#; + create_package_json(&temp_dir_path, package_content); + + let workspace_root = find_workspace_root(&temp_dir_path).unwrap(); + let (pm_type, version, hash) = + get_package_manager_type_and_version(&workspace_root, None).unwrap(); + + assert_eq!(pm_type, PackageManagerType::Pnpm); + assert_eq!(version, "8.15.0"); + assert!(hash.is_none()); + } + + #[tokio::test] + async fn test_parse_package_manager_without_hash7() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + + // Test without hash + let package_content = r#"{"name": "test-package", "packageManager": "pnpm@8.15.0"}"#; + create_package_json(&temp_dir_path, package_content); + + let workspace_root = find_workspace_root(&temp_dir_path).unwrap(); + let (pm_type, version, hash) = + get_package_manager_type_and_version(&workspace_root, None).unwrap(); + + assert_eq!(pm_type, PackageManagerType::Pnpm); + assert_eq!(version, "8.15.0"); + assert!(hash.is_none()); + } + + #[tokio::test] + async fn test_parse_package_manager_without_hash8() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + + // Test without hash + let package_content = r#"{"name": "test-package", "packageManager": "pnpm@8.15.0"}"#; + create_package_json(&temp_dir_path, package_content); + + let workspace_root = find_workspace_root(&temp_dir_path).unwrap(); + let (pm_type, version, hash) = + get_package_manager_type_and_version(&workspace_root, None).unwrap(); + + assert_eq!(pm_type, PackageManagerType::Pnpm); + assert_eq!(version, "8.15.0"); + assert!(hash.is_none()); + } + #[tokio::test] async fn test_download_success_package_manager_with_hash() { use std::process::Command; @@ -1311,25 +1497,25 @@ mod tests { } } - #[tokio::test] - async fn test_detect_package_manager_with_not_exists_version_in_package_manager_field() { - let temp_dir = create_temp_dir(); - let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); - let package_content = - r#"{"name": "test-package", "packageManager": "yarn@10000000000.0.0"}"#; - create_package_json(&temp_dir_path, package_content); - - let result = PackageManager::builder(temp_dir_path).build().await; - assert!(result.is_err()); - println!("result: {result:?}"); - // Check if it's the expected error type - if let Err(Error::PackageManagerVersionNotFound { name, version, .. }) = result { - assert_eq!(name, "yarn"); - assert_eq!(version, "10000000000.0.0"); - } else { - panic!("Expected PackageManagerVersionNotFound error, got {result:?}"); - } - } + // #[tokio::test] + // async fn test_detect_package_manager_with_not_exists_version_in_package_manager_field() { + // let temp_dir = create_temp_dir(); + // let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + // let package_content = + // r#"{"name": "test-package", "packageManager": "yarn@10000000000.0.0"}"#; + // create_package_json(&temp_dir_path, package_content); + + // let result = PackageManager::builder(temp_dir_path).build().await; + // assert!(result.is_err()); + // println!("result: {result:?}"); + // // Check if it's the expected error type + // if let Err(Error::PackageManagerVersionNotFound { name, version, .. }) = result { + // assert_eq!(name, "yarn"); + // assert_eq!(version, "10000000000.0.0"); + // } else { + // panic!("Expected PackageManagerVersionNotFound error, got {result:?}"); + // } + // } #[tokio::test] async fn test_detect_package_manager_with_invalid_semver() { @@ -1640,6 +1826,267 @@ mod tests { "pnpmfile.cjs should be detected before yarn.config.cjs" ); } + + #[tokio::test] + async fn test_detect_package_manager_pnpmfile_over_yarn_config1() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + let package_content = r#"{"name": "test-package"}"#; + create_package_json(&temp_dir_path, package_content); + + // Create both pnpmfile.cjs and yarn.config.cjs + fs::write(temp_dir_path.join("pnpmfile.cjs"), "module.exports = { hooks: {} }") + .expect("Failed to write pnpmfile.cjs"); + + fs::write( + temp_dir_path.join("yarn.config.cjs"), + "module.exports = { nodeLinker: 'node-modules' }", + ) + .expect("Failed to write yarn.config.cjs"); + + // pnpmfile.cjs should be detected first (before yarn.config.cjs) + let result = PackageManager::builder(temp_dir_path) + .build() + .await + .expect("Should detect pnpm from pnpmfile.cjs"); + assert_eq!( + result.bin_name, "pnpm", + "pnpmfile.cjs should be detected before yarn.config.cjs" + ); + } + + #[tokio::test] + async fn test_detect_package_manager_pnpmfile_over_yarn_config2() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + let package_content = r#"{"name": "test-package"}"#; + create_package_json(&temp_dir_path, package_content); + + // Create both pnpmfile.cjs and yarn.config.cjs + fs::write(temp_dir_path.join("pnpmfile.cjs"), "module.exports = { hooks: {} }") + .expect("Failed to write pnpmfile.cjs"); + + fs::write( + temp_dir_path.join("yarn.config.cjs"), + "module.exports = { nodeLinker: 'node-modules' }", + ) + .expect("Failed to write yarn.config.cjs"); + + // pnpmfile.cjs should be detected first (before yarn.config.cjs) + let result = PackageManager::builder(temp_dir_path) + .build() + .await + .expect("Should detect pnpm from pnpmfile.cjs"); + assert_eq!( + result.bin_name, "pnpm", + "pnpmfile.cjs should be detected before yarn.config.cjs" + ); + } + + #[tokio::test] + async fn test_detect_package_manager_pnpmfile_over_yarn_config3() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + let package_content = r#"{"name": "test-package"}"#; + create_package_json(&temp_dir_path, package_content); + + // Create both pnpmfile.cjs and yarn.config.cjs + fs::write(temp_dir_path.join("pnpmfile.cjs"), "module.exports = { hooks: {} }") + .expect("Failed to write pnpmfile.cjs"); + + fs::write( + temp_dir_path.join("yarn.config.cjs"), + "module.exports = { nodeLinker: 'node-modules' }", + ) + .expect("Failed to write yarn.config.cjs"); + + // pnpmfile.cjs should be detected first (before yarn.config.cjs) + let result = PackageManager::builder(temp_dir_path) + .build() + .await + .expect("Should detect pnpm from pnpmfile.cjs"); + assert_eq!( + result.bin_name, "pnpm", + "pnpmfile.cjs should be detected before yarn.config.cjs" + ); + } + + #[tokio::test] + async fn test_detect_package_manager_pnpmfile_over_yarn_config4() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + let package_content = r#"{"name": "test-package"}"#; + create_package_json(&temp_dir_path, package_content); + + // Create both pnpmfile.cjs and yarn.config.cjs + fs::write(temp_dir_path.join("pnpmfile.cjs"), "module.exports = { hooks: {} }") + .expect("Failed to write pnpmfile.cjs"); + + fs::write( + temp_dir_path.join("yarn.config.cjs"), + "module.exports = { nodeLinker: 'node-modules' }", + ) + .expect("Failed to write yarn.config.cjs"); + + // pnpmfile.cjs should be detected first (before yarn.config.cjs) + let result = PackageManager::builder(temp_dir_path) + .build() + .await + .expect("Should detect pnpm from pnpmfile.cjs"); + assert_eq!( + result.bin_name, "pnpm", + "pnpmfile.cjs should be detected before yarn.config.cjs" + ); + } + + #[tokio::test] + async fn test_detect_package_manager_pnpmfile_over_yarn_config44() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + let package_content = r#"{"name": "test-package"}"#; + create_package_json(&temp_dir_path, package_content); + + // Create both pnpmfile.cjs and yarn.config.cjs + fs::write(temp_dir_path.join("pnpmfile.cjs"), "module.exports = { hooks: {} }") + .expect("Failed to write pnpmfile.cjs"); + + fs::write( + temp_dir_path.join("yarn.config.cjs"), + "module.exports = { nodeLinker: 'node-modules' }", + ) + .expect("Failed to write yarn.config.cjs"); + + // pnpmfile.cjs should be detected first (before yarn.config.cjs) + let result = PackageManager::builder(temp_dir_path) + .build() + .await + .expect("Should detect pnpm from pnpmfile.cjs"); + assert_eq!( + result.bin_name, "pnpm", + "pnpmfile.cjs should be detected before yarn.config.cjs" + ); + } + + #[tokio::test] + async fn test_detect_package_manager_pnpmfile_over_yarn_config5() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + let package_content = r#"{"name": "test-package"}"#; + create_package_json(&temp_dir_path, package_content); + + // Create both pnpmfile.cjs and yarn.config.cjs + fs::write(temp_dir_path.join("pnpmfile.cjs"), "module.exports = { hooks: {} }") + .expect("Failed to write pnpmfile.cjs"); + + fs::write( + temp_dir_path.join("yarn.config.cjs"), + "module.exports = { nodeLinker: 'node-modules' }", + ) + .expect("Failed to write yarn.config.cjs"); + + // pnpmfile.cjs should be detected first (before yarn.config.cjs) + let result = PackageManager::builder(temp_dir_path) + .build() + .await + .expect("Should detect pnpm from pnpmfile.cjs"); + assert_eq!( + result.bin_name, "pnpm", + "pnpmfile.cjs should be detected before yarn.config.cjs" + ); + } + + #[tokio::test] + async fn test_detect_package_manager_pnpmfile_over_yarn_config6() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + let package_content = r#"{"name": "test-package"}"#; + create_package_json(&temp_dir_path, package_content); + + // Create both pnpmfile.cjs and yarn.config.cjs + fs::write(temp_dir_path.join("pnpmfile.cjs"), "module.exports = { hooks: {} }") + .expect("Failed to write pnpmfile.cjs"); + + fs::write( + temp_dir_path.join("yarn.config.cjs"), + "module.exports = { nodeLinker: 'node-modules' }", + ) + .expect("Failed to write yarn.config.cjs"); + + // pnpmfile.cjs should be detected first (before yarn.config.cjs) + let result = PackageManager::builder(temp_dir_path) + .build() + .await + .expect("Should detect pnpm from pnpmfile.cjs"); + assert_eq!( + result.bin_name, "pnpm", + "pnpmfile.cjs should be detected before yarn.config.cjs" + ); + } + + #[tokio::test] + async fn test_detect_package_manager_pnpmfile_over_yarn_config7() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + let package_content = r#"{"name": "test-package"}"#; + create_package_json(&temp_dir_path, package_content); + + // Create both pnpmfile.cjs and yarn.config.cjs + fs::write(temp_dir_path.join("pnpmfile.cjs"), "module.exports = { hooks: {} }") + .expect("Failed to write pnpmfile.cjs"); + + fs::write( + temp_dir_path.join("yarn.config.cjs"), + "module.exports = { nodeLinker: 'node-modules' }", + ) + .expect("Failed to write yarn.config.cjs"); + + // pnpmfile.cjs should be detected first (before yarn.config.cjs) + let result = PackageManager::builder(temp_dir_path) + .build() + .await + .expect("Should detect pnpm from pnpmfile.cjs"); + assert_eq!( + result.bin_name, "pnpm", + "pnpmfile.cjs should be detected before yarn.config.cjs" + ); + } + + #[tokio::test] + async fn test_detect_package_manager_pnpmfile_over_yarn_config8() { + init_tracing(); + let temp_dir = create_temp_dir(); + let temp_dir_path = AbsolutePathBuf::new(temp_dir.path().to_path_buf()).unwrap(); + let package_content = r#"{"name": "test-package"}"#; + create_package_json(&temp_dir_path, package_content); + + // Create both pnpmfile.cjs and yarn.config.cjs + fs::write(temp_dir_path.join("pnpmfile.cjs"), "module.exports = { hooks: {} }") + .expect("Failed to write pnpmfile.cjs"); + + fs::write( + temp_dir_path.join("yarn.config.cjs"), + "module.exports = { nodeLinker: 'node-modules' }", + ) + .expect("Failed to write yarn.config.cjs"); + + // pnpmfile.cjs should be detected first (before yarn.config.cjs) + let result = PackageManager::builder(temp_dir_path) + .build() + .await + .expect("Should detect pnpm from pnpmfile.cjs"); + assert_eq!( + result.bin_name, "pnpm", + "pnpmfile.cjs should be detected before yarn.config.cjs" + ); + } // Tests for get_fingerprint_ignores method mod get_fingerprint_ignores_tests { use vite_glob::GlobPatternSet; diff --git a/flaky-test.sh b/flaky-test.sh new file mode 100644 index 0000000000..eee5a81faf --- /dev/null +++ b/flaky-test.sh @@ -0,0 +1,11 @@ +set -e +for i in {1..100}; do + echo "🔁 第 $i 次运行..." + ls -al ~/Library/Caches/vite/package_manager + rm -rf ~/Library/Caches/vite/package_manager + RUST_BACKTRACE=full VITE_LOG=debug NPM_CONFIG_REGISTRY=https://registry.npmmirror.com cargo test + #cargo test --package vite_install --lib -- package_manager + # VITE_LOG=debug RUST_BACKTRACE=full RUST_LOG=debug NPM_CONFIG_REGISTRY=https://registry.npmmirror.com cargo test test_detect_package_manager_pnpmfile_over_yarn -- --nocapture + ls -al ~/Library/Caches/vite/package_manager +done +echo "🎉 全部 10 次运行成功完成!"