test: skip the symlink tests when the platform won't allow symlinks

Nine tests fail on an ordinary Windows machine, eight in backend/local and
TestEnvironmentVariables in cmdtest, all with

    symlink file.txt \?\C:\Users\...\symlink.txt: A required privilege is not
    held by the client.

Windows grants SeCreateSymbolicLinkPrivilege only to an elevated process or one
running with Developer Mode enabled, and a default install gives an ordinary
user neither. CI does not see this because the windows-latest runner is
elevated, so the failures only show up on a contributor's own machine, where
AGENTS.md asks for make quicktest to pass before opening a pull request.

cmdtest already recognised the situation and attached a note to the failure
saying the test could safely be ignored. If it is safe to ignore then the test
knows it cannot run, so skip it and say why instead.

backend/local gains a helper that tries a symlink in t.TempDir() and skips if it
cannot make one, called from the six tests that need the privilege. Where a
platform can create symlinks the probe succeeds and nothing is skipped, so other
platforms are unchanged.

TestMetadata is skipped whole because it creates its symlink before anything
else and the object built from it is used throughout.
TestSymlinkEscapeConcurrent is left alone: it goes through putLink and ignores
the error, so it never needed the privilege.
This commit is contained in:
Dhevenddra
2026-08-28 14:10:46 +01:00
committed by Nick Craig-Wood
parent 1583cce1e2
commit 5fc1cc3ca1
2 changed files with 23 additions and 6 deletions
+18
View File
@@ -96,7 +96,19 @@ func TestVerifyCopy(t *testing.T) {
assert.Error(t, err)
}
// skipIfNoSymlinks skips the test if this process can't create symlinks.
//
// Windows grants the privilege only to an elevated process or one running with
// Developer Mode enabled, so an ordinary user gets ERROR_PRIVILEGE_NOT_HELD.
func skipIfNoSymlinks(t *testing.T) {
t.Helper()
if err := os.Symlink("target", filepath.Join(t.TempDir(), "link")); err != nil {
t.Skipf("Skipping as symlinks are unavailable: %v", err)
}
}
func TestSymlink(t *testing.T) {
skipIfNoSymlinks(t)
ctx := context.Background()
r := fstest.NewRun(t)
f := r.Flocal.(*Fs)
@@ -238,6 +250,7 @@ func linksMode(f *Fs) {
// faithful backup of the source) but must refuse to write through it, so
// nothing lands outside the destination (CWE-59).
func TestSymlinkEscapeWriteThroughBlocked(t *testing.T) {
skipIfNoSymlinks(t)
ctx := context.Background()
// A directory outside the destination the attacker wants to write into
@@ -273,6 +286,7 @@ func TestSymlinkEscapeWriteThroughBlocked(t *testing.T) {
// re-validated against the root, so the write-through is refused and nothing
// escapes.
func TestSymlinkEscapeNestedBlocked(t *testing.T) {
skipIfNoSymlinks(t)
ctx := context.Background()
evil := t.TempDir()
@@ -324,6 +338,7 @@ func TestSymlinkEscapeConcurrent(t *testing.T) {
// use: an in-tree symlink to a sibling directory can still be created and
// written through, since that write stays inside the destination.
func TestSymlinkInTreeWriteThroughWorks(t *testing.T) {
skipIfNoSymlinks(t)
ctx := context.Background()
r := fstest.NewRun(t)
@@ -555,6 +570,7 @@ func TestHashOnDelete(t *testing.T) {
}
func TestMetadata(t *testing.T) {
skipIfNoSymlinks(t)
ctx := context.Background()
r := fstest.NewRun(t)
const filePath = "metafile.txt"
@@ -831,6 +847,7 @@ func TestFilter(t *testing.T) {
}
func testFilterSymlink(t *testing.T, copyLinks bool) {
skipIfNoSymlinks(t)
ctx := context.Background()
r := fstest.NewRun(t)
defer r.Finalise()
@@ -935,6 +952,7 @@ func TestFilterSymlinkLinks(t *testing.T) {
}
func TestCopySymlink(t *testing.T) {
skipIfNoSymlinks(t)
ctx := context.Background()
r := fstest.NewRun(t)
defer r.Finalise()