-// Copyright 2020 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package cache
-
-import (
- "context"
- "os"
- "testing"
-
- "golang.org/x/tools/internal/lsp/fake"
- "golang.org/x/tools/internal/lsp/source"
- "golang.org/x/tools/internal/span"
-)
-
-// osFileSource is a fileSource that just reads from the operating system.
-type osFileSource struct{}
-
-func (s osFileSource) GetFile(ctx context.Context, uri span.URI) (source.FileHandle, error) {
- fi, statErr := os.Stat(uri.Filename())
- if statErr != nil {
- return &fileHandle{
- err: statErr,
- uri: uri,
- }, nil
- }
- fh, err := readFile(ctx, uri, fi.ModTime())
- if err != nil {
- return nil, err
- }
- return fh, nil
-}
-
-func TestWorkspaceModule(t *testing.T) {
- tests := []struct {
- desc string
- initial string // txtar-encoded
- legacyMode bool
- initialSource workspaceSource
- initialModules []string
- initialDirs []string
- updates map[string]string
- finalSource workspaceSource
- finalModules []string
- finalDirs []string
- }{
- {
- desc: "legacy mode",
- initial: `
--- go.mod --
-module mod.com
--- a/go.mod --
-module moda.com`,
- legacyMode: true,
- initialModules: []string{"./go.mod"},
- initialSource: legacyWorkspace,
- initialDirs: []string{"."},
- },
- {
- desc: "nested module",
- initial: `
--- go.mod --
-module mod.com
--- a/go.mod --
-module moda.com`,
- initialModules: []string{"./go.mod", "a/go.mod"},
- initialSource: fileSystemWorkspace,
- initialDirs: []string{".", "a"},
- },
- {
- desc: "removing module",
- initial: `
--- a/go.mod --
-module moda.com
--- b/go.mod --
-module modb.com`,
- initialModules: []string{"a/go.mod", "b/go.mod"},
- initialSource: fileSystemWorkspace,
- initialDirs: []string{".", "a", "b"},
- updates: map[string]string{
- "gopls.mod": `module gopls-workspace
-
-require moda.com v0.0.0-goplsworkspace
-replace moda.com => $SANDBOX_WORKDIR/a`,
- },
- finalModules: []string{"a/go.mod"},
- finalSource: goplsModWorkspace,
- finalDirs: []string{".", "a"},
- },
- {
- desc: "adding module",
- initial: `
--- gopls.mod --
-require moda.com v0.0.0-goplsworkspace
-replace moda.com => $SANDBOX_WORKDIR/a
--- a/go.mod --
-module moda.com
--- b/go.mod --
-module modb.com`,
- initialModules: []string{"a/go.mod"},
- initialSource: goplsModWorkspace,
- initialDirs: []string{".", "a"},
- updates: map[string]string{
- "gopls.mod": `module gopls-workspace
-
-require moda.com v0.0.0-goplsworkspace
-require modb.com v0.0.0-goplsworkspace
-
-replace moda.com => $SANDBOX_WORKDIR/a
-replace modb.com => $SANDBOX_WORKDIR/b`,
- },
- finalModules: []string{"a/go.mod", "b/go.mod"},
- finalSource: goplsModWorkspace,
- finalDirs: []string{".", "a", "b"},
- },
- {
- desc: "deleting gopls.mod",
- initial: `
--- gopls.mod --
-module gopls-workspace
-
-require moda.com v0.0.0-goplsworkspace
-replace moda.com => $SANDBOX_WORKDIR/a
--- a/go.mod --
-module moda.com
--- b/go.mod --
-module modb.com`,
- initialModules: []string{"a/go.mod"},
- initialSource: goplsModWorkspace,
- initialDirs: []string{".", "a"},
- updates: map[string]string{
- "gopls.mod": "",
- },
- finalModules: []string{"a/go.mod", "b/go.mod"},
- finalSource: fileSystemWorkspace,
- finalDirs: []string{".", "a", "b"},
- },
- {
- desc: "broken module parsing",
- initial: `
--- a/go.mod --
-module moda.com
-
-require gopls.test v0.0.0-goplsworkspace
-replace gopls.test => ../../gopls.test // (this path shouldn't matter)
--- b/go.mod --
-module modb.com`,
- initialModules: []string{"a/go.mod", "b/go.mod"},
- initialSource: fileSystemWorkspace,
- initialDirs: []string{".", "a", "b", "../gopls.test"},
- updates: map[string]string{
- "a/go.mod": `modul moda.com
-
-require gopls.test v0.0.0-goplsworkspace
-replace gopls.test => ../../gopls.test2`,
- },
- finalModules: []string{"a/go.mod", "b/go.mod"},
- finalSource: fileSystemWorkspace,
- // finalDirs should be unchanged: we should preserve dirs in the presence
- // of a broken modfile.
- finalDirs: []string{".", "a", "b", "../gopls.test"},
- },
- }
-
- for _, test := range tests {
- t.Run(test.desc, func(t *testing.T) {
- ctx := context.Background()
- dir, err := fake.Tempdir(test.initial)
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(dir)
- root := span.URIFromPath(dir)
-
- fs := osFileSource{}
- wm, err := newWorkspace(ctx, root, fs, !test.legacyMode)
- if err != nil {
- t.Fatal(err)
- }
- rel := fake.RelativeTo(dir)
- checkWorkspaceModule(t, rel, wm, test.initialSource, test.initialModules)
- gotDirs := wm.dirs(ctx, fs)
- checkWorkspaceDirs(t, rel, gotDirs, test.initialDirs)
- if test.updates != nil {
- changes := make(map[span.URI]*fileChange)
- for k, v := range test.updates {
- if v == "" {
- // for convenience, use this to signal a deletion. TODO: more doc
- err := os.Remove(rel.AbsPath(k))
- if err != nil {
- t.Fatal(err)
- }
- } else {
- fake.WriteFileData(k, []byte(v), rel)
- }
- uri := span.URIFromPath(rel.AbsPath(k))
- fh, err := fs.GetFile(ctx, uri)
- if err != nil {
- t.Fatal(err)
- }
- content, err := fh.Read()
- changes[uri] = &fileChange{
- content: content,
- exists: err == nil,
- fileHandle: &closedFile{fh},
- }
- }
- wm, _ := wm.invalidate(ctx, changes)
- checkWorkspaceModule(t, rel, wm, test.finalSource, test.finalModules)
- gotDirs := wm.dirs(ctx, fs)
- checkWorkspaceDirs(t, rel, gotDirs, test.finalDirs)
- }
- })
- }
-}
-
-func checkWorkspaceModule(t *testing.T, rel fake.RelativeTo, got *workspace, wantSource workspaceSource, want []string) {
- t.Helper()
- if got.moduleSource != wantSource {
- t.Errorf("module source = %v, want %v", got.moduleSource, wantSource)
- }
- modules := make(map[span.URI]struct{})
- for k := range got.activeModFiles() {
- modules[k] = struct{}{}
- }
- for _, modPath := range want {
- path := rel.AbsPath(modPath)
- uri := span.URIFromPath(path)
- if _, ok := modules[uri]; !ok {
- t.Errorf("missing module %q", uri)
- }
- delete(modules, uri)
- }
- for remaining := range modules {
- t.Errorf("unexpected module %q", remaining)
- }
-}
-
-func checkWorkspaceDirs(t *testing.T, rel fake.RelativeTo, got []span.URI, want []string) {
- t.Helper()
- gotM := make(map[span.URI]bool)
- for _, dir := range got {
- gotM[dir] = true
- }
- for _, dir := range want {
- path := rel.AbsPath(dir)
- uri := span.URIFromPath(path)
- if !gotM[uri] {
- t.Errorf("missing dir %q", uri)
- }
- delete(gotM, uri)
- }
- for remaining := range gotM {
- t.Errorf("unexpected dir %q", remaining)
- }
-}