Skip to content

Commit f705efc

Browse files
authored
Merge pull request #2386 from rbtcollins/unit-tests
Fix removing from PATH on windows when last item
2 parents a580f60 + 91155e8 commit f705efc

File tree

2 files changed

+66
-59
lines changed

2 files changed

+66
-59
lines changed

src/cli/self_update/windows.rs

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -222,43 +222,59 @@ fn get_windows_path_var() -> Result<Option<String>> {
222222
}
223223
}
224224

225-
pub fn do_remove_from_path(methods: &[PathUpdateMethod]) -> Result<()> {
226-
assert!(methods.len() == 1 && methods[0] == PathUpdateMethod::Windows);
225+
// Returns None if the existing old_path does not need changing
226+
fn _remove_from_path(old_path: &str, path_str: &str) -> Option<String> {
227+
let idx = old_path.find(&path_str)?;
228+
// If there's a trailing semicolon (likely, since we probably added one
229+
// during install), include that in the substring to remove. We don't search
230+
// for that to find the string, because if its the last string in the path,
231+
// there may not be.
232+
let mut len = path_str.len();
233+
if old_path.as_bytes().get(idx + path_str.len()) == Some(&b';') {
234+
len += 1;
235+
}
227236

228-
use std::ptr;
229-
use winapi::shared::minwindef::*;
230-
use winapi::um::winuser::{
231-
SendMessageTimeoutA, HWND_BROADCAST, SMTO_ABORTIFHUNG, WM_SETTINGCHANGE,
232-
};
233-
use winreg::enums::{RegType, HKEY_CURRENT_USER, KEY_READ, KEY_WRITE};
234-
use winreg::{RegKey, RegValue};
237+
let mut new_path = old_path[..idx].to_string();
238+
new_path.push_str(&old_path[idx + len..]);
239+
// Don't leave a trailing ; though, we don't want an empty string in the
240+
// path.
241+
if new_path.ends_with(';') {
242+
new_path.pop();
243+
}
244+
Some(new_path)
245+
}
235246

247+
fn _path_without_cargo_home_bin() -> Result<Option<String>> {
236248
let old_path = if let Some(s) = get_windows_path_var()? {
237249
s
238250
} else {
239251
// Non-unicode path
240-
return Ok(());
252+
return Ok(None);
241253
};
242254

243255
let path_str = utils::cargo_home()?
244256
.join("bin")
245257
.to_string_lossy()
246258
.into_owned();
247-
let idx = if let Some(i) = old_path.find(&path_str) {
248-
i
249-
} else {
250-
return Ok(());
251-
};
252259

253-
// If there's a trailing semicolon (likely, since we added one during install),
254-
// include that in the substring to remove.
255-
let mut len = path_str.len();
256-
if old_path.as_bytes().get(idx + path_str.len()) == Some(&b';') {
257-
len += 1;
258-
}
260+
Ok(_remove_from_path(&old_path, &path_str))
261+
}
259262

260-
let mut new_path = old_path[..idx].to_string();
261-
new_path.push_str(&old_path[idx + len..]);
263+
pub fn do_remove_from_path(methods: &[PathUpdateMethod]) -> Result<()> {
264+
assert!(methods.len() == 1 && methods[0] == PathUpdateMethod::Windows);
265+
266+
use std::ptr;
267+
use winapi::shared::minwindef::*;
268+
use winapi::um::winuser::{
269+
SendMessageTimeoutA, HWND_BROADCAST, SMTO_ABORTIFHUNG, WM_SETTINGCHANGE,
270+
};
271+
use winreg::enums::{RegType, HKEY_CURRENT_USER, KEY_READ, KEY_WRITE};
272+
use winreg::{RegKey, RegValue};
273+
274+
let new_path = match _path_without_cargo_home_bin()? {
275+
Some(new_path) => new_path,
276+
None => return Ok(()), // No need to set the path
277+
};
262278

263279
let root = RegKey::predef(HKEY_CURRENT_USER);
264280
let environment = root
@@ -430,3 +446,30 @@ pub fn get_add_path_methods() -> Vec<PathUpdateMethod> {
430446
pub fn get_remove_path_methods() -> Result<Vec<PathUpdateMethod>> {
431447
Ok(vec![PathUpdateMethod::Windows])
432448
}
449+
450+
#[cfg(test)]
451+
mod tests {
452+
#[test]
453+
fn windows_uninstall_removes_semicolon_from_path_prefix() {
454+
assert_eq!(
455+
"foo",
456+
super::_remove_from_path(
457+
r"c:\users\example\.cargo\bin;foo",
458+
r"c:\users\example\.cargo\bin"
459+
)
460+
.unwrap()
461+
)
462+
}
463+
464+
#[test]
465+
fn windows_uninstall_removes_semicolon_from_path_suffix() {
466+
assert_eq!(
467+
"foo",
468+
super::_remove_from_path(
469+
r"foo;c:\users\example\.cargo\bin",
470+
r"c:\users\example\.cargo\bin"
471+
)
472+
.unwrap()
473+
)
474+
}
475+
}

tests/cli-self-upd.rs

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1091,42 +1091,6 @@ fn windows_handle_empty_path_registry_key() {
10911091
});
10921092
}
10931093

1094-
#[test]
1095-
#[cfg(windows)]
1096-
fn windows_uninstall_removes_semicolon_from_path() {
1097-
use winreg::enums::{RegType, HKEY_CURRENT_USER, KEY_READ, KEY_WRITE};
1098-
use winreg::RegKey;
1099-
1100-
setup(&|config| {
1101-
let root = RegKey::predef(HKEY_CURRENT_USER);
1102-
let environment = root
1103-
.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)
1104-
.unwrap();
1105-
1106-
// This time set the value of PATH and make sure it's restored exactly after uninstall,
1107-
// not leaving behind any semi-colons
1108-
environment.set_value("PATH", &"foo").unwrap();
1109-
1110-
expect_ok(config, &["rustup-init", "-y"]);
1111-
1112-
let root = RegKey::predef(HKEY_CURRENT_USER);
1113-
let environment = root
1114-
.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)
1115-
.unwrap();
1116-
let path = environment.get_raw_value("PATH").unwrap();
1117-
assert!(path.vtype == RegType::REG_EXPAND_SZ);
1118-
1119-
expect_ok(config, &["rustup", "self", "uninstall", "-y"]);
1120-
1121-
let root = RegKey::predef(HKEY_CURRENT_USER);
1122-
let environment = root
1123-
.open_subkey_with_flags("Environment", KEY_READ | KEY_WRITE)
1124-
.unwrap();
1125-
let path: String = environment.get_value("PATH").unwrap();
1126-
assert!(path == "foo");
1127-
});
1128-
}
1129-
11301094
#[test]
11311095
#[cfg(windows)]
11321096
fn install_doesnt_mess_with_a_non_unicode_path() {

0 commit comments

Comments
 (0)