返回 DeepSeek-Reasonix
physical_windows_test.go
根目录 / internal / pathidentity / physical_windows_test.go
1 //go:build windows
2
3 package pathidentity
4
5 import (
6 "errors"
7 "os"
8 "os/exec"
9 "path/filepath"
10 "strings"
11 "testing"
12 )
13
14 func junctionFixture(t *testing.T) (string, string) {
15 t.Helper()
16 root := t.TempDir()
17 target, alias := filepath.Join(root, "target"), filepath.Join(root, "alias")
18 if err := os.Mkdir(target, 0o700); err != nil {
19 t.Fatal(err)
20 }
21 if out, err := exec.Command("cmd", "/c", "mklink", "/J", alias, target).CombinedOutput(); err != nil {
22 t.Fatalf("create junction: %v: %s", err, out)
23 }
24 return alias, target
25 }
26
27 func TestResolveWindowsJunctionEntryChildrenAndMissingTail(t *testing.T) {
28 alias, target := junctionFixture(t)
29 if err := os.Mkdir(filepath.Join(target, "locks"), 0o700); err != nil {
30 t.Fatal(err)
31 }
32 if err := os.WriteFile(filepath.Join(target, "locks", "existing.lock"), nil, 0o600); err != nil {
33 t.Fatal(err)
34 }
35 for _, tail := range []string{"", "locks", `locks\existing.lock`, `locks\absent\new.lock`} {
36 t.Run(tail, func(t *testing.T) {
37 left, err := Resolve(filepath.Join(alias, tail), Options{FollowLeaf: true})
38 if err != nil {
39 t.Fatal(err)
40 }
41 right, err := Resolve(filepath.Join(target, tail), Options{FollowLeaf: true})
42 if err != nil || left.Key != right.Key || left.PhysicalPath != right.PhysicalPath {
43 t.Fatalf("alias=%+v target=%+v err=%v", left, right, err)
44 }
45 if left.AccessPath != filepath.Join(alias, tail) {
46 t.Fatal("access spelling changed")
47 }
48 })
49 }
50 if _, err := os.Lstat(filepath.Join(target, "locks", "absent")); !os.IsNotExist(err) {
51 t.Fatalf("missing tail was created: %v", err)
52 }
53 }
54
55 func TestResolveWindowsJunctionPreservesOnlyLeaf(t *testing.T) {
56 alias, target := junctionFixture(t)
57 entry, err := Resolve(alias, Options{FollowLeaf: false})
58 if err != nil {
59 t.Fatal(err)
60 }
61 parent, err := Resolve(filepath.Dir(alias), Options{FollowLeaf: true})
62 if err != nil || entry.PhysicalPath != filepath.Join(parent.PhysicalPath, filepath.Base(alias)) {
63 t.Fatalf("leaf entry followed: %+v, %v", entry, err)
64 }
65 child, err := Resolve(filepath.Join(alias, "child"), Options{FollowLeaf: false})
66 real, realErr := Resolve(filepath.Join(target, "child"), Options{FollowLeaf: false})
67 if err != nil || realErr != nil || child.Key != real.Key {
68 t.Fatalf("parent junction not resolved: %+v / %+v, %v / %v", child, real, err, realErr)
69 }
70 }
71
72 func TestResolveWindowsCrossVolumeJunction(t *testing.T) {
73 root := t.TempDir()
74 // GitHub's Windows runners place RUNNER_TEMP on the work drive and the
75 // process temp directory on the system drive. Local single-drive machines
76 // still execute the unconditional junction cases above.
77 other := os.Getenv("RUNNER_TEMP")
78 if !filepath.IsAbs(other) || strings.EqualFold(filepath.VolumeName(root), filepath.VolumeName(other)) {
79 if os.Getenv("GITHUB_ACTIONS") == "true" {
80 t.Fatal("Windows CI must provide temporary directories on two volumes")
81 }
82 t.Skip("no temporary directory on a second volume")
83 }
84 target, err := os.MkdirTemp(other, "reasonix-junction-*")
85 if err != nil {
86 t.Fatal(err)
87 }
88 t.Cleanup(func() { _ = os.RemoveAll(target) })
89 alias := filepath.Join(root, "alias")
90 if out, err := exec.Command("cmd", "/c", "mklink", "/J", alias, target).CombinedOutput(); err != nil {
91 t.Fatalf("create cross-volume junction: %v: %s", err, out)
92 }
93 for _, tail := range []string{"", `missing\new.lock`} {
94 left, err := Resolve(filepath.Join(alias, tail), Options{FollowLeaf: true})
95 right, rightErr := Resolve(filepath.Join(target, tail), Options{FollowLeaf: true})
96 if err != nil || rightErr != nil || left.Key != right.Key || left.PhysicalPath != right.PhysicalPath {
97 t.Fatalf("cross-volume identity split: %+v / %+v, %v / %v", left, right, err, rightErr)
98 }
99 }
100 }
101
102 func TestResolveWindowsRejectsDanglingJunction(t *testing.T) {
103 alias, target := junctionFixture(t)
104 if err := os.Remove(target); err != nil {
105 t.Fatal(err)
106 }
107 for _, path := range []string{alias, filepath.Join(alias, "missing.lock")} {
108 _, err := Resolve(path, Options{FollowLeaf: true})
109 var failure *Error
110 if !errors.As(err, &failure) || failure.Stage != "physical" || failure.Kind != ErrorUnavailable {
111 t.Fatalf("dangling junction accepted: %q, %v", path, err)
112 }
113 }
114 }
115
116 func TestResolveWindowsLongPath(t *testing.T) {
117 path := t.TempDir()
118 for len(path) < 300 {
119 path = filepath.Join(path, strings.Repeat("a", 40))
120 }
121 if err := os.MkdirAll(path, 0o700); err != nil {
122 t.Fatal(err)
123 }
124 plain, err := Resolve(path, Options{FollowLeaf: true})
125 extended, extendedErr := Resolve(extendedWindowsPath(path), Options{FollowLeaf: true})
126 if err != nil || extendedErr != nil || plain.Key != extended.Key {
127 t.Fatalf("long path identity: %+v / %+v, %v / %v", plain, extended, err, extendedErr)
128 }
129 }
130
131 func TestWindowsNativePathSpelling(t *testing.T) {
132 for _, pair := range [][2]string{
133 {`C:\dir`, `\\?\C:\dir`},
134 {`\\server\share\dir`, `\\?\UNC\server\share\dir`},
135 {`\\?\Volume{test}\dir`, `\\?\Volume{test}\dir`},
136 } {
137 if got := extendedWindowsPath(pair[0]); got != pair[1] {
138 t.Errorf("extend %q = %q", pair[0], got)
139 }
140 if got, want := ntPhysicalPath(pair[0]), `\??\`+pair[1][4:]; got != want {
141 t.Errorf("NT path %q = %q, want %q", pair[0], got, want)
142 }
143 if got := stripExtendedPrefix(pair[1]); got != pair[0] {
144 t.Errorf("strip %q = %q", pair[1], got)
145 }
146 }
147 }
148
148 lines GO