1 // Copyright 2020 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.
12 "golang.org/x/tools/internal/lsp/fake"
13 "golang.org/x/tools/internal/lsp/source"
14 "golang.org/x/tools/internal/span"
17 // osFileSource is a fileSource that just reads from the operating system.
18 type osFileSource struct{}
20 func (s osFileSource) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
21 fi, statErr := os.Stat(uri.Filename())
28 fh, err := readFile(ctx, uri, fi.ModTime())
35 func TestWorkspaceModule(t *testing.T) {
38 initial string // txtar-encoded
40 initialSource workspaceSource
41 initialModules []string
43 updates map[string]string
44 finalSource workspaceSource
56 initialModules: []string{"./go.mod"},
57 initialSource: legacyWorkspace,
58 initialDirs: []string{"."},
61 desc: "nested module",
67 initialModules: []string{"./go.mod", "a/go.mod"},
68 initialSource: fileSystemWorkspace,
69 initialDirs: []string{".", "a"},
72 desc: "removing module",
78 initialModules: []string{"a/go.mod", "b/go.mod"},
79 initialSource: fileSystemWorkspace,
80 initialDirs: []string{".", "a", "b"},
81 updates: map[string]string{
82 "gopls.mod": `module gopls-workspace
84 require moda.com v0.0.0-goplsworkspace
85 replace moda.com => $SANDBOX_WORKDIR/a`,
87 finalModules: []string{"a/go.mod"},
88 finalSource: goplsModWorkspace,
89 finalDirs: []string{".", "a"},
92 desc: "adding module",
95 require moda.com v0.0.0-goplsworkspace
96 replace moda.com => $SANDBOX_WORKDIR/a
101 initialModules: []string{"a/go.mod"},
102 initialSource: goplsModWorkspace,
103 initialDirs: []string{".", "a"},
104 updates: map[string]string{
105 "gopls.mod": `module gopls-workspace
107 require moda.com v0.0.0-goplsworkspace
108 require modb.com v0.0.0-goplsworkspace
110 replace moda.com => $SANDBOX_WORKDIR/a
111 replace modb.com => $SANDBOX_WORKDIR/b`,
113 finalModules: []string{"a/go.mod", "b/go.mod"},
114 finalSource: goplsModWorkspace,
115 finalDirs: []string{".", "a", "b"},
118 desc: "deleting gopls.mod",
121 module gopls-workspace
123 require moda.com v0.0.0-goplsworkspace
124 replace moda.com => $SANDBOX_WORKDIR/a
129 initialModules: []string{"a/go.mod"},
130 initialSource: goplsModWorkspace,
131 initialDirs: []string{".", "a"},
132 updates: map[string]string{
135 finalModules: []string{"a/go.mod", "b/go.mod"},
136 finalSource: fileSystemWorkspace,
137 finalDirs: []string{".", "a", "b"},
140 desc: "broken module parsing",
145 require gopls.test v0.0.0-goplsworkspace
146 replace gopls.test => ../../gopls.test // (this path shouldn't matter)
149 initialModules: []string{"a/go.mod", "b/go.mod"},
150 initialSource: fileSystemWorkspace,
151 initialDirs: []string{".", "a", "b", "../gopls.test"},
152 updates: map[string]string{
153 "a/go.mod": `modul moda.com
155 require gopls.test v0.0.0-goplsworkspace
156 replace gopls.test => ../../gopls.test2`,
158 finalModules: []string{"a/go.mod", "b/go.mod"},
159 finalSource: fileSystemWorkspace,
160 // finalDirs should be unchanged: we should preserve dirs in the presence
161 // of a broken modfile.
162 finalDirs: []string{".", "a", "b", "../gopls.test"},
166 for _, test := range tests {
167 t.Run(test.desc, func(t *testing.T) {
168 ctx := context.Background()
169 dir, err := fake.Tempdir(test.initial)
173 defer os.RemoveAll(dir)
174 root := span.URIFromPath(dir)
177 wm, err := newWorkspace(ctx, root, fs, !test.legacyMode)
181 rel := fake.RelativeTo(dir)
182 checkWorkspaceModule(t, rel, wm, test.initialSource, test.initialModules)
183 gotDirs := wm.dirs(ctx, fs)
184 checkWorkspaceDirs(t, rel, gotDirs, test.initialDirs)
185 if test.updates != nil {
186 changes := make(map[span.URI]*fileChange)
187 for k, v := range test.updates {
189 // for convenience, use this to signal a deletion. TODO: more doc
190 err := os.Remove(rel.AbsPath(k))
195 fake.WriteFileData(k, []byte(v), rel)
197 uri := span.URIFromPath(rel.AbsPath(k))
198 fh, err := fs.GetFile(ctx, uri)
202 content, err := fh.Read()
203 changes[uri] = &fileChange{
206 fileHandle: &closedFile{fh},
209 wm, _ := wm.invalidate(ctx, changes)
210 checkWorkspaceModule(t, rel, wm, test.finalSource, test.finalModules)
211 gotDirs := wm.dirs(ctx, fs)
212 checkWorkspaceDirs(t, rel, gotDirs, test.finalDirs)
218 func checkWorkspaceModule(t *testing.T, rel fake.RelativeTo, got *workspace, wantSource workspaceSource, want []string) {
220 if got.moduleSource != wantSource {
221 t.Errorf("module source = %v, want %v", got.moduleSource, wantSource)
223 modules := make(map[span.URI]struct{})
224 for k := range got.activeModFiles() {
225 modules[k] = struct{}{}
227 for _, modPath := range want {
228 path := rel.AbsPath(modPath)
229 uri := span.URIFromPath(path)
230 if _, ok := modules[uri]; !ok {
231 t.Errorf("missing module %q", uri)
235 for remaining := range modules {
236 t.Errorf("unexpected module %q", remaining)
240 func checkWorkspaceDirs(t *testing.T, rel fake.RelativeTo, got []span.URI, want []string) {
242 gotM := make(map[span.URI]bool)
243 for _, dir := range got {
246 for _, dir := range want {
247 path := rel.AbsPath(dir)
248 uri := span.URIFromPath(path)
250 t.Errorf("missing dir %q", uri)
254 for remaining := range gotM {
255 t.Errorf("unexpected dir %q", remaining)