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  }