Skip to content

Commit 2c4828e

Browse files
chansukerbtcollins
andcommitted
Fix duplicated PATH entries
- Unit test append_path and prepend_path - Order is preserved, second and above instances of a given entry are dropped. Co-Authored-By: Robert Collins <robertc@robertcollins.net> Signed-off-by: Robert Collins <robertc@robertcollins.net>
1 parent 060b8f9 commit 2c4828e

File tree

1 file changed

+178
-3
lines changed

1 file changed

+178
-3
lines changed

src/env_var.rs

Lines changed: 178 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,41 @@
11
use std::collections::VecDeque;
22
use std::env;
3+
use std::ffi::OsStr;
34
use std::path::PathBuf;
45
use std::process::Command;
56

67
use crate::process;
78

89
pub const RUST_RECURSION_COUNT_MAX: u32 = 20;
910

11+
/// This can be removed when https://github.com/rust-lang/rust/issues/ 44434 is
12+
/// stablised.
13+
pub(crate) trait ProcessEnvs {
14+
fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
15+
where
16+
Self: Sized,
17+
K: AsRef<OsStr>,
18+
V: AsRef<OsStr>;
19+
}
20+
21+
impl ProcessEnvs for Command {
22+
fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
23+
where
24+
Self: Sized,
25+
K: AsRef<OsStr>,
26+
V: AsRef<OsStr>,
27+
{
28+
self.env(key, val)
29+
}
30+
}
31+
1032
#[allow(unused)]
11-
fn append_path(name: &str, value: Vec<PathBuf>, cmd: &mut Command) {
33+
fn append_path<E: ProcessEnvs>(name: &str, value: Vec<PathBuf>, cmd: &mut E) {
1234
let old_value = process().var_os(name);
1335
let mut parts: Vec<PathBuf>;
1436
if let Some(ref v) = old_value {
15-
parts = env::split_paths(v).collect();
16-
parts.extend(value);
37+
let old_paths: Vec<PathBuf> = env::split_paths(v).collect::<Vec<_>>();
38+
parts = concat_uniq_paths(old_paths, value);
1739
} else {
1840
parts = value;
1941
}
@@ -50,3 +72,156 @@ pub(crate) fn inc(name: &str, cmd: &mut Command) {
5072

5173
cmd.env(name, (old_value + 1).to_string());
5274
}
75+
76+
fn concat_uniq_paths(fst_paths: Vec<PathBuf>, snd_paths: Vec<PathBuf>) -> Vec<PathBuf> {
77+
let deduped_fst_paths = dedupe_with_preserved_order(fst_paths);
78+
let deduped_snd_paths = dedupe_with_preserved_order(snd_paths);
79+
80+
let vec_fst_paths: Vec<_> = deduped_fst_paths.into_iter().collect();
81+
82+
let mut unified_paths;
83+
unified_paths = vec_fst_paths.clone();
84+
unified_paths.extend(
85+
deduped_snd_paths
86+
.into_iter()
87+
.filter(|v| !vec_fst_paths.contains(v))
88+
.collect::<Vec<_>>(),
89+
);
90+
91+
unified_paths
92+
}
93+
94+
fn dedupe_with_preserved_order(paths: Vec<PathBuf>) -> Vec<PathBuf> {
95+
let mut uniq_paths: Vec<PathBuf> = Vec::new();
96+
let mut seen_paths: HashSet<PathBuf> = HashSet::new();
97+
98+
for path in &paths {
99+
if !seen_paths.contains(path) {
100+
seen_paths.insert(path.to_path_buf());
101+
uniq_paths.push(path.to_path_buf());
102+
}
103+
}
104+
105+
uniq_paths
106+
}
107+
108+
#[cfg(test)]
109+
mod tests {
110+
use super::*;
111+
use crate::currentprocess;
112+
use crate::test::{with_saved_path, Env};
113+
114+
use super::ProcessEnvs;
115+
use std::collections::HashMap;
116+
use std::ffi::{OsStr, OsString};
117+
118+
#[derive(Default)]
119+
struct TestCommand {
120+
envs: HashMap<OsString, Option<OsString>>,
121+
}
122+
123+
impl ProcessEnvs for TestCommand {
124+
fn env<K, V>(&mut self, key: K, val: V) -> &mut Self
125+
where
126+
Self: Sized,
127+
K: AsRef<OsStr>,
128+
V: AsRef<OsStr>,
129+
{
130+
self.envs
131+
.insert(key.as_ref().to_owned(), Some(val.as_ref().to_owned()));
132+
self
133+
}
134+
}
135+
136+
#[test]
137+
fn deduplicate_and_concat_paths() {
138+
let mut old_paths = vec![];
139+
140+
let z = OsString::from("/home/z/.cargo/bin");
141+
let path_z = PathBuf::from(z);
142+
old_paths.push(path_z);
143+
144+
let a = OsString::from("/home/a/.cargo/bin");
145+
let path_a = PathBuf::from(a);
146+
old_paths.push(path_a);
147+
148+
let _a = OsString::from("/home/a/.cargo/bin");
149+
let _path_a = PathBuf::from(_a);
150+
old_paths.push(_path_a);
151+
152+
let mut new_paths = vec![];
153+
154+
let n = OsString::from("/home/n/.cargo/bin");
155+
let path_n = PathBuf::from(n);
156+
new_paths.push(path_n);
157+
158+
let g = OsString::from("/home/g/.cargo/bin");
159+
let path_g = PathBuf::from(g);
160+
new_paths.push(path_g);
161+
162+
let _g = OsString::from("/home/g/.cargo/bin");
163+
let _path_g = PathBuf::from(_g);
164+
new_paths.push(_path_g);
165+
166+
let _z = OsString::from("/home/z/.cargo/bin");
167+
let path_z = PathBuf::from(_z);
168+
old_paths.push(path_z);
169+
170+
let mut unified_paths: Vec<PathBuf> = vec![];
171+
let zpath = OsString::from("/home/z/.cargo/bin");
172+
let _zpath = PathBuf::from(zpath);
173+
unified_paths.push(_zpath);
174+
let apath = OsString::from("/home/a/.cargo/bin");
175+
let _apath = PathBuf::from(apath);
176+
unified_paths.push(_apath);
177+
let npath = OsString::from("/home/n/.cargo/bin");
178+
let _npath = PathBuf::from(npath);
179+
unified_paths.push(_npath);
180+
let gpath = OsString::from("/home/g/.cargo/bin");
181+
let _gpath = PathBuf::from(gpath);
182+
unified_paths.push(_gpath);
183+
184+
assert_eq!(concat_uniq_paths(old_paths, new_paths), unified_paths);
185+
}
186+
187+
#[test]
188+
fn prepend_unique_path() {
189+
let mut vars = HashMap::new();
190+
vars.env("PATH", "/home/a/.cargo/bin:/home/b/.cargo/bin");
191+
let tp = Box::new(currentprocess::TestProcess {
192+
vars,
193+
..Default::default()
194+
});
195+
with_saved_path(&|| {
196+
currentprocess::with(tp.clone(), || {
197+
let mut path_entries = vec![];
198+
let mut cmd = TestCommand::default();
199+
200+
let a = OsString::from("/home/a/.cargo/bin");
201+
let path_a = PathBuf::from(a);
202+
path_entries.push(path_a);
203+
204+
let _a = OsString::from("/home/a/.cargo/bin");
205+
let _path_a = PathBuf::from(_a);
206+
path_entries.push(_path_a);
207+
208+
let z = OsString::from("/home/z/.cargo/bin");
209+
let path_z = PathBuf::from(z);
210+
path_entries.push(path_z);
211+
212+
prepend_path("PATH", path_entries, &mut cmd);
213+
let envs: Vec<_> = cmd.envs.iter().collect();
214+
215+
assert_eq!(
216+
envs,
217+
&[(
218+
&OsString::from("PATH"),
219+
&Some(OsString::from(
220+
"/home/a/.cargo/bin:/home/z/.cargo/bin:/home/b/.cargo/bin"
221+
))
222+
),]
223+
);
224+
});
225+
});
226+
}
227+
}

0 commit comments

Comments
 (0)