Skip to content

Commit 33381fc

Browse files
authored
refactor: make TaskEnvs::resolve accept env iterator parameter (#28)
refactor: make TaskEnvs::resolve accept env iterator parameter Change TaskEnvs::resolve to accept environment variables as an iterator parameter instead of reading from std::env::vars_os() directly. This makes tests cleaner and more isolated. Changes: - Update resolve_envs_with_patterns to accept env_vars iterator - Update TaskEnvs::resolve to accept current_envs iterator parameter - Update production call sites to pass std::env::vars_os() - Refactor all tests to use clean mock environment vectors - Remove all unsafe std::env::set_var/remove_var calls from tests Benefits: - Tests are isolated and don't modify global process environment - No unsafe code needed in tests - Tests can run safely in parallel - More functional and easier to test 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> remove unnecessary code
1 parent 1f1c73b commit 33381fc

File tree

3 files changed

+97
-119
lines changed

3 files changed

+97
-119
lines changed

crates/vite_task/src/config/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ impl ResolvedTask {
181181
let cwd = &workspace.cwd;
182182
let resolved_task_config =
183183
ResolvedTaskConfig { config_dir: cwd.clone(), config: task_config };
184-
let resolved_envs = TaskEnvs::resolve(&workspace.root_dir, &resolved_task_config)?;
184+
let resolved_envs =
185+
TaskEnvs::resolve(std::env::vars_os(), &workspace.root_dir, &resolved_task_config)?;
185186
let resolved_command = ResolvedTaskCommand {
186187
fingerprint: CommandFingerprint {
187188
cwd: cwd.clone(),

crates/vite_task/src/config/task_command.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ impl ResolvedTaskConfig {
9696
}
9797
}
9898
};
99-
let task_envs = TaskEnvs::resolve(base_dir, self)?;
99+
let task_envs = TaskEnvs::resolve(std::env::vars_os(), base_dir, self)?;
100100
Ok(ResolvedTaskCommand {
101101
fingerprint: CommandFingerprint {
102102
cwd,

crates/vite_task/src/execute.rs

Lines changed: 94 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{
22
collections::hash_map::Entry,
33
env::{join_paths, split_paths},
4-
ffi::OsStr,
4+
ffi::{OsStr, OsString},
55
path::PathBuf,
66
process::{ExitStatus, Stdio},
77
sync::{Arc, LazyLock, Mutex},
@@ -123,7 +123,10 @@ pub struct TaskEnvs {
123123
pub envs_without_pass_through: HashMap<Str, Str>,
124124
}
125125

126-
fn resolve_envs_with_patterns(patterns: &[&str]) -> Result<HashMap<Str, Arc<OsStr>>, Error> {
126+
fn resolve_envs_with_patterns(
127+
env_vars: impl Iterator<Item = (OsString, OsString)>,
128+
patterns: &[&str],
129+
) -> Result<HashMap<Str, Arc<OsStr>>, Error> {
127130
let patterns = GlobPatternSet::new(patterns.iter().filter(|pattern| {
128131
if pattern.starts_with('!') {
129132
// FIXME: use better way to print warning log
@@ -137,7 +140,7 @@ fn resolve_envs_with_patterns(patterns: &[&str]) -> Result<HashMap<Str, Arc<OsSt
137140
true
138141
}
139142
}))?;
140-
let envs: HashMap<Str, Arc<OsStr>> = std::env::vars_os()
143+
let envs: HashMap<Str, Arc<OsStr>> = env_vars
141144
.filter_map(|(name, value)| {
142145
let Some(name) = name.to_str() else {
143146
return None;
@@ -241,15 +244,19 @@ const SENSITIVE_PATTERNS: &[&str] = &[
241244
];
242245

243246
impl TaskEnvs {
244-
pub fn resolve(base_dir: &AbsolutePath, task: &ResolvedTaskConfig) -> Result<Self, Error> {
247+
pub fn resolve(
248+
current_envs: impl Iterator<Item = (OsString, OsString)>,
249+
base_dir: &AbsolutePath,
250+
task: &ResolvedTaskConfig,
251+
) -> Result<Self, Error> {
245252
// All envs that are passed to the task
246253
let all_patterns: Vec<&str> = DEFAULT_PASSTHROUGH_ENVS
247254
.iter()
248255
.copied()
249256
.chain(task.config.pass_through_envs.iter().map(std::convert::AsRef::as_ref))
250257
.chain(task.config.envs.iter().map(std::convert::AsRef::as_ref))
251258
.collect();
252-
let mut all_envs = resolve_envs_with_patterns(&all_patterns)?;
259+
let mut all_envs = resolve_envs_with_patterns(current_envs, &all_patterns)?;
253260

254261
// envs need to calculate fingerprint
255262
let mut envs_without_pass_through = HashMap::<Str, Str>::new();
@@ -562,17 +569,16 @@ mod tests {
562569
let resolved_task_config =
563570
ResolvedTaskConfig { config_dir: RelativePathBuf::default(), config: task_config };
564571

565-
// Test when FORCE_COLOR is not already set
566-
unsafe {
567-
std::env::remove_var("FORCE_COLOR");
568-
}
569-
570572
let base_dir = if cfg!(windows) {
571573
AbsolutePath::new("C:\\workspace").unwrap()
572574
} else {
573575
AbsolutePath::new("/workspace").unwrap()
574576
};
575-
let result = TaskEnvs::resolve(base_dir, &resolved_task_config).unwrap();
577+
578+
// Test when FORCE_COLOR is not already set
579+
let mock_envs = vec![("PATH".into(), "/usr/bin".into())];
580+
let result =
581+
TaskEnvs::resolve(mock_envs.into_iter(), base_dir, &resolved_task_config).unwrap();
576582

577583
// FORCE_COLOR should be automatically added if color is supported
578584
// Note: This test might vary based on the test environment
@@ -585,11 +591,10 @@ mod tests {
585591
}
586592

587593
// Test when FORCE_COLOR is already set - should not be overridden
588-
unsafe {
589-
std::env::set_var("FORCE_COLOR", "2");
590-
}
591-
592-
let result2 = TaskEnvs::resolve(base_dir, &resolved_task_config).unwrap();
594+
let mock_envs =
595+
vec![("PATH".into(), "/usr/bin".into()), ("FORCE_COLOR".into(), "2".into())];
596+
let result2 =
597+
TaskEnvs::resolve(mock_envs.into_iter(), base_dir, &resolved_task_config).unwrap();
593598

594599
// Should contain the original FORCE_COLOR value
595600
assert!(result2.all_envs.contains_key("FORCE_COLOR"));
@@ -599,27 +604,15 @@ mod tests {
599604
// FORCE_COLOR should not be in envs_without_pass_through since it's a passthrough env
600605
assert!(!result2.envs_without_pass_through.contains_key("FORCE_COLOR"));
601606

602-
// Clean up
603-
unsafe {
604-
std::env::remove_var("FORCE_COLOR");
605-
}
606-
607-
// Test when NO_COLOR is already set - should not be overridden
608-
unsafe {
609-
std::env::set_var("NO_COLOR", "1");
610-
}
611-
612-
let result3 = TaskEnvs::resolve(base_dir, &resolved_task_config).unwrap();
607+
// Test when NO_COLOR is already set - FORCE_COLOR should not be automatically added
608+
let mock_envs = vec![("PATH".into(), "/usr/bin".into()), ("NO_COLOR".into(), "1".into())];
609+
let result3 =
610+
TaskEnvs::resolve(mock_envs.into_iter(), base_dir, &resolved_task_config).unwrap();
613611
assert!(result3.all_envs.contains_key("NO_COLOR"));
614612
let no_color_value = result3.all_envs.get("NO_COLOR").unwrap();
615613
assert_eq!(no_color_value.to_str().unwrap(), "1");
616614
// FORCE_COLOR should not be automatically added since NO_COLOR is set
617615
assert!(!result3.all_envs.contains_key("FORCE_COLOR"));
618-
619-
// Clean up
620-
unsafe {
621-
std::env::remove_var("NO_COLOR");
622-
}
623616
}
624617

625618
#[test]
@@ -654,31 +647,35 @@ mod tests {
654647
let resolved_task_config =
655648
ResolvedTaskConfig { config_dir: RelativePathBuf::default(), config: task_config };
656649

657-
let base_dir = if cfg!(windows) {
658-
AbsolutePath::new("C:\\workspace").unwrap()
659-
} else {
660-
AbsolutePath::new("/workspace").unwrap()
661-
};
662-
663-
// Set up environment variables
664-
unsafe {
665-
std::env::set_var("ZEBRA_VAR", "zebra_value");
666-
std::env::set_var("ALPHA_VAR", "alpha_value");
667-
std::env::set_var("MIDDLE_VAR", "middle_value");
668-
std::env::set_var("BETA_VAR", "beta_value");
669-
std::env::set_var("VSCODE_VAR", "vscode_value");
670-
std::env::set_var("APP1_TOKEN", "app1_token");
671-
std::env::set_var("APP2_TOKEN", "app2_token");
672-
std::env::set_var("APP1_NAME", "app1_value");
673-
std::env::set_var("APP2_NAME", "app2_value");
674-
std::env::set_var("APP1_PASSWORD", "app1_password");
675-
std::env::set_var("OXLINT_TSGOLINT_PATH", "/path/to/oxlint_tsgolint");
676-
}
650+
let base_dir = AbsolutePath::new("/workspace").unwrap();
651+
652+
// Create mock environment variables
653+
let mock_envs = vec![
654+
("ZEBRA_VAR".into(), "zebra_value".into()),
655+
("ALPHA_VAR".into(), "alpha_value".into()),
656+
("MIDDLE_VAR".into(), "middle_value".into()),
657+
("BETA_VAR".into(), "beta_value".into()),
658+
("VSCODE_VAR".into(), "vscode_value".into()),
659+
("APP1_TOKEN".into(), "app1_token".into()),
660+
("APP2_TOKEN".into(), "app2_token".into()),
661+
("APP1_NAME".into(), "app1_value".into()),
662+
("APP2_NAME".into(), "app2_value".into()),
663+
("APP1_PASSWORD".into(), "app1_password".into()),
664+
("OXLINT_TSGOLINT_PATH".into(), "/path/to/oxlint_tsgolint".into()),
665+
("PATH".into(), "/usr/bin".into()),
666+
("HOME".into(), "/home/user".into()),
667+
];
677668

678669
// Resolve envs multiple times
679-
let result1 = TaskEnvs::resolve(base_dir, &resolved_task_config).unwrap();
680-
let result2 = TaskEnvs::resolve(base_dir, &resolved_task_config).unwrap();
681-
let result3 = TaskEnvs::resolve(base_dir, &resolved_task_config).unwrap();
670+
let result1 =
671+
TaskEnvs::resolve(mock_envs.clone().into_iter(), base_dir, &resolved_task_config)
672+
.unwrap();
673+
let result2 =
674+
TaskEnvs::resolve(mock_envs.clone().into_iter(), base_dir, &resolved_task_config)
675+
.unwrap();
676+
let result3 =
677+
TaskEnvs::resolve(mock_envs.clone().into_iter(), base_dir, &resolved_task_config)
678+
.unwrap();
682679

683680
// Convert to sorted vecs for comparison
684681
let mut envs1: Vec<_> = result1.envs_without_pass_through.iter().collect();
@@ -730,21 +727,6 @@ mod tests {
730727
assert_eq!(env_value.to_str().unwrap(), "1");
731728
// VITE_TASK_EXECUTION_ENV should not be in envs_without_pass_through since it's not declared
732729
assert!(!result1.envs_without_pass_through.contains_key("VITE_TASK_EXECUTION_ENV"));
733-
734-
// Clean up
735-
unsafe {
736-
std::env::remove_var("ZEBRA_VAR");
737-
std::env::remove_var("ALPHA_VAR");
738-
std::env::remove_var("MIDDLE_VAR");
739-
std::env::remove_var("BETA_VAR");
740-
std::env::remove_var("VSCODE_VAR");
741-
std::env::remove_var("APP1_NAME");
742-
std::env::remove_var("APP2_NAME");
743-
std::env::remove_var("APP1_PASSWORD");
744-
std::env::remove_var("APP1_TOKEN");
745-
std::env::remove_var("APP2_TOKEN");
746-
std::env::remove_var("OXLINT_TSGOLINT_PATH");
747-
}
748730
}
749731

750732
#[test]
@@ -777,16 +759,20 @@ mod tests {
777759
let resolved_task_config =
778760
ResolvedTaskConfig { config_dir: RelativePathBuf::default(), config: task_config };
779761

780-
// Set up environment variables with different cases
781-
unsafe {
782-
std::env::set_var("TEST_VAR", "uppercase");
783-
std::env::set_var("test_var", "lowercase");
784-
std::env::set_var("Test_Var", "mixed");
785-
}
762+
// Create mock environment variables with different cases
763+
let mock_envs = vec![
764+
("TEST_VAR".into(), "uppercase".into()),
765+
("test_var".into(), "lowercase".into()),
766+
("Test_Var".into(), "mixed".into()),
767+
];
786768

787769
// Resolve envs
788-
let result =
789-
TaskEnvs::resolve(AbsolutePath::new("/tmp").unwrap(), &resolved_task_config).unwrap();
770+
let result = TaskEnvs::resolve(
771+
mock_envs.into_iter(),
772+
AbsolutePath::new("/tmp").unwrap(),
773+
&resolved_task_config,
774+
)
775+
.unwrap();
790776
let envs_without_pass_through = result.envs_without_pass_through;
791777

792778
// On Unix, all three should be treated as separate variables
@@ -808,13 +794,6 @@ mod tests {
808794
envs_without_pass_through.get("Test_Var").map(vite_str::Str::as_str),
809795
Some("mixed")
810796
);
811-
812-
// Clean up
813-
unsafe {
814-
std::env::remove_var("TEST_VAR");
815-
std::env::remove_var("test_var");
816-
std::env::remove_var("Test_Var");
817-
}
818797
}
819798

820799
#[test]
@@ -847,28 +826,37 @@ mod tests {
847826
let resolved_task_config =
848827
ResolvedTaskConfig { config_dir: RelativePathBuf::default(), config: task_config };
849828

850-
// Set up environment variables
851-
unsafe {
852-
std::env::set_var("ZEBRA_VAR", "zebra_value");
853-
std::env::set_var("ALPHA_VAR", "alpha_value");
854-
std::env::set_var("MIDDLE_VAR", "middle_value");
855-
std::env::set_var("BETA_VAR", "beta_value");
856-
// VSCode specific
857-
std::env::set_var("VSCODE_VAR", "vscode_value");
858-
std::env::set_var("app1_name", "app1_value");
859-
std::env::set_var("app2_name", "app2_value");
860-
}
829+
// Create mock environment variables
830+
let mock_envs = vec![
831+
("ZEBRA_VAR".into(), "zebra_value".into()),
832+
("ALPHA_VAR".into(), "alpha_value".into()),
833+
("MIDDLE_VAR".into(), "middle_value".into()),
834+
("BETA_VAR".into(), "beta_value".into()),
835+
("VSCODE_VAR".into(), "vscode_value".into()),
836+
("app1_name".into(), "app1_value".into()),
837+
("app2_name".into(), "app2_value".into()),
838+
("Path".into(), "C:\\Windows\\System32".into()),
839+
];
861840

862841
// Resolve envs multiple times
863-
let result1 =
864-
TaskEnvs::resolve(AbsolutePath::new("C:\\tmp").unwrap(), &resolved_task_config)
865-
.unwrap();
866-
let result2 =
867-
TaskEnvs::resolve(AbsolutePath::new("C:\\tmp").unwrap(), &resolved_task_config)
868-
.unwrap();
869-
let result3 =
870-
TaskEnvs::resolve(AbsolutePath::new("C:\\tmp").unwrap(), &resolved_task_config)
871-
.unwrap();
842+
let result1 = TaskEnvs::resolve(
843+
mock_envs.clone().into_iter(),
844+
AbsolutePath::new("C:\\tmp").unwrap(),
845+
&resolved_task_config,
846+
)
847+
.unwrap();
848+
let result2 = TaskEnvs::resolve(
849+
mock_envs.clone().into_iter(),
850+
AbsolutePath::new("C:\\tmp").unwrap(),
851+
&resolved_task_config,
852+
)
853+
.unwrap();
854+
let result3 = TaskEnvs::resolve(
855+
mock_envs.clone().into_iter(),
856+
AbsolutePath::new("C:\\tmp").unwrap(),
857+
&resolved_task_config,
858+
)
859+
.unwrap();
872860

873861
// Convert to sorted vecs for comparison
874862
let mut envs1: Vec<_> = result1.envs_without_pass_through.iter().collect();
@@ -897,17 +885,6 @@ mod tests {
897885
assert!(all_envs.contains_key("VSCODE_VAR"));
898886
assert!(all_envs.contains_key("Path") || all_envs.contains_key("PATH"));
899887
assert!(all_envs.contains_key("app1_name"));
900-
assert!(all_envs.contains_key("app1_name"));
901-
902-
// Clean up
903-
unsafe {
904-
std::env::remove_var("ZEBRA_VAR");
905-
std::env::remove_var("ALPHA_VAR");
906-
std::env::remove_var("MIDDLE_VAR");
907-
std::env::remove_var("BETA_VAR");
908-
std::env::remove_var("VSCODE_VAR");
909-
std::env::remove_var("app1_name");
910-
std::env::remove_var("app1_name");
911-
}
888+
assert!(all_envs.contains_key("app2_name"));
912889
}
913890
}

0 commit comments

Comments
 (0)