golang.org/x/tools/gopls@v0.15.3/internal/cache/session_test.go (about) 1 // Copyright 2023 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 "path" 11 "path/filepath" 12 "testing" 13 14 "github.com/google/go-cmp/cmp" 15 "golang.org/x/tools/gopls/internal/protocol" 16 "golang.org/x/tools/gopls/internal/settings" 17 "golang.org/x/tools/gopls/internal/test/integration/fake" 18 "golang.org/x/tools/internal/testenv" 19 ) 20 21 func TestZeroConfigAlgorithm(t *testing.T) { 22 testenv.NeedsExec(t) // executes the Go command 23 t.Setenv("GOPACKAGESDRIVER", "off") 24 25 type viewSummary struct { 26 // fields exported for cmp.Diff 27 Type ViewType 28 Root string 29 Env []string 30 } 31 32 type folderSummary struct { 33 dir string 34 options func(dir string) map[string]any // options may refer to the temp dir 35 } 36 37 includeReplaceInWorkspace := func(string) map[string]any { 38 return map[string]any{ 39 "includeReplaceInWorkspace": true, 40 } 41 } 42 43 type test struct { 44 name string 45 files map[string]string // use a map rather than txtar as file content is tiny 46 folders []folderSummary 47 open []string // open files 48 want []viewSummary 49 } 50 51 tests := []test{ 52 // TODO(rfindley): add a test for GOPACKAGESDRIVER. 53 // Doing so doesn't yet work using options alone (user env is not honored) 54 55 // TODO(rfindley): add a test for degenerate cases, such as missing 56 // workspace folders (once we decide on the correct behavior). 57 { 58 "basic go.work workspace", 59 map[string]string{ 60 "go.work": "go 1.18\nuse (\n\t./a\n\t./b\n)\n", 61 "a/go.mod": "module golang.org/a\ngo 1.18\n", 62 "b/go.mod": "module golang.org/b\ngo 1.18\n", 63 }, 64 []folderSummary{{dir: "."}}, 65 nil, 66 []viewSummary{{GoWorkView, ".", nil}}, 67 }, 68 { 69 "basic go.mod workspace", 70 map[string]string{ 71 "go.mod": "module golang.org/a\ngo 1.18\n", 72 }, 73 []folderSummary{{dir: "."}}, 74 nil, 75 []viewSummary{{GoModView, ".", nil}}, 76 }, 77 { 78 "basic GOPATH workspace", 79 map[string]string{ 80 "src/golang.org/a/a.go": "package a", 81 "src/golang.org/b/b.go": "package b", 82 }, 83 []folderSummary{{ 84 dir: "src", 85 options: func(dir string) map[string]any { 86 return map[string]any{ 87 "env": map[string]any{ 88 "GOPATH": dir, 89 }, 90 } 91 }, 92 }}, 93 []string{"src/golang.org/a//a.go", "src/golang.org/b/b.go"}, 94 []viewSummary{{GOPATHView, "src", nil}}, 95 }, 96 { 97 "basic AdHoc workspace", 98 map[string]string{ 99 "foo.go": "package foo", 100 }, 101 []folderSummary{{dir: "."}}, 102 nil, 103 []viewSummary{{AdHocView, ".", nil}}, 104 }, 105 { 106 "multi-folder workspace", 107 map[string]string{ 108 "a/go.mod": "module golang.org/a\ngo 1.18\n", 109 "b/go.mod": "module golang.org/b\ngo 1.18\n", 110 }, 111 []folderSummary{{dir: "a"}, {dir: "b"}}, 112 nil, 113 []viewSummary{{GoModView, "a", nil}, {GoModView, "b", nil}}, 114 }, 115 { 116 "multi-module workspace", 117 map[string]string{ 118 "a/go.mod": "module golang.org/a\ngo 1.18\n", 119 "b/go.mod": "module golang.org/b\ngo 1.18\n", 120 }, 121 []folderSummary{{dir: "."}}, 122 nil, 123 []viewSummary{{AdHocView, ".", nil}}, 124 }, 125 { 126 "zero-config open module", 127 map[string]string{ 128 "a/go.mod": "module golang.org/a\ngo 1.18\n", 129 "a/a.go": "package a", 130 "b/go.mod": "module golang.org/b\ngo 1.18\n", 131 "b/b.go": "package b", 132 }, 133 []folderSummary{{dir: "."}}, 134 []string{"a/a.go"}, 135 []viewSummary{ 136 {AdHocView, ".", nil}, 137 {GoModView, "a", nil}, 138 }, 139 }, 140 { 141 "zero-config open modules", 142 map[string]string{ 143 "a/go.mod": "module golang.org/a\ngo 1.18\n", 144 "a/a.go": "package a", 145 "b/go.mod": "module golang.org/b\ngo 1.18\n", 146 "b/b.go": "package b", 147 }, 148 []folderSummary{{dir: "."}}, 149 []string{"a/a.go", "b/b.go"}, 150 []viewSummary{ 151 {AdHocView, ".", nil}, 152 {GoModView, "a", nil}, 153 {GoModView, "b", nil}, 154 }, 155 }, 156 { 157 "unified workspace", 158 map[string]string{ 159 "go.work": "go 1.18\nuse (\n\t./a\n\t./b\n)\n", 160 "a/go.mod": "module golang.org/a\ngo 1.18\n", 161 "a/a.go": "package a", 162 "b/go.mod": "module golang.org/b\ngo 1.18\n", 163 "b/b.go": "package b", 164 }, 165 []folderSummary{{dir: "."}}, 166 []string{"a/a.go", "b/b.go"}, 167 []viewSummary{{GoWorkView, ".", nil}}, 168 }, 169 { 170 "go.work from env", 171 map[string]string{ 172 "nested/go.work": "go 1.18\nuse (\n\t../a\n\t../b\n)\n", 173 "a/go.mod": "module golang.org/a\ngo 1.18\n", 174 "a/a.go": "package a", 175 "b/go.mod": "module golang.org/b\ngo 1.18\n", 176 "b/b.go": "package b", 177 }, 178 []folderSummary{{ 179 dir: ".", 180 options: func(dir string) map[string]any { 181 return map[string]any{ 182 "env": map[string]any{ 183 "GOWORK": filepath.Join(dir, "nested", "go.work"), 184 }, 185 } 186 }, 187 }}, 188 []string{"a/a.go", "b/b.go"}, 189 []viewSummary{{GoWorkView, ".", nil}}, 190 }, 191 { 192 "independent module view", 193 map[string]string{ 194 "go.work": "go 1.18\nuse (\n\t./a\n)\n", // not using b 195 "a/go.mod": "module golang.org/a\ngo 1.18\n", 196 "a/a.go": "package a", 197 "b/go.mod": "module golang.org/a\ngo 1.18\n", 198 "b/b.go": "package b", 199 }, 200 []folderSummary{{dir: "."}}, 201 []string{"a/a.go", "b/b.go"}, 202 []viewSummary{ 203 {GoWorkView, ".", nil}, 204 {GoModView, "b", []string{"GOWORK=off"}}, 205 }, 206 }, 207 { 208 "multiple go.work", 209 map[string]string{ 210 "go.work": "go 1.18\nuse (\n\t./a\n\t./b\n)\n", 211 "a/go.mod": "module golang.org/a\ngo 1.18\n", 212 "a/a.go": "package a", 213 "b/go.work": "go 1.18\nuse (\n\t.\n\t./c\n)\n", 214 "b/go.mod": "module golang.org/b\ngo 1.18\n", 215 "b/b.go": "package b", 216 "b/c/go.mod": "module golang.org/c\ngo 1.18\n", 217 }, 218 []folderSummary{{dir: "."}}, 219 []string{"a/a.go", "b/b.go", "b/c/c.go"}, 220 []viewSummary{{GoWorkView, ".", nil}, {GoWorkView, "b", nil}}, 221 }, 222 { 223 "multiple go.work, c unused", 224 map[string]string{ 225 "go.work": "go 1.18\nuse (\n\t./a\n\t./b\n)\n", 226 "a/go.mod": "module golang.org/a\ngo 1.18\n", 227 "a/a.go": "package a", 228 "b/go.work": "go 1.18\nuse (\n\t.\n)\n", 229 "b/go.mod": "module golang.org/b\ngo 1.18\n", 230 "b/b.go": "package b", 231 "b/c/go.mod": "module golang.org/c\ngo 1.18\n", 232 }, 233 []folderSummary{{dir: "."}}, 234 []string{"a/a.go", "b/b.go", "b/c/c.go"}, 235 []viewSummary{{GoWorkView, ".", nil}, {GoModView, "b/c", []string{"GOWORK=off"}}}, 236 }, 237 { 238 "go.mod with nested replace", 239 map[string]string{ 240 "go.mod": "module golang.org/a\n require golang.org/b v1.2.3\nreplace example.com/b => ./b", 241 "a.go": "package a", 242 "b/go.mod": "module golang.org/b\ngo 1.18\n", 243 "b/b.go": "package b", 244 }, 245 []folderSummary{{dir: ".", options: includeReplaceInWorkspace}}, 246 []string{"a/a.go", "b/b.go"}, 247 []viewSummary{{GoModView, ".", nil}}, 248 }, 249 { 250 "go.mod with parent replace, parent folder", 251 map[string]string{ 252 "go.mod": "module golang.org/a", 253 "a.go": "package a", 254 "b/go.mod": "module golang.org/b\ngo 1.18\nrequire golang.org/a v1.2.3\nreplace golang.org/a => ../", 255 "b/b.go": "package b", 256 }, 257 []folderSummary{{dir: ".", options: includeReplaceInWorkspace}}, 258 []string{"a/a.go", "b/b.go"}, 259 []viewSummary{{GoModView, ".", nil}, {GoModView, "b", nil}}, 260 }, 261 { 262 "go.mod with multiple replace", 263 map[string]string{ 264 "go.mod": ` 265 module golang.org/root 266 267 require ( 268 golang.org/a v1.2.3 269 golang.org/b v1.2.3 270 golang.org/c v1.2.3 271 ) 272 273 replace ( 274 golang.org/b => ./b 275 golang.org/c => ./c 276 // Note: d is not replaced 277 ) 278 `, 279 "a.go": "package a", 280 "b/go.mod": "module golang.org/b\ngo 1.18", 281 "b/b.go": "package b", 282 "c/go.mod": "module golang.org/c\ngo 1.18", 283 "c/c.go": "package c", 284 "d/go.mod": "module golang.org/d\ngo 1.18", 285 "d/d.go": "package d", 286 }, 287 []folderSummary{{dir: ".", options: includeReplaceInWorkspace}}, 288 []string{"b/b.go", "c/c.go", "d/d.go"}, 289 []viewSummary{{GoModView, ".", nil}, {GoModView, "d", nil}}, 290 }, 291 { 292 "go.mod with replace outside the workspace", 293 map[string]string{ 294 "go.mod": "module golang.org/a\ngo 1.18", 295 "a.go": "package a", 296 "b/go.mod": "module golang.org/b\ngo 1.18\nrequire golang.org/a v1.2.3\nreplace golang.org/a => ../", 297 "b/b.go": "package b", 298 }, 299 []folderSummary{{dir: "b"}}, 300 []string{"a.go", "b/b.go"}, 301 []viewSummary{{GoModView, "b", nil}}, 302 }, 303 { 304 "go.mod with replace directive; workspace replace off", 305 map[string]string{ 306 "go.mod": "module golang.org/a\n require golang.org/b v1.2.3\nreplace example.com/b => ./b", 307 "a.go": "package a", 308 "b/go.mod": "module golang.org/b\ngo 1.18\n", 309 "b/b.go": "package b", 310 }, 311 []folderSummary{{ 312 dir: ".", 313 options: func(string) map[string]any { 314 return map[string]any{ 315 "includeReplaceInWorkspace": false, 316 } 317 }, 318 }}, 319 []string{"a/a.go", "b/b.go"}, 320 []viewSummary{{GoModView, ".", nil}, {GoModView, "b", nil}}, 321 }, 322 } 323 324 for _, test := range tests { 325 ctx := context.Background() 326 t.Run(test.name, func(t *testing.T) { 327 dir := writeFiles(t, test.files) 328 rel := fake.RelativeTo(dir) 329 fs := newMemoizedFS() 330 331 toURI := func(path string) protocol.DocumentURI { 332 return protocol.URIFromPath(rel.AbsPath(path)) 333 } 334 335 var folders []*Folder 336 for _, f := range test.folders { 337 opts := settings.DefaultOptions() 338 if f.options != nil { 339 results := settings.SetOptions(opts, f.options(dir)) 340 for _, r := range results { 341 if r.Error != nil { 342 t.Fatalf("setting option %v: %v", r.Name, r.Error) 343 } 344 } 345 } 346 env, err := FetchGoEnv(ctx, toURI(f.dir), opts) 347 if err != nil { 348 t.Fatalf("FetchGoEnv failed: %v", err) 349 } 350 folders = append(folders, &Folder{ 351 Dir: toURI(f.dir), 352 Name: path.Base(f.dir), 353 Options: opts, 354 Env: *env, 355 }) 356 } 357 358 var openFiles []protocol.DocumentURI 359 for _, path := range test.open { 360 openFiles = append(openFiles, toURI(path)) 361 } 362 363 defs, err := selectViewDefs(ctx, fs, folders, openFiles) 364 if err != nil { 365 t.Fatal(err) 366 } 367 var got []viewSummary 368 for _, def := range defs { 369 got = append(got, viewSummary{ 370 Type: def.Type(), 371 Root: rel.RelPath(def.root.Path()), 372 Env: def.EnvOverlay(), 373 }) 374 } 375 if diff := cmp.Diff(test.want, got); diff != "" { 376 t.Errorf("selectViews() mismatch (-want +got):\n%s", diff) 377 } 378 }) 379 } 380 } 381 382 // TODO(rfindley): this function could be meaningfully factored with the 383 // various other test helpers of this nature. 384 func writeFiles(t *testing.T, files map[string]string) string { 385 root := t.TempDir() 386 387 // This unfortunate step is required because gopls output 388 // expands symbolic links in its input file names (arguably it 389 // should not), and on macOS the temp dir is in /var -> private/var. 390 root, err := filepath.EvalSymlinks(root) 391 if err != nil { 392 t.Fatal(err) 393 } 394 395 for name, content := range files { 396 filename := filepath.Join(root, name) 397 if err := os.MkdirAll(filepath.Dir(filename), 0777); err != nil { 398 t.Fatal(err) 399 } 400 if err := os.WriteFile(filename, []byte(content), 0666); err != nil { 401 t.Fatal(err) 402 } 403 } 404 return root 405 }