Skip to content

Commit 0f2c5c8

Browse files
committed
mock fs now supports multiple volumes on windows
1 parent 100a51e commit 0f2c5c8

File tree

2 files changed

+119
-50
lines changed

2 files changed

+119
-50
lines changed

internal/fs/fs_mock.go

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,24 @@ type mockFS struct {
2222
dirs map[string]DirEntries
2323
files map[string]string
2424
absWorkingDir string
25+
defaultVolume string
2526
Kind MockKind
2627
}
2728

2829
func MockFS(input map[string]string, kind MockKind, absWorkingDir string) FS {
2930
dirs := make(map[string]DirEntries)
3031
files := make(map[string]string)
3132

33+
var defaultVolume string
34+
if kind == MockWindows {
35+
_, defaultVolume = win2unix(absWorkingDir)
36+
}
37+
3238
for k, v := range input {
39+
var volume string
3340
files[k] = v
3441
if kind == MockWindows {
35-
k = win2unix(k)
42+
k, volume = win2unix(k)
3643
}
3744
original := k
3845

@@ -41,7 +48,7 @@ func MockFS(input map[string]string, kind MockKind, absWorkingDir string) FS {
4148
kDir := path.Dir(k)
4249
key := kDir
4350
if kind == MockWindows {
44-
key = unix2win(key)
51+
key = unix2win(key, volume, defaultVolume)
4552
}
4653
dir, ok := dirs[key]
4754
if !ok {
@@ -61,7 +68,7 @@ func MockFS(input map[string]string, kind MockKind, absWorkingDir string) FS {
6168
}
6269
}
6370

64-
return &mockFS{dirs, files, absWorkingDir, kind}
71+
return &mockFS{dirs, files, absWorkingDir, defaultVolume, kind}
6572
}
6673

6774
func (fs *mockFS) ReadDirectory(path string) (DirEntries, error, error) {
@@ -114,92 +121,106 @@ func (fs *mockFS) ModKey(path string) (ModKey, error) {
114121
return ModKey{}, errors.New("This is not available during tests")
115122
}
116123

117-
func win2unix(p string) string {
118-
if strings.HasPrefix(p, "C:\\") || strings.HasPrefix(p, "c:\\") {
119-
p = p[2:]
124+
func win2unix(p string) (result string, volume string) {
125+
if len(p) > 0 && strings.HasPrefix(p[1:], ":\\") {
126+
if c := p[0]; (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') {
127+
volume = p[0:1]
128+
p = p[2:]
129+
}
120130
}
121131
p = strings.ReplaceAll(p, "\\", "/")
122-
return p
132+
return p, volume
123133
}
124134

125-
func unix2win(p string) string {
135+
func unix2win(p string, volume string, defaultVolume string) string {
126136
p = strings.ReplaceAll(p, "/", "\\")
127137
if strings.HasPrefix(p, "\\") {
128-
p = "C:" + p
138+
if volume == "" {
139+
volume = defaultVolume
140+
}
141+
p = volume + ":" + p
129142
}
130143
return p
131144
}
132145

133146
func (fs *mockFS) IsAbs(p string) bool {
134147
if fs.Kind == MockWindows {
135-
p = win2unix(p)
148+
p, _ = win2unix(p)
136149
}
137150
return path.IsAbs(p)
138151
}
139152

140153
func (fs *mockFS) Abs(p string) (string, bool) {
154+
var volume string
141155
if fs.Kind == MockWindows {
142-
p = win2unix(p)
156+
p, volume = win2unix(p)
143157
}
144158

145159
p = path.Clean(path.Join("/", p))
146160

147161
if fs.Kind == MockWindows {
148-
p = unix2win(p)
162+
p = unix2win(p, volume, fs.defaultVolume)
149163
}
150164

151165
return p, true
152166
}
153167

154168
func (fs *mockFS) Dir(p string) string {
169+
var volume string
155170
if fs.Kind == MockWindows {
156-
p = win2unix(p)
171+
p, volume = win2unix(p)
157172
}
158173

159174
p = path.Dir(p)
160175

161176
if fs.Kind == MockWindows {
162-
p = unix2win(p)
177+
p = unix2win(p, volume, fs.defaultVolume)
163178
}
164179

165180
return p
166181
}
167182

168183
func (fs *mockFS) Base(p string) string {
184+
var volume string
169185
if fs.Kind == MockWindows {
170-
p = win2unix(p)
186+
p, volume = win2unix(p)
171187
}
172188

173189
p = path.Base(p)
174190

175191
if fs.Kind == MockWindows && p == "/" {
176-
p = "\\"
192+
p = volume + ":\\"
177193
}
178194

179195
return p
180196
}
181197

182198
func (fs *mockFS) Ext(p string) string {
183199
if fs.Kind == MockWindows {
184-
p = win2unix(p)
200+
p, _ = win2unix(p)
185201
}
186202

187203
return path.Ext(p)
188204
}
189205

190206
func (fs *mockFS) Join(parts ...string) string {
207+
var volume string
191208
if fs.Kind == MockWindows {
192209
converted := make([]string, len(parts))
193210
for i, part := range parts {
194-
converted[i] = win2unix(part)
211+
var v string
212+
converted[i], v = win2unix(part)
213+
if i == 0 {
214+
volume = v
215+
}
195216
}
196217
parts = converted
197218
}
198219

199220
p := path.Clean(path.Join(parts...))
200221

201222
if fs.Kind == MockWindows {
202-
p = unix2win(p)
223+
p = unix2win(p, volume, fs.defaultVolume)
203224
}
204225

205226
return p
@@ -217,9 +238,20 @@ func splitOnSlash(path string) (string, string) {
217238
}
218239

219240
func (fs *mockFS) Rel(base string, target string) (string, bool) {
241+
var volume string
220242
if fs.Kind == MockWindows {
221-
base = win2unix(base)
222-
target = win2unix(target)
243+
var v string
244+
base, volume = win2unix(base)
245+
target, v = win2unix(target)
246+
if volume == "" {
247+
volume = fs.defaultVolume
248+
}
249+
if v == "" {
250+
v = fs.defaultVolume
251+
}
252+
if strings.ToUpper(v) != strings.ToUpper(volume) {
253+
return "", false
254+
}
223255
}
224256

225257
base = path.Clean(base)
@@ -255,7 +287,7 @@ func (fs *mockFS) Rel(base string, target string) (string, bool) {
255287
// Stop now if base is a subpath of target
256288
if base == "" {
257289
if fs.Kind == MockWindows {
258-
target = unix2win(target)
290+
target = unix2win(target, volume, fs.defaultVolume)
259291
}
260292
return target, true
261293
}
@@ -267,15 +299,15 @@ func (fs *mockFS) Rel(base string, target string) (string, bool) {
267299
if target == "" {
268300
target = commonParent[:len(commonParent)-1]
269301
if fs.Kind == MockWindows {
270-
target = unix2win(target)
302+
target = unix2win(target, volume, fs.defaultVolume)
271303
}
272304
return target, true
273305
}
274306

275307
// Otherwise, down to the parent
276308
target = commonParent + target
277309
if fs.Kind == MockWindows {
278-
target = unix2win(target)
310+
target = unix2win(target, volume, fs.defaultVolume)
279311
}
280312
return target, true
281313
}

internal/fs/fs_mock_test.go

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,11 @@ func TestMockFSBasicUnix(t *testing.T) {
7474

7575
func TestMockFSBasicWindows(t *testing.T) {
7676
fs := MockFS(map[string]string{
77-
"C:\\README.md": "// README.md",
78-
"C:\\package.json": "// package.json",
79-
"C:\\src\\index.js": "// src/index.js",
80-
"C:\\src\\util.js": "// src/util.js",
77+
"C:\\README.md": "// README.md",
78+
"C:\\package.json": "// package.json",
79+
"C:\\src\\index.js": "// src/index.js",
80+
"C:\\src\\util.js": "// src/util.js",
81+
"D:\\other\\file.txt": "// other/file.txt",
8182
}, MockWindows, "C:\\")
8283

8384
// Test a missing file
@@ -104,6 +105,21 @@ func TestMockFSBasicWindows(t *testing.T) {
104105
t.Fatalf("Incorrect contents for C:\\src\\index.js: %q", index)
105106
}
106107

108+
// Test an existing nested file on another drive
109+
file, err, _ := fs.ReadFile("D:\\other\\file.txt")
110+
if err != nil {
111+
t.Fatal("Expected to find D:\\other\\file.txt")
112+
}
113+
if file != "// other/file.txt" {
114+
t.Fatalf("Incorrect contents for D:\\other/file.txt: %q", file)
115+
}
116+
117+
// Should not find a file on another drive
118+
_, err, _ = fs.ReadFile("C:\\other\\file.txt")
119+
if err == nil {
120+
t.Fatal("Unexpectedly found C:\\other\\file.txt")
121+
}
122+
107123
// Test a missing directory
108124
_, err, _ = fs.ReadDirectory("C:\\missing")
109125
if err == nil {
@@ -123,6 +139,17 @@ func TestMockFSBasicWindows(t *testing.T) {
123139
t.Fatalf("Incorrect contents for C:\\src: %v", src)
124140
}
125141

142+
// Test a nested directory on another drive
143+
other, err, _ := fs.ReadDirectory("D:\\other")
144+
if err != nil {
145+
t.Fatal("Expected to find D:\\other")
146+
}
147+
fileEntry, _ := other.Get("file.txt")
148+
if len(other.data) != 1 ||
149+
fileEntry == nil || fileEntry.Kind(fs) != FileEntry {
150+
t.Fatalf("Incorrect contents for D:\\other: %v", other)
151+
}
152+
126153
// Test the top-level directory
127154
slash, err, _ := fs.ReadDirectory("C:\\")
128155
if err != nil {
@@ -177,34 +204,44 @@ func TestMockFSRelUnix(t *testing.T) {
177204
func TestMockFSRelWindows(t *testing.T) {
178205
fs := MockFS(map[string]string{}, MockWindows, "C:\\")
179206

180-
expect := func(a string, b string, c string) {
207+
expect := func(a string, b string, works bool, c string) {
181208
t.Helper()
182209
t.Run(fmt.Sprintf("Rel(%q, %q) == %q", a, b, c), func(t *testing.T) {
183210
t.Helper()
184211
rel, ok := fs.Rel(a, b)
185-
if !ok {
186-
t.Fatalf("!ok")
187-
}
188-
if rel != c {
189-
t.Fatalf("Expected %q, got %q", c, rel)
212+
if works {
213+
if !ok {
214+
t.Fatalf("!ok")
215+
}
216+
if rel != c {
217+
t.Fatalf("Expected %q, got %q", c, rel)
218+
}
219+
} else {
220+
if ok {
221+
t.Fatalf("ok")
222+
}
190223
}
191224
})
192225
}
193226

194-
expect("C:\\a\\b", "C:\\a\\b", ".")
195-
expect("C:\\a\\b", "C:\\a\\b\\c", "c")
196-
expect("C:\\a\\b", "C:\\a\\b\\c\\d", "c\\d")
197-
expect("C:\\a\\b\\c", "C:\\a\\b", "..")
198-
expect("C:\\a\\b\\c\\d", "C:\\a\\b", "..\\..")
199-
expect("C:\\a\\b\\c", "C:\\a\\b\\x", "..\\x")
200-
expect("C:\\a\\b\\c\\d", "C:\\a\\b\\x", "..\\..\\x")
201-
expect("C:\\a\\b\\c", "C:\\a\\b\\x\\y", "..\\x\\y")
202-
expect("C:\\a\\b\\c\\d", "C:\\a\\b\\x\\y", "..\\..\\x\\y")
203-
204-
expect("a\\b", "a\\c", "..\\c")
205-
expect(".\\a\\b", ".\\a\\c", "..\\c")
206-
expect(".", ".\\a\\b", "a\\b")
207-
expect(".", ".\\\\a\\b", "a\\b")
208-
expect(".", ".\\.\\a\\b", "a\\b")
209-
expect(".", ".\\.\\\\a\\b", "a\\b")
227+
expect("C:\\a\\b", "C:\\a\\b", true, ".")
228+
expect("C:\\a\\b", "C:\\a\\b\\c", true, "c")
229+
expect("C:\\a\\b", "C:\\a\\b\\c\\d", true, "c\\d")
230+
expect("C:\\a\\b\\c", "C:\\a\\b", true, "..")
231+
expect("C:\\a\\b\\c\\d", "C:\\a\\b", true, "..\\..")
232+
expect("C:\\a\\b\\c", "C:\\a\\b\\x", true, "..\\x")
233+
expect("C:\\a\\b\\c\\d", "C:\\a\\b\\x", true, "..\\..\\x")
234+
expect("C:\\a\\b\\c", "C:\\a\\b\\x\\y", true, "..\\x\\y")
235+
expect("C:\\a\\b\\c\\d", "C:\\a\\b\\x\\y", true, "..\\..\\x\\y")
236+
237+
expect("a\\b", "a\\c", true, "..\\c")
238+
expect(".\\a\\b", ".\\a\\c", true, "..\\c")
239+
expect(".", ".\\a\\b", true, "a\\b")
240+
expect(".", ".\\\\a\\b", true, "a\\b")
241+
expect(".", ".\\.\\a\\b", true, "a\\b")
242+
expect(".", ".\\.\\\\a\\b", true, "a\\b")
243+
244+
expect("C:\\a\\b", "\\a\\b", true, ".")
245+
expect("\\a", "\\b", true, "..\\b")
246+
expect("C:\\a", "D:\\a", false, "")
210247
}

0 commit comments

Comments
 (0)