diff --git a/.config/nextest.toml b/.config/nextest.toml index ba9dab796c36b..179ced7f2c9d8 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -10,6 +10,10 @@ turborepo-process-serial = { max-threads = 1 } # race conditions with binary detection and stdout/stderr capture turborepo-integration-serial = { max-threads = 1 } +# turborepo-dirs tests should run serially to avoid race conditions +# with environment variable manipulation +turborepo-dirs-serial = { max-threads = 1 } + [[profile.default.overrides]] # Run all tests in the turborepo-process crate serially filter = 'package(turborepo-process)' @@ -20,3 +24,9 @@ test-group = 'turborepo-process-serial' # These tests spawn turbo processes and parse JSON from stdout filter = 'package(turbo) and (test(boundaries) or test(query) or test(ls))' test-group = 'turborepo-integration-serial' + +[[profile.default.overrides]] +# Run all tests in the turborepo-dirs crate serially +# These tests manipulate environment variables +filter = 'package(turborepo-dirs)' +test-group = 'turborepo-dirs-serial' diff --git a/.github/actions/setup-capnproto/action.yml b/.github/actions/setup-capnproto/action.yml index 32964e2c2a1bd..458fe1db3af7c 100644 --- a/.github/actions/setup-capnproto/action.yml +++ b/.github/actions/setup-capnproto/action.yml @@ -14,16 +14,6 @@ runs: /usr/bin/capnpc key: ${{ runner.os }}-capnproto-0.11.2 - - name: "Cache capnproto (Windows)" - if: runner.os == 'Windows' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) - uses: actions/cache@v4 - id: cache-capnp-windows - with: - path: | - C:\ProgramData\chocolatey\bin\capnp.exe - C:\ProgramData\chocolatey\bin\capnpc.exe - key: ${{ runner.os }}-capnproto-0.11.2 - - name: "Setup capnproto for Linux" if: runner.os == 'Linux' && (steps.cache-capnp-linux.outcome == 'skipped' || steps.cache-capnp-linux.outputs.cache-hit != 'true') shell: bash @@ -35,6 +25,6 @@ runs: run: brew install capnp - name: "Setup capnproto for Windows" - if: runner.os == 'Windows' && (steps.cache-capnp-windows.outcome == 'skipped' || steps.cache-capnp-windows.outputs.cache-hit != 'true') + if: runner.os == 'Windows' shell: bash run: choco install capnproto diff --git a/crates/turborepo-dirs/src/lib.rs b/crates/turborepo-dirs/src/lib.rs index c8c28b9cfa02b..678adcffb9705 100644 --- a/crates/turborepo-dirs/src/lib.rs +++ b/crates/turborepo-dirs/src/lib.rs @@ -41,3 +41,238 @@ pub enum Error { #[error("Config directory not found.")] ConfigDirNotFound, } + +#[cfg(test)] +mod tests { + use std::env; + + use super::*; + + #[test] + fn test_config_dir_with_env_var() { + // Set TURBO_CONFIG_DIR_PATH to an absolute path + let test_path = if cfg!(windows) { + "C:\\test\\config" + } else { + "/test/config" + }; + + unsafe { + env::set_var("TURBO_CONFIG_DIR_PATH", test_path); + } + + let result = config_dir(); + assert!(result.is_ok()); + let path = result.unwrap(); + assert!(path.is_some()); + assert_eq!(path.unwrap().as_str(), test_path); + + unsafe { + env::remove_var("TURBO_CONFIG_DIR_PATH"); + } + } + + #[test] + fn test_config_dir_with_invalid_env_var() { + // Set TURBO_CONFIG_DIR_PATH to a relative path (invalid) + unsafe { + env::set_var("TURBO_CONFIG_DIR_PATH", "relative/path"); + } + + let result = config_dir(); + assert!(result.is_err()); + + unsafe { + env::remove_var("TURBO_CONFIG_DIR_PATH"); + } + } + + #[test] + fn test_config_dir_without_env_var() { + // Ensure TURBO_CONFIG_DIR_PATH is not set + unsafe { + env::remove_var("TURBO_CONFIG_DIR_PATH"); + } + + let result = config_dir(); + assert!(result.is_ok()); + // On most systems, config_dir should return Some path + // We can't assert the exact path since it's platform-specific + let path = result.unwrap(); + if let Some(p) = path { + // Verify it's an absolute path + assert!(p.as_path().is_absolute()); + } + } + + #[test] + fn test_vercel_config_dir_with_env_var() { + // Set VERCEL_CONFIG_DIR_PATH to an absolute path + let test_path = if cfg!(windows) { + "C:\\vercel\\config" + } else { + "/vercel/config" + }; + + unsafe { + env::set_var("VERCEL_CONFIG_DIR_PATH", test_path); + } + + let result = vercel_config_dir(); + assert!(result.is_ok()); + let path = result.unwrap(); + assert!(path.is_some()); + assert_eq!(path.unwrap().as_str(), test_path); + + unsafe { + env::remove_var("VERCEL_CONFIG_DIR_PATH"); + } + } + + #[test] + fn test_vercel_config_dir_with_invalid_env_var() { + // Set VERCEL_CONFIG_DIR_PATH to a relative path (invalid) + unsafe { + env::set_var("VERCEL_CONFIG_DIR_PATH", "relative/path"); + } + + let result = vercel_config_dir(); + assert!(result.is_err()); + + unsafe { + env::remove_var("VERCEL_CONFIG_DIR_PATH"); + } + } + + #[test] + fn test_vercel_config_dir_without_env_var() { + // Ensure VERCEL_CONFIG_DIR_PATH is not set + unsafe { + env::remove_var("VERCEL_CONFIG_DIR_PATH"); + } + + let result = vercel_config_dir(); + assert!(result.is_ok()); + // On most systems, config_dir should return Some path + // We can't assert the exact path since it's platform-specific + let path = result.unwrap(); + if let Some(p) = path { + // Verify it's an absolute path + assert!(p.as_path().is_absolute()); + } + } + + #[test] + fn test_config_dir_empty_env_var() { + // Set TURBO_CONFIG_DIR_PATH to empty string + unsafe { + env::set_var("TURBO_CONFIG_DIR_PATH", ""); + } + + let result = config_dir(); + // Empty string should be invalid as it's not an absolute path + assert!(result.is_err()); + + unsafe { + env::remove_var("TURBO_CONFIG_DIR_PATH"); + } + } + + #[test] + fn test_vercel_config_dir_empty_env_var() { + // Set VERCEL_CONFIG_DIR_PATH to empty string + unsafe { + env::set_var("VERCEL_CONFIG_DIR_PATH", ""); + } + + let result = vercel_config_dir(); + // Empty string should be invalid as it's not an absolute path + assert!(result.is_err()); + + unsafe { + env::remove_var("VERCEL_CONFIG_DIR_PATH"); + } + } + + #[test] + fn test_config_dir_and_vercel_config_dir_independence() { + // Test that TURBO_CONFIG_DIR_PATH doesn't affect vercel_config_dir + // Use a path that would be created by dirs_config_dir to ensure both succeed + let turbo_path = if cfg!(windows) { + "C:\\Users\\test\\config" + } else { + "/Users/test/config" + }; + + unsafe { + env::set_var("TURBO_CONFIG_DIR_PATH", turbo_path); + env::remove_var("VERCEL_CONFIG_DIR_PATH"); + } + + let turbo_result = config_dir(); + let vercel_result = vercel_config_dir(); + + assert!(turbo_result.is_ok(), "turbo_result should be ok"); + let turbo_path_result = turbo_result.unwrap(); + assert!(turbo_path_result.is_some(), "turbo path should be some"); + assert_eq!(turbo_path_result.unwrap().as_str(), turbo_path); + + assert!(vercel_result.is_ok(), "vercel_result should be ok"); + // vercel_config_dir should return the default, not turbo_path + if let Some(vercel_path) = vercel_result.unwrap() { + assert_ne!(vercel_path.as_str(), turbo_path); + } + + unsafe { + env::remove_var("TURBO_CONFIG_DIR_PATH"); + } + } + + #[test] + fn test_vercel_config_dir_and_config_dir_independence() { + // Test that VERCEL_CONFIG_DIR_PATH doesn't affect config_dir + let vercel_path = if cfg!(windows) { + "C:\\Users\\test\\vercel" + } else { + "/Users/test/vercel" + }; + + unsafe { + env::set_var("VERCEL_CONFIG_DIR_PATH", vercel_path); + env::remove_var("TURBO_CONFIG_DIR_PATH"); + } + + let vercel_result = vercel_config_dir(); + let turbo_result = config_dir(); + + assert!(vercel_result.is_ok(), "vercel_result should be ok"); + let vercel_path_result = vercel_result.unwrap(); + assert!(vercel_path_result.is_some(), "vercel path should be some"); + assert_eq!(vercel_path_result.unwrap().as_str(), vercel_path); + + assert!(turbo_result.is_ok(), "turbo_result should be ok"); + // config_dir should return the default, not vercel_path + if let Some(turbo_path) = turbo_result.unwrap() { + assert_ne!(turbo_path.as_str(), vercel_path); + } + + unsafe { + env::remove_var("VERCEL_CONFIG_DIR_PATH"); + } + } + + #[test] + fn test_error_display() { + // Test that the Error enum formats correctly + let error = Error::ConfigDirNotFound; + assert_eq!(error.to_string(), "Config directory not found."); + } + + #[test] + fn test_error_debug() { + // Test that the Error enum can be debug formatted + let error = Error::ConfigDirNotFound; + let debug_str = format!("{:?}", error); + assert!(debug_str.contains("ConfigDirNotFound")); + } +}