.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.1.1-0.20210319172145-bda8f5cee399 / internal / lsp / cache / workspace_test.go
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.
4
5 package cache
6
7 import (
8         "context"
9         "os"
10         "strings"
11         "testing"
12
13         "golang.org/x/tools/internal/lsp/fake"
14         "golang.org/x/tools/internal/lsp/source"
15         "golang.org/x/tools/internal/span"
16 )
17
18 // osFileSource is a fileSource that just reads from the operating system.
19 type osFileSource struct {
20         overlays map[span.URI]fakeOverlay
21 }
22
23 type fakeOverlay struct {
24         source.VersionedFileHandle
25         uri     span.URI
26         content string
27         err     error
28         saved   bool
29 }
30
31 func (o fakeOverlay) Saved() bool { return o.saved }
32
33 func (o fakeOverlay) Read() ([]byte, error) {
34         if o.err != nil {
35                 return nil, o.err
36         }
37         return []byte(o.content), nil
38 }
39
40 func (o fakeOverlay) URI() span.URI {
41         return o.uri
42 }
43
44 // change updates the file source with the given file content. For convenience,
45 // empty content signals a deletion. If saved is true, these changes are
46 // persisted to disk.
47 func (s *osFileSource) change(ctx context.Context, uri span.URI, content string, saved bool) (*fileChange, error) {
48         if content == "" {
49                 delete(s.overlays, uri)
50                 if saved {
51                         if err := os.Remove(uri.Filename()); err != nil {
52                                 return nil, err
53                         }
54                 }
55                 fh, err := s.GetFile(ctx, uri)
56                 if err != nil {
57                         return nil, err
58                 }
59                 data, err := fh.Read()
60                 return &fileChange{exists: err == nil, content: data, fileHandle: &closedFile{fh}}, nil
61         }
62         if s.overlays == nil {
63                 s.overlays = map[span.URI]fakeOverlay{}
64         }
65         s.overlays[uri] = fakeOverlay{uri: uri, content: content, saved: saved}
66         return &fileChange{
67                 exists:     content != "",
68                 content:    []byte(content),
69                 fileHandle: s.overlays[uri],
70         }, nil
71 }
72
73 func (s *osFileSource) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
74         if overlay, ok := s.overlays[uri]; ok {
75                 return overlay, nil
76         }
77         fi, statErr := os.Stat(uri.Filename())
78         if statErr != nil {
79                 return &fileHandle{
80                         err: statErr,
81                         uri: uri,
82                 }, nil
83         }
84         fh, err := readFile(ctx, uri, fi)
85         if err != nil {
86                 return nil, err
87         }
88         return fh, nil
89 }
90
91 type wsState struct {
92         source  workspaceSource
93         modules []string
94         dirs    []string
95         sum     string
96 }
97
98 type wsChange struct {
99         content string
100         saved   bool
101 }
102
103 func TestWorkspaceModule(t *testing.T) {
104         tests := []struct {
105                 desc         string
106                 initial      string // txtar-encoded
107                 legacyMode   bool
108                 initialState wsState
109                 updates      map[string]wsChange
110                 wantChanged  bool
111                 wantReload   bool
112                 finalState   wsState
113         }{
114                 {
115                         desc: "legacy mode",
116                         initial: `
117 -- go.mod --
118 module mod.com
119 -- go.sum --
120 golang.org/x/mod v0.3.0 h1:deadbeef
121 -- a/go.mod --
122 module moda.com`,
123                         legacyMode: true,
124                         initialState: wsState{
125                                 modules: []string{"./go.mod"},
126                                 source:  legacyWorkspace,
127                                 dirs:    []string{"."},
128                                 sum:     "golang.org/x/mod v0.3.0 h1:deadbeef\n",
129                         },
130                 },
131                 {
132                         desc: "nested module",
133                         initial: `
134 -- go.mod --
135 module mod.com
136 -- a/go.mod --
137 module moda.com`,
138                         initialState: wsState{
139                                 modules: []string{"./go.mod", "a/go.mod"},
140                                 source:  fileSystemWorkspace,
141                                 dirs:    []string{".", "a"},
142                         },
143                 },
144                 {
145                         desc: "removing module",
146                         initial: `
147 -- a/go.mod --
148 module moda.com
149 -- a/go.sum --
150 golang.org/x/mod v0.3.0 h1:deadbeef
151 -- b/go.mod --
152 module modb.com
153 -- b/go.sum --
154 golang.org/x/mod v0.3.0 h1:beefdead`,
155                         initialState: wsState{
156                                 modules: []string{"a/go.mod", "b/go.mod"},
157                                 source:  fileSystemWorkspace,
158                                 dirs:    []string{".", "a", "b"},
159                                 sum:     "golang.org/x/mod v0.3.0 h1:beefdead\ngolang.org/x/mod v0.3.0 h1:deadbeef\n",
160                         },
161                         updates: map[string]wsChange{
162                                 "gopls.mod": {`module gopls-workspace
163
164 require moda.com v0.0.0-goplsworkspace
165 replace moda.com => $SANDBOX_WORKDIR/a`, true},
166                         },
167                         wantChanged: true,
168                         wantReload:  true,
169                         finalState: wsState{
170                                 modules: []string{"a/go.mod"},
171                                 source:  goplsModWorkspace,
172                                 dirs:    []string{".", "a"},
173                                 sum:     "golang.org/x/mod v0.3.0 h1:deadbeef\n",
174                         },
175                 },
176                 {
177                         desc: "adding module",
178                         initial: `
179 -- gopls.mod --
180 require moda.com v0.0.0-goplsworkspace
181 replace moda.com => $SANDBOX_WORKDIR/a
182 -- a/go.mod --
183 module moda.com
184 -- b/go.mod --
185 module modb.com`,
186                         initialState: wsState{
187                                 modules: []string{"a/go.mod"},
188                                 source:  goplsModWorkspace,
189                                 dirs:    []string{".", "a"},
190                         },
191                         updates: map[string]wsChange{
192                                 "gopls.mod": {`module gopls-workspace
193
194 require moda.com v0.0.0-goplsworkspace
195 require modb.com v0.0.0-goplsworkspace
196
197 replace moda.com => $SANDBOX_WORKDIR/a
198 replace modb.com => $SANDBOX_WORKDIR/b`, true},
199                         },
200                         wantChanged: true,
201                         wantReload:  true,
202                         finalState: wsState{
203                                 modules: []string{"a/go.mod", "b/go.mod"},
204                                 source:  goplsModWorkspace,
205                                 dirs:    []string{".", "a", "b"},
206                         },
207                 },
208                 {
209                         desc: "deleting gopls.mod",
210                         initial: `
211 -- gopls.mod --
212 module gopls-workspace
213
214 require moda.com v0.0.0-goplsworkspace
215 replace moda.com => $SANDBOX_WORKDIR/a
216 -- a/go.mod --
217 module moda.com
218 -- b/go.mod --
219 module modb.com`,
220                         initialState: wsState{
221                                 modules: []string{"a/go.mod"},
222                                 source:  goplsModWorkspace,
223                                 dirs:    []string{".", "a"},
224                         },
225                         updates: map[string]wsChange{
226                                 "gopls.mod": {"", true},
227                         },
228                         wantChanged: true,
229                         wantReload:  true,
230                         finalState: wsState{
231                                 modules: []string{"a/go.mod", "b/go.mod"},
232                                 source:  fileSystemWorkspace,
233                                 dirs:    []string{".", "a", "b"},
234                         },
235                 },
236                 {
237                         desc: "broken module parsing",
238                         initial: `
239 -- a/go.mod --
240 module moda.com
241
242 require gopls.test v0.0.0-goplsworkspace
243 replace gopls.test => ../../gopls.test // (this path shouldn't matter)
244 -- b/go.mod --
245 module modb.com`,
246                         initialState: wsState{
247                                 modules: []string{"a/go.mod", "b/go.mod"},
248                                 source:  fileSystemWorkspace,
249                                 dirs:    []string{".", "a", "b", "../gopls.test"},
250                         },
251                         updates: map[string]wsChange{
252                                 "a/go.mod": {`modul moda.com
253
254 require gopls.test v0.0.0-goplsworkspace
255 replace gopls.test => ../../gopls.test2`, false},
256                         },
257                         wantChanged: true,
258                         wantReload:  false,
259                         finalState: wsState{
260                                 modules: []string{"a/go.mod", "b/go.mod"},
261                                 source:  fileSystemWorkspace,
262                                 // finalDirs should be unchanged: we should preserve dirs in the presence
263                                 // of a broken modfile.
264                                 dirs: []string{".", "a", "b", "../gopls.test"},
265                         },
266                 },
267         }
268
269         for _, test := range tests {
270                 t.Run(test.desc, func(t *testing.T) {
271                         ctx := context.Background()
272                         dir, err := fake.Tempdir(test.initial)
273                         if err != nil {
274                                 t.Fatal(err)
275                         }
276                         defer os.RemoveAll(dir)
277                         root := span.URIFromPath(dir)
278
279                         fs := &osFileSource{}
280                         excludeNothing := func(string) bool { return false }
281                         w, err := newWorkspace(ctx, root, fs, excludeNothing, false, !test.legacyMode)
282                         if err != nil {
283                                 t.Fatal(err)
284                         }
285                         rel := fake.RelativeTo(dir)
286                         checkState(ctx, t, fs, rel, w, test.initialState)
287
288                         // Apply updates.
289                         if test.updates != nil {
290                                 changes := make(map[span.URI]*fileChange)
291                                 for k, v := range test.updates {
292                                         content := strings.ReplaceAll(v.content, "$SANDBOX_WORKDIR", string(rel))
293                                         uri := span.URIFromPath(rel.AbsPath(k))
294                                         changes[uri], err = fs.change(ctx, uri, content, v.saved)
295                                         if err != nil {
296                                                 t.Fatal(err)
297                                         }
298                                 }
299                                 got, gotChanged, gotReload := w.invalidate(ctx, changes)
300                                 if gotChanged != test.wantChanged {
301                                         t.Errorf("w.invalidate(): got changed %t, want %t", gotChanged, test.wantChanged)
302                                 }
303                                 if gotReload != test.wantReload {
304                                         t.Errorf("w.invalidate(): got reload %t, want %t", gotReload, test.wantReload)
305                                 }
306                                 checkState(ctx, t, fs, rel, got, test.finalState)
307                         }
308                 })
309         }
310 }
311
312 func checkState(ctx context.Context, t *testing.T, fs source.FileSource, rel fake.RelativeTo, got *workspace, want wsState) {
313         t.Helper()
314         if got.moduleSource != want.source {
315                 t.Errorf("module source = %v, want %v", got.moduleSource, want.source)
316         }
317         modules := make(map[span.URI]struct{})
318         for k := range got.getActiveModFiles() {
319                 modules[k] = struct{}{}
320         }
321         for _, modPath := range want.modules {
322                 path := rel.AbsPath(modPath)
323                 uri := span.URIFromPath(path)
324                 if _, ok := modules[uri]; !ok {
325                         t.Errorf("missing module %q", uri)
326                 }
327                 delete(modules, uri)
328         }
329         for remaining := range modules {
330                 t.Errorf("unexpected module %q", remaining)
331         }
332         gotDirs := got.dirs(ctx, fs)
333         gotM := make(map[span.URI]bool)
334         for _, dir := range gotDirs {
335                 gotM[dir] = true
336         }
337         for _, dir := range want.dirs {
338                 path := rel.AbsPath(dir)
339                 uri := span.URIFromPath(path)
340                 if !gotM[uri] {
341                         t.Errorf("missing dir %q", uri)
342                 }
343                 delete(gotM, uri)
344         }
345         for remaining := range gotM {
346                 t.Errorf("unexpected dir %q", remaining)
347         }
348         gotSumBytes, err := got.sumFile(ctx, fs)
349         if err != nil {
350                 t.Fatal(err)
351         }
352         if gotSum := string(gotSumBytes); gotSum != want.sum {
353                 t.Errorf("got final sum %q, want %q", gotSum, want.sum)
354         }
355 }