Skip to content

Commit 8439b73

Browse files
author
Jay Conrod
committed
godoc/vfs: fix union logic in NameSpace.ReadDir
ReadDir now returns files from directories in all matching mount points if no Go files are present in any of them. The behavior now matches the documentation. Fixes golang/go#34571 Change-Id: I3a0c8d49a5906ec33ebe9e3efea9d2b9d267506c Reviewed-on: https://go-review.googlesource.com/c/tools/+/197801 Run-TryBot: Jay Conrod <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent c4c97a4 commit 8439b73

File tree

3 files changed

+165
-100
lines changed

3 files changed

+165
-100
lines changed

vfs/emptyvfs_test.go

Lines changed: 0 additions & 58 deletions
This file was deleted.

vfs/namespace.go

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -298,66 +298,52 @@ var startTime = time.Now()
298298
func (ns NameSpace) ReadDir(path string) ([]os.FileInfo, error) {
299299
path = ns.clean(path)
300300

301+
// List matching directories and determine whether any of them contain
302+
// Go files.
301303
var (
302-
haveGo = false
303-
haveName = map[string]bool{}
304-
all []os.FileInfo
305-
err error
306-
first []os.FileInfo
304+
dirs [][]os.FileInfo
305+
goDirIndex = -1
306+
readDirErr error
307307
)
308308

309309
for _, m := range ns.resolve(path) {
310-
dir, err1 := m.fs.ReadDir(m.translate(path))
311-
if err1 != nil {
312-
if err == nil {
313-
err = err1
310+
dir, err := m.fs.ReadDir(m.translate(path))
311+
if err != nil {
312+
if readDirErr == nil {
313+
readDirErr = err
314314
}
315315
continue
316316
}
317317

318-
if dir == nil {
319-
dir = []os.FileInfo{}
320-
}
321-
322-
if first == nil {
323-
first = dir
324-
}
318+
dirs = append(dirs, dir)
325319

326-
// If we don't yet have Go files in 'all' and this directory
327-
// has some, add all the files from this directory.
328-
// Otherwise, only add subdirectories.
329-
useFiles := false
330-
if !haveGo {
331-
for _, d := range dir {
332-
if strings.HasSuffix(d.Name(), ".go") {
333-
useFiles = true
334-
haveGo = true
320+
if goDirIndex < 0 {
321+
for _, f := range dir {
322+
if !f.IsDir() && strings.HasSuffix(f.Name(), ".go") {
323+
goDirIndex = len(dirs) - 1
335324
break
336325
}
337326
}
338327
}
339-
340-
for _, d := range dir {
341-
name := d.Name()
342-
if (d.IsDir() || useFiles) && !haveName[name] {
343-
haveName[name] = true
344-
all = append(all, d)
345-
}
346-
}
347328
}
348329

349-
// We didn't find any directories containing Go files.
350-
// If some directory returned successfully, use that.
351-
if !haveGo {
352-
for _, d := range first {
353-
if !haveName[d.Name()] {
354-
haveName[d.Name()] = true
355-
all = append(all, d)
330+
// Build a list of files and subdirectories. If a directory contains Go files,
331+
// only include files from that directory. Otherwise, include files from
332+
// all directories. Include subdirectories from all directories regardless
333+
// of whether Go files are present.
334+
haveName := make(map[string]bool)
335+
var all []os.FileInfo
336+
for i, dir := range dirs {
337+
for _, f := range dir {
338+
name := f.Name()
339+
if !haveName[name] && (f.IsDir() || goDirIndex < 0 || goDirIndex == i) {
340+
all = append(all, f)
341+
haveName[name] = true
356342
}
357343
}
358344
}
359345

360-
// Built union. Add any missing directories needed to reach mount points.
346+
// Add any missing directories needed to reach mount points.
361347
for old := range ns {
362348
if hasPathPrefix(old, path) && old != path {
363349
// Find next element after path in old.
@@ -374,7 +360,7 @@ func (ns NameSpace) ReadDir(path string) ([]os.FileInfo, error) {
374360
}
375361

376362
if len(all) == 0 {
377-
return nil, err
363+
return nil, readDirErr
378364
}
379365

380366
sort.Sort(byName(all))

vfs/namespace_test.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package vfs_test
6+
7+
import (
8+
"fmt"
9+
"strings"
10+
"testing"
11+
"time"
12+
13+
"golang.org/x/tools/godoc/vfs"
14+
"golang.org/x/tools/godoc/vfs/mapfs"
15+
)
16+
17+
func TestNewNameSpace(t *testing.T) {
18+
19+
// We will mount this filesystem under /fs1
20+
mount := mapfs.New(map[string]string{"fs1file": "abcdefgh"})
21+
22+
// Existing process. This should give error on Stat("/")
23+
t1 := vfs.NameSpace{}
24+
t1.Bind("/fs1", mount, "/", vfs.BindReplace)
25+
26+
// using NewNameSpace. This should work fine.
27+
t2 := vfs.NewNameSpace()
28+
t2.Bind("/fs1", mount, "/", vfs.BindReplace)
29+
30+
testcases := map[string][]bool{
31+
"/": {false, true},
32+
"/fs1": {true, true},
33+
"/fs1/fs1file": {true, true},
34+
}
35+
36+
fss := []vfs.FileSystem{t1, t2}
37+
38+
for j, fs := range fss {
39+
for k, v := range testcases {
40+
_, err := fs.Stat(k)
41+
result := err == nil
42+
if result != v[j] {
43+
t.Errorf("fs: %d, testcase: %s, want: %v, got: %v, err: %s", j, k, v[j], result, err)
44+
}
45+
}
46+
}
47+
48+
fi, err := t2.Stat("/")
49+
if err != nil {
50+
t.Fatal(err)
51+
}
52+
53+
if fi.Name() != "/" {
54+
t.Errorf("t2.Name() : want:%s got:%s", "/", fi.Name())
55+
}
56+
57+
if !fi.ModTime().IsZero() {
58+
t.Errorf("t2.ModTime() : want:%v got:%v", time.Time{}, fi.ModTime())
59+
}
60+
}
61+
62+
func TestReadDirUnion(t *testing.T) {
63+
for _, tc := range []struct {
64+
desc string
65+
ns vfs.NameSpace
66+
path, want string
67+
}{
68+
{
69+
desc: "no_go_files",
70+
ns: func() vfs.NameSpace {
71+
rootFs := mapfs.New(map[string]string{
72+
"doc/a.txt": "1",
73+
"doc/b.txt": "1",
74+
"doc/dir1/d1.txt": "",
75+
})
76+
docFs := mapfs.New(map[string]string{
77+
"doc/a.txt": "22",
78+
"doc/dir2/d2.txt": "",
79+
})
80+
ns := vfs.NameSpace{}
81+
ns.Bind("/", rootFs, "/", vfs.BindReplace)
82+
ns.Bind("/doc", docFs, "/doc", vfs.BindBefore)
83+
return ns
84+
}(),
85+
path: "/doc",
86+
want: "a.txt:2,b.txt:1,dir1:0,dir2:0",
87+
}, {
88+
desc: "have_go_files",
89+
ns: func() vfs.NameSpace {
90+
a := mapfs.New(map[string]string{
91+
"src/x/a.txt": "",
92+
"src/x/suba/sub.txt": "",
93+
})
94+
b := mapfs.New(map[string]string{
95+
"src/x/b.go": "package b",
96+
"src/x/subb/sub.txt": "",
97+
})
98+
c := mapfs.New(map[string]string{
99+
"src/x/c.txt": "",
100+
"src/x/subc/sub.txt": "",
101+
})
102+
ns := vfs.NameSpace{}
103+
ns.Bind("/", a, "/", vfs.BindReplace)
104+
ns.Bind("/", b, "/", vfs.BindAfter)
105+
ns.Bind("/", c, "/", vfs.BindAfter)
106+
return ns
107+
}(),
108+
path: "/src/x",
109+
want: "b.go:9,suba:0,subb:0,subc:0",
110+
}, {
111+
desc: "empty_mount",
112+
ns: func() vfs.NameSpace {
113+
ns := vfs.NameSpace{}
114+
ns.Bind("/empty", mapfs.New(nil), "/empty", vfs.BindReplace)
115+
return ns
116+
}(),
117+
path: "/",
118+
want: "empty:0",
119+
},
120+
} {
121+
t.Run(tc.desc, func(t *testing.T) {
122+
fis, err := tc.ns.ReadDir(tc.path)
123+
if err != nil {
124+
t.Fatal(err)
125+
}
126+
buf := &strings.Builder{}
127+
sep := ""
128+
for _, fi := range fis {
129+
fmt.Fprintf(buf, "%s%s:%d", sep, fi.Name(), fi.Size())
130+
sep = ","
131+
}
132+
if got := buf.String(); got != tc.want {
133+
t.Errorf("got %q; want %q", got, tc.want)
134+
}
135+
})
136+
}
137+
}

0 commit comments

Comments
 (0)