github.com/jd-ly/tools@v0.5.7/internal/lsp/cache/workspace_test.go (about) 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 "testing" 11 12 "github.com/jd-ly/tools/internal/lsp/fake" 13 "github.com/jd-ly/tools/internal/lsp/source" 14 "github.com/jd-ly/tools/internal/span" 15 ) 16 17 // osFileSource is a fileSource that just reads from the operating system. 18 type osFileSource struct{} 19 20 func (s osFileSource) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) { 21 fi, statErr := os.Stat(uri.Filename()) 22 if statErr != nil { 23 return &fileHandle{ 24 err: statErr, 25 uri: uri, 26 }, nil 27 } 28 fh, err := readFile(ctx, uri, fi.ModTime()) 29 if err != nil { 30 return nil, err 31 } 32 return fh, nil 33 } 34 35 func TestWorkspaceModule(t *testing.T) { 36 tests := []struct { 37 desc string 38 initial string // txtar-encoded 39 legacyMode bool 40 initialSource workspaceSource 41 initialModules []string 42 initialDirs []string 43 updates map[string]string 44 finalSource workspaceSource 45 finalModules []string 46 finalDirs []string 47 }{ 48 { 49 desc: "legacy mode", 50 initial: ` 51 -- go.mod -- 52 module mod.com 53 -- a/go.mod -- 54 module moda.com`, 55 legacyMode: true, 56 initialModules: []string{"./go.mod"}, 57 initialSource: legacyWorkspace, 58 initialDirs: []string{"."}, 59 }, 60 { 61 desc: "nested module", 62 initial: ` 63 -- go.mod -- 64 module mod.com 65 -- a/go.mod -- 66 module moda.com`, 67 initialModules: []string{"./go.mod", "a/go.mod"}, 68 initialSource: fileSystemWorkspace, 69 initialDirs: []string{".", "a"}, 70 }, 71 { 72 desc: "removing module", 73 initial: ` 74 -- a/go.mod -- 75 module moda.com 76 -- b/go.mod -- 77 module modb.com`, 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 83 84 require moda.com v0.0.0-goplsworkspace 85 replace moda.com => $SANDBOX_WORKDIR/a`, 86 }, 87 finalModules: []string{"a/go.mod"}, 88 finalSource: goplsModWorkspace, 89 finalDirs: []string{".", "a"}, 90 }, 91 { 92 desc: "adding module", 93 initial: ` 94 -- gopls.mod -- 95 require moda.com v0.0.0-goplsworkspace 96 replace moda.com => $SANDBOX_WORKDIR/a 97 -- a/go.mod -- 98 module moda.com 99 -- b/go.mod -- 100 module modb.com`, 101 initialModules: []string{"a/go.mod"}, 102 initialSource: goplsModWorkspace, 103 initialDirs: []string{".", "a"}, 104 updates: map[string]string{ 105 "gopls.mod": `module gopls-workspace 106 107 require moda.com v0.0.0-goplsworkspace 108 require modb.com v0.0.0-goplsworkspace 109 110 replace moda.com => $SANDBOX_WORKDIR/a 111 replace modb.com => $SANDBOX_WORKDIR/b`, 112 }, 113 finalModules: []string{"a/go.mod", "b/go.mod"}, 114 finalSource: goplsModWorkspace, 115 finalDirs: []string{".", "a", "b"}, 116 }, 117 { 118 desc: "deleting gopls.mod", 119 initial: ` 120 -- gopls.mod -- 121 module gopls-workspace 122 123 require moda.com v0.0.0-goplsworkspace 124 replace moda.com => $SANDBOX_WORKDIR/a 125 -- a/go.mod -- 126 module moda.com 127 -- b/go.mod -- 128 module modb.com`, 129 initialModules: []string{"a/go.mod"}, 130 initialSource: goplsModWorkspace, 131 initialDirs: []string{".", "a"}, 132 updates: map[string]string{ 133 "gopls.mod": "", 134 }, 135 finalModules: []string{"a/go.mod", "b/go.mod"}, 136 finalSource: fileSystemWorkspace, 137 finalDirs: []string{".", "a", "b"}, 138 }, 139 { 140 desc: "broken module parsing", 141 initial: ` 142 -- a/go.mod -- 143 module moda.com 144 145 require gopls.test v0.0.0-goplsworkspace 146 replace gopls.test => ../../gopls.test // (this path shouldn't matter) 147 -- b/go.mod -- 148 module modb.com`, 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 154 155 require gopls.test v0.0.0-goplsworkspace 156 replace gopls.test => ../../gopls.test2`, 157 }, 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"}, 163 }, 164 } 165 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) 170 if err != nil { 171 t.Fatal(err) 172 } 173 defer os.RemoveAll(dir) 174 root := span.URIFromPath(dir) 175 176 fs := osFileSource{} 177 wm, err := newWorkspace(ctx, root, fs, false, !test.legacyMode) 178 if err != nil { 179 t.Fatal(err) 180 } 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 { 188 if v == "" { 189 // for convenience, use this to signal a deletion. TODO: more doc 190 err := os.Remove(rel.AbsPath(k)) 191 if err != nil { 192 t.Fatal(err) 193 } 194 } else { 195 fake.WriteFileData(k, []byte(v), rel) 196 } 197 uri := span.URIFromPath(rel.AbsPath(k)) 198 fh, err := fs.GetFile(ctx, uri) 199 if err != nil { 200 t.Fatal(err) 201 } 202 content, err := fh.Read() 203 changes[uri] = &fileChange{ 204 content: content, 205 exists: err == nil, 206 fileHandle: &closedFile{fh}, 207 } 208 } 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) 213 } 214 }) 215 } 216 } 217 218 func checkWorkspaceModule(t *testing.T, rel fake.RelativeTo, got *workspace, wantSource workspaceSource, want []string) { 219 t.Helper() 220 if got.moduleSource != wantSource { 221 t.Errorf("module source = %v, want %v", got.moduleSource, wantSource) 222 } 223 modules := make(map[span.URI]struct{}) 224 for k := range got.getActiveModFiles() { 225 modules[k] = struct{}{} 226 } 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) 232 } 233 delete(modules, uri) 234 } 235 for remaining := range modules { 236 t.Errorf("unexpected module %q", remaining) 237 } 238 } 239 240 func checkWorkspaceDirs(t *testing.T, rel fake.RelativeTo, got []span.URI, want []string) { 241 t.Helper() 242 gotM := make(map[span.URI]bool) 243 for _, dir := range got { 244 gotM[dir] = true 245 } 246 for _, dir := range want { 247 path := rel.AbsPath(dir) 248 uri := span.URIFromPath(path) 249 if !gotM[uri] { 250 t.Errorf("missing dir %q", uri) 251 } 252 delete(gotM, uri) 253 } 254 for remaining := range gotM { 255 t.Errorf("unexpected dir %q", remaining) 256 } 257 }