Repository: rust-lang/rustup (Rust)

Issue:
The test `toolchain_broken_symlink` is failing on Windows
The test `suite::cli_misc::toolchain_broken_symlink` is failing on my Windows 10 machine.

I tested this on commit b5d74ac69307de29cee586c779bdb6b3aee7f7af

I ran `cargo test --features=test toolchain_broken_symlink` and got this output:

```
...
---- suite::cli_misc::toolchain_broken_symlink stdout ----
ran: rustup ["set", "default-host", "x86_64-pc-windows-msvc"]
inprocess: true
status: Some(0)
duration: 0.170s
stdout:
====

====

stderr:
====

====

ran: rustup ["set", "auto-self-update", "disable"]
inprocess: true
status: Some(0)
duration: 0.056s
stdout:
====

====

stderr:
====
info: auto-self-update mode set to 'disable'

====

thread 'suite::cli_misc::toolchain_broken_symlink' panicked at tests\suite\cli_misc.rs:680:35:
called `Result::unwrap()` on an `Err` value: Os { code: 1314, kind: Uncategorized, message: "A required privilege is not held by the client." }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    suite::cli_misc::toolchain_broken_symlink

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 373 filtered out; finished in 1.71s

error: test failed, to rerun pass `--test test_bonanza`
```


Discussion:
You need to enable "Developer Mode" in the settings to allow creating of symlinks as a non-privileged user.
Ah, I thought it would be something like that.

Is it worth disabling this test on Windows? Or is it possible to detect that setting and suppress the failure?
It's important that this test is run in CI on a Windows host. It could perhaps pass the test if it's not in CI and creating a symlink returns `ERROR_PRIVILEGE_NOT_HELD` (aka error code `1314`).
That sounds like a good course of action to me.
@majaha would you be able to submit a PR for this? IIRC we can detect CI environments by the existence of a `CI` environment variable.
I've had a deeper look into this and found that there's actually another underlying issue: rustup on windows doesn't use Window's symbolic links, it uses "directory junctions", which are somewhere between a hard link and a symbolic link, and usually don't need permissions. The test should be using those instead (and preferably using the same code-path as rustup itself uses to create them). I'll try to come up with a patch for that.
Okay, I've created a PR that fixes this over here: https://github.com/rust-lang/rustup/pull/3522



Patch:
diff --git a/src/utils/raw.rs b/src/utils/raw.rs
index bbfe799d1e..cd733f538e 100644
--- a/src/utils/raw.rs
+++ b/src/utils/raw.rs
@@ -131,7 +131,7 @@ pub fn append_file(dest: &Path, line: &str) -> io::Result<()> {
     Ok(())
 }
 
-pub(crate) fn symlink_dir(src: &Path, dest: &Path) -> io::Result<()> {
+pub fn symlink_dir(src: &Path, dest: &Path) -> io::Result<()> {
     #[cfg(windows)]
     fn symlink_dir_inner(src: &Path, dest: &Path) -> io::Result<()> {
         // std's symlink uses Windows's symlink function, which requires


Test patch:
diff --git a/tests/suite/cli_misc.rs b/tests/suite/cli_misc.rs
index 4aaf5f76be..37bf1346cc 100644
--- a/tests/suite/cli_misc.rs
+++ b/tests/suite/cli_misc.rs
@@ -665,29 +665,22 @@ fn rename_rls_remove() {
 #[test]
 #[cfg(any(unix, windows))]
 fn toolchain_broken_symlink() {
+    use rustup::utils::raw::symlink_dir;
     use std::fs;
     use std::path::Path;
 
-    #[cfg(unix)]
-    fn create_symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) {
-        use std::os::unix::fs;
-        fs::symlink(src, dst).unwrap();
-    }
-
-    #[cfg(windows)]
-    fn create_symlink_dir<P: AsRef<Path>, Q: AsRef<Path>>(src: P, dst: Q) {
-        use std::os::windows::fs;
-        fs::symlink_dir(src, dst).unwrap();
-    }
-
     clitools::test(Scenario::None, &|config| {
         // We artificially create a broken symlink toolchain -- but this can also happen "legitimately"
         // by having a proper toolchain there, using "toolchain link", and later removing the directory.
         fs::create_dir(config.rustupdir.join("toolchains")).unwrap();
-        create_symlink_dir(
-            config.rustupdir.join("this-directory-does-not-exist"),
-            config.rustupdir.join("toolchains").join("test"),
-        );
+        fs::create_dir(config.rustupdir.join("this-directory-does-not-exist")).unwrap();
+        symlink_dir(
+            &config.rustupdir.join("this-directory-does-not-exist"),
+            &config.rustupdir.join("toolchains").join("test"),
+        )
+        .unwrap();
+        fs::remove_dir(config.rustupdir.join("this-directory-does-not-exist")).unwrap();
+
         // Make sure this "fake install" actually worked
         config.expect_ok_ex(&["rustup", "toolchain", "list"], "test\n", "");
         // Now try to uninstall it.  That should work only once.