Skip to content

Commit d597385

Browse files
authored
Merge branch 'main' into bkonkle/otel-exporter
2 parents 6562048 + 445df7e commit d597385

File tree

3 files changed

+272
-12
lines changed

3 files changed

+272
-12
lines changed

.config/nextest.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ turborepo-process-serial = { max-threads = 1 }
1010
# race conditions with binary detection and stdout/stderr capture
1111
turborepo-integration-serial = { max-threads = 1 }
1212

13+
# turborepo-dirs tests should run serially to avoid race conditions
14+
# with environment variable manipulation
15+
turborepo-dirs-serial = { max-threads = 1 }
16+
1317
[[profile.default.overrides]]
1418
# Run all tests in the turborepo-process crate serially
1519
filter = 'package(turborepo-process)'
@@ -20,3 +24,9 @@ test-group = 'turborepo-process-serial'
2024
# These tests spawn turbo processes and parse JSON from stdout
2125
filter = 'package(turbo) and (test(boundaries) or test(query) or test(ls))'
2226
test-group = 'turborepo-integration-serial'
27+
28+
[[profile.default.overrides]]
29+
# Run all tests in the turborepo-dirs crate serially
30+
# These tests manipulate environment variables
31+
filter = 'package(turborepo-dirs)'
32+
test-group = 'turborepo-dirs-serial'

.github/actions/setup-capnproto/action.yml

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,6 @@ runs:
1414
/usr/bin/capnpc
1515
key: ${{ runner.os }}-capnproto-0.11.2
1616

17-
- name: "Cache capnproto (Windows)"
18-
if: runner.os == 'Windows' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
19-
uses: actions/cache@v4
20-
id: cache-capnp-windows
21-
with:
22-
path: |
23-
C:\ProgramData\chocolatey\bin\capnp.exe
24-
C:\ProgramData\chocolatey\bin\capnpc.exe
25-
key: ${{ runner.os }}-capnproto-0.11.2
26-
2717
- name: "Setup capnproto for Linux"
2818
if: runner.os == 'Linux' && (steps.cache-capnp-linux.outcome == 'skipped' || steps.cache-capnp-linux.outputs.cache-hit != 'true')
2919
shell: bash
@@ -35,6 +25,6 @@ runs:
3525
run: brew install capnp
3626

3727
- name: "Setup capnproto for Windows"
38-
if: runner.os == 'Windows' && (steps.cache-capnp-windows.outcome == 'skipped' || steps.cache-capnp-windows.outputs.cache-hit != 'true')
28+
if: runner.os == 'Windows'
3929
shell: bash
4030
run: choco install capnproto

crates/turborepo-dirs/src/lib.rs

Lines changed: 261 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,23 @@ use turbopath::{AbsoluteSystemPathBuf, PathError};
1313
/// is set, it will return that path instead of `dirs_next::config_dir`.
1414
pub fn config_dir() -> Result<Option<AbsoluteSystemPathBuf>, PathError> {
1515
if let Ok(dir) = std::env::var("TURBO_CONFIG_DIR_PATH") {
16-
return AbsoluteSystemPathBuf::new(dir).map(Some);
16+
// Reject empty strings per Unix convention
17+
if dir.is_empty() {
18+
return Err(PathError::InvalidUnicode(dir));
19+
}
20+
21+
let raw = std::path::PathBuf::from(&dir);
22+
23+
// Resolve to absolute path if necessary
24+
let abs = if raw.is_absolute() {
25+
raw
26+
} else {
27+
std::env::current_dir()?.join(raw)
28+
};
29+
30+
let abs_str = abs.to_str().ok_or(PathError::InvalidUnicode(dir))?;
31+
32+
return AbsoluteSystemPathBuf::new(abs_str).map(Some);
1733
}
1834

1935
dirs_config_dir()
@@ -28,6 +44,11 @@ pub fn config_dir() -> Result<Option<AbsoluteSystemPathBuf>, PathError> {
2844
/// is set, it will return that path instead of `dirs_next::config_dir`.
2945
pub fn vercel_config_dir() -> Result<Option<AbsoluteSystemPathBuf>, PathError> {
3046
if let Ok(dir) = std::env::var("VERCEL_CONFIG_DIR_PATH") {
47+
// Reject empty strings per Unix convention.
48+
if dir.is_empty() {
49+
return Err(PathError::InvalidUnicode(dir));
50+
}
51+
3152
return AbsoluteSystemPathBuf::new(dir).map(Some);
3253
}
3354

@@ -41,3 +62,242 @@ pub enum Error {
4162
#[error("Config directory not found.")]
4263
ConfigDirNotFound,
4364
}
65+
66+
#[cfg(test)]
67+
mod tests {
68+
use std::env;
69+
70+
use super::*;
71+
72+
#[test]
73+
fn test_config_dir_with_env_var() {
74+
// Set TURBO_CONFIG_DIR_PATH to an absolute path
75+
let test_path = if cfg!(windows) {
76+
"C:\\test\\config"
77+
} else {
78+
"/test/config"
79+
};
80+
81+
unsafe {
82+
env::set_var("TURBO_CONFIG_DIR_PATH", test_path);
83+
}
84+
85+
let result = config_dir();
86+
assert!(result.is_ok());
87+
let path = result.unwrap();
88+
assert!(path.is_some());
89+
assert_eq!(path.unwrap().as_str(), test_path);
90+
91+
unsafe {
92+
env::remove_var("TURBO_CONFIG_DIR_PATH");
93+
}
94+
}
95+
96+
#[test]
97+
fn test_config_dir_with_relative_path() {
98+
// Set TURBO_CONFIG_DIR_PATH to a relative path (should be resolved to absolute)
99+
unsafe {
100+
env::set_var("TURBO_CONFIG_DIR_PATH", "relative/path");
101+
}
102+
103+
let result = config_dir();
104+
assert!(result.is_ok());
105+
let path = result.unwrap();
106+
assert!(path.is_some());
107+
// Verify it was resolved to an absolute path
108+
assert!(path.unwrap().as_path().is_absolute());
109+
110+
unsafe {
111+
env::remove_var("TURBO_CONFIG_DIR_PATH");
112+
}
113+
}
114+
115+
#[test]
116+
fn test_config_dir_without_env_var() {
117+
// Ensure TURBO_CONFIG_DIR_PATH is not set
118+
unsafe {
119+
env::remove_var("TURBO_CONFIG_DIR_PATH");
120+
}
121+
122+
let result = config_dir();
123+
assert!(result.is_ok());
124+
// On most systems, config_dir should return Some path
125+
// We can't assert the exact path since it's platform-specific
126+
let path = result.unwrap();
127+
if let Some(p) = path {
128+
// Verify it's an absolute path
129+
assert!(p.as_path().is_absolute());
130+
}
131+
}
132+
133+
#[test]
134+
fn test_vercel_config_dir_with_env_var() {
135+
// Set VERCEL_CONFIG_DIR_PATH to an absolute path
136+
let test_path = if cfg!(windows) {
137+
"C:\\vercel\\config"
138+
} else {
139+
"/vercel/config"
140+
};
141+
142+
unsafe {
143+
env::set_var("VERCEL_CONFIG_DIR_PATH", test_path);
144+
}
145+
146+
let result = vercel_config_dir();
147+
assert!(result.is_ok());
148+
let path = result.unwrap();
149+
assert!(path.is_some());
150+
assert_eq!(path.unwrap().as_str(), test_path);
151+
152+
unsafe {
153+
env::remove_var("VERCEL_CONFIG_DIR_PATH");
154+
}
155+
}
156+
157+
#[test]
158+
fn test_vercel_config_dir_with_invalid_env_var() {
159+
// Set VERCEL_CONFIG_DIR_PATH to a relative path (invalid)
160+
unsafe {
161+
env::set_var("VERCEL_CONFIG_DIR_PATH", "relative/path");
162+
}
163+
164+
let result = vercel_config_dir();
165+
assert!(result.is_err());
166+
167+
unsafe {
168+
env::remove_var("VERCEL_CONFIG_DIR_PATH");
169+
}
170+
}
171+
172+
#[test]
173+
fn test_vercel_config_dir_without_env_var() {
174+
// Ensure VERCEL_CONFIG_DIR_PATH is not set
175+
unsafe {
176+
env::remove_var("VERCEL_CONFIG_DIR_PATH");
177+
}
178+
179+
let result = vercel_config_dir();
180+
assert!(result.is_ok());
181+
// On most systems, config_dir should return Some path
182+
// We can't assert the exact path since it's platform-specific
183+
let path = result.unwrap();
184+
if let Some(p) = path {
185+
// Verify it's an absolute path
186+
assert!(p.as_path().is_absolute());
187+
}
188+
}
189+
190+
#[test]
191+
fn test_config_dir_empty_env_var() {
192+
// Set TURBO_CONFIG_DIR_PATH to empty string
193+
unsafe {
194+
env::set_var("TURBO_CONFIG_DIR_PATH", "");
195+
}
196+
197+
let result = config_dir();
198+
// Empty string should be invalid as it's not an absolute path
199+
assert!(result.is_err());
200+
201+
unsafe {
202+
env::remove_var("TURBO_CONFIG_DIR_PATH");
203+
}
204+
}
205+
206+
#[test]
207+
fn test_vercel_config_dir_empty_env_var() {
208+
// Set VERCEL_CONFIG_DIR_PATH to empty string
209+
unsafe {
210+
env::set_var("VERCEL_CONFIG_DIR_PATH", "");
211+
}
212+
213+
let result = vercel_config_dir();
214+
// Empty string should be invalid as it's not an absolute path
215+
assert!(result.is_err());
216+
217+
unsafe {
218+
env::remove_var("VERCEL_CONFIG_DIR_PATH");
219+
}
220+
}
221+
222+
#[test]
223+
fn test_config_dir_and_vercel_config_dir_independence() {
224+
// Test that TURBO_CONFIG_DIR_PATH doesn't affect vercel_config_dir
225+
// Use a path that would be created by dirs_config_dir to ensure both succeed
226+
let turbo_path = if cfg!(windows) {
227+
"C:\\Users\\test\\config"
228+
} else {
229+
"/Users/test/config"
230+
};
231+
232+
unsafe {
233+
env::set_var("TURBO_CONFIG_DIR_PATH", turbo_path);
234+
env::remove_var("VERCEL_CONFIG_DIR_PATH");
235+
}
236+
237+
let turbo_result = config_dir();
238+
let vercel_result = vercel_config_dir();
239+
240+
assert!(turbo_result.is_ok(), "turbo_result should be ok");
241+
let turbo_path_result = turbo_result.unwrap();
242+
assert!(turbo_path_result.is_some(), "turbo path should be some");
243+
assert_eq!(turbo_path_result.unwrap().as_str(), turbo_path);
244+
245+
assert!(vercel_result.is_ok(), "vercel_result should be ok");
246+
// vercel_config_dir should return the default, not turbo_path
247+
if let Some(vercel_path) = vercel_result.unwrap() {
248+
assert_ne!(vercel_path.as_str(), turbo_path);
249+
}
250+
251+
unsafe {
252+
env::remove_var("TURBO_CONFIG_DIR_PATH");
253+
}
254+
}
255+
256+
#[test]
257+
fn test_vercel_config_dir_and_config_dir_independence() {
258+
// Test that VERCEL_CONFIG_DIR_PATH doesn't affect config_dir
259+
let vercel_path = if cfg!(windows) {
260+
"C:\\Users\\test\\vercel"
261+
} else {
262+
"/Users/test/vercel"
263+
};
264+
265+
unsafe {
266+
env::set_var("VERCEL_CONFIG_DIR_PATH", vercel_path);
267+
env::remove_var("TURBO_CONFIG_DIR_PATH");
268+
}
269+
270+
let vercel_result = vercel_config_dir();
271+
let turbo_result = config_dir();
272+
273+
assert!(vercel_result.is_ok(), "vercel_result should be ok");
274+
let vercel_path_result = vercel_result.unwrap();
275+
assert!(vercel_path_result.is_some(), "vercel path should be some");
276+
assert_eq!(vercel_path_result.unwrap().as_str(), vercel_path);
277+
278+
assert!(turbo_result.is_ok(), "turbo_result should be ok");
279+
// config_dir should return the default, not vercel_path
280+
if let Some(turbo_path) = turbo_result.unwrap() {
281+
assert_ne!(turbo_path.as_str(), vercel_path);
282+
}
283+
284+
unsafe {
285+
env::remove_var("VERCEL_CONFIG_DIR_PATH");
286+
}
287+
}
288+
289+
#[test]
290+
fn test_error_display() {
291+
// Test that the Error enum formats correctly
292+
let error = Error::ConfigDirNotFound;
293+
assert_eq!(error.to_string(), "Config directory not found.");
294+
}
295+
296+
#[test]
297+
fn test_error_debug() {
298+
// Test that the Error enum can be debug formatted
299+
let error = Error::ConfigDirNotFound;
300+
let debug_str = format!("{:?}", error);
301+
assert!(debug_str.contains("ConfigDirNotFound"));
302+
}
303+
}

0 commit comments

Comments
 (0)