golang.org/x/tools/gopls@v0.15.3/internal/test/integration/fake/workdir_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 fake 6 7 import ( 8 "context" 9 "os" 10 "sync" 11 "testing" 12 13 "github.com/google/go-cmp/cmp" 14 "golang.org/x/tools/gopls/internal/protocol" 15 ) 16 17 const sharedData = ` 18 -- go.mod -- 19 go 1.12 20 -- nested/README.md -- 21 Hello World! 22 ` 23 24 // newWorkdir sets up a temporary Workdir with the given txtar-encoded content. 25 // It also configures an eventBuffer to receive file event notifications. These 26 // notifications are sent synchronously for each operation, such that once a 27 // workdir file operation has returned the caller can expect that any relevant 28 // file notifications are present in the buffer. 29 // 30 // It is the caller's responsibility to call the returned cleanup function. 31 func newWorkdir(t *testing.T, txt string) (*Workdir, *eventBuffer, func()) { 32 t.Helper() 33 34 tmpdir, err := os.MkdirTemp("", "goplstest-workdir-") 35 if err != nil { 36 t.Fatal(err) 37 } 38 wd, err := NewWorkdir(tmpdir, UnpackTxt(txt)) 39 if err != nil { 40 t.Fatal(err) 41 } 42 cleanup := func() { 43 if err := os.RemoveAll(tmpdir); err != nil { 44 t.Error(err) 45 } 46 } 47 48 buf := new(eventBuffer) 49 wd.AddWatcher(buf.onEvents) 50 return wd, buf, cleanup 51 } 52 53 // eventBuffer collects events from a file watcher. 54 type eventBuffer struct { 55 mu sync.Mutex 56 events []protocol.FileEvent 57 } 58 59 // onEvents collects adds events to the buffer; to be used with Workdir.AddWatcher. 60 func (c *eventBuffer) onEvents(_ context.Context, events []protocol.FileEvent) { 61 c.mu.Lock() 62 defer c.mu.Unlock() 63 64 c.events = append(c.events, events...) 65 } 66 67 // take empties the buffer, returning its previous contents. 68 func (c *eventBuffer) take() []protocol.FileEvent { 69 c.mu.Lock() 70 defer c.mu.Unlock() 71 72 evts := c.events 73 c.events = nil 74 return evts 75 } 76 77 func TestWorkdir_ReadFile(t *testing.T) { 78 wd, _, cleanup := newWorkdir(t, sharedData) 79 defer cleanup() 80 81 got, err := wd.ReadFile("nested/README.md") 82 if err != nil { 83 t.Fatal(err) 84 } 85 want := "Hello World!\n" 86 if got := string(got); got != want { 87 t.Errorf("reading workdir file, got %q, want %q", got, want) 88 } 89 } 90 91 func TestWorkdir_WriteFile(t *testing.T) { 92 wd, events, cleanup := newWorkdir(t, sharedData) 93 defer cleanup() 94 ctx := context.Background() 95 96 tests := []struct { 97 path string 98 wantType protocol.FileChangeType 99 }{ 100 {"data.txt", protocol.Created}, 101 {"nested/README.md", protocol.Changed}, 102 } 103 104 for _, test := range tests { 105 if err := wd.WriteFile(ctx, test.path, "42"); err != nil { 106 t.Fatal(err) 107 } 108 es := events.take() 109 if got := len(es); got != 1 { 110 t.Fatalf("len(events) = %d, want 1", got) 111 } 112 path := wd.URIToPath(es[0].URI) 113 if path != test.path { 114 t.Errorf("event path = %q, want %q", path, test.path) 115 } 116 if es[0].Type != test.wantType { 117 t.Errorf("event type = %v, want %v", es[0].Type, test.wantType) 118 } 119 got, err := wd.ReadFile(test.path) 120 if err != nil { 121 t.Fatal(err) 122 } 123 want := "42" 124 if got := string(got); got != want { 125 t.Errorf("ws.ReadFile(%q) = %q, want %q", test.path, got, want) 126 } 127 } 128 } 129 130 // Test for file notifications following file operations. 131 func TestWorkdir_FileWatching(t *testing.T) { 132 wd, events, cleanup := newWorkdir(t, "") 133 defer cleanup() 134 ctx := context.Background() 135 136 must := func(err error) { 137 if err != nil { 138 t.Fatal(err) 139 } 140 } 141 142 type changeMap map[string]protocol.FileChangeType 143 checkEvent := func(wantChanges changeMap) { 144 gotChanges := make(changeMap) 145 for _, e := range events.take() { 146 gotChanges[wd.URIToPath(e.URI)] = e.Type 147 } 148 if diff := cmp.Diff(wantChanges, gotChanges); diff != "" { 149 t.Errorf("mismatching file events (-want +got):\n%s", diff) 150 } 151 } 152 153 must(wd.WriteFile(ctx, "foo.go", "package foo")) 154 checkEvent(changeMap{"foo.go": protocol.Created}) 155 156 must(wd.RenameFile(ctx, "foo.go", "bar.go")) 157 checkEvent(changeMap{"foo.go": protocol.Deleted, "bar.go": protocol.Created}) 158 159 must(wd.RemoveFile(ctx, "bar.go")) 160 checkEvent(changeMap{"bar.go": protocol.Deleted}) 161 } 162 163 func TestWorkdir_CheckForFileChanges(t *testing.T) { 164 t.Skip("broken on darwin-amd64-10_12") 165 wd, events, cleanup := newWorkdir(t, sharedData) 166 defer cleanup() 167 ctx := context.Background() 168 169 checkChange := func(wantPath string, wantType protocol.FileChangeType) { 170 if err := wd.CheckForFileChanges(ctx); err != nil { 171 t.Fatal(err) 172 } 173 ev := events.take() 174 if len(ev) == 0 { 175 t.Fatal("no file events received") 176 } 177 gotEvt := ev[0] 178 gotPath := wd.URIToPath(gotEvt.URI) 179 // Only check relative path and Type 180 if gotPath != wantPath || gotEvt.Type != wantType { 181 t.Errorf("file events: got %v, want {Path: %s, Type: %v}", gotEvt, wantPath, wantType) 182 } 183 } 184 // Sleep some positive amount of time to ensure a distinct mtime. 185 if err := writeFileData("go.mod", []byte("module foo.test\n"), wd.RelativeTo); err != nil { 186 t.Fatal(err) 187 } 188 checkChange("go.mod", protocol.Changed) 189 if err := writeFileData("newFile", []byte("something"), wd.RelativeTo); err != nil { 190 t.Fatal(err) 191 } 192 checkChange("newFile", protocol.Created) 193 fp := wd.AbsPath("newFile") 194 if err := os.Remove(fp); err != nil { 195 t.Fatal(err) 196 } 197 checkChange("newFile", protocol.Deleted) 198 } 199 200 func TestSplitModuleVersionPath(t *testing.T) { 201 tests := []struct { 202 path string 203 wantModule, wantVersion, wantSuffix string 204 }{ 205 {"foo.com@v1.2.3/bar", "foo.com", "v1.2.3", "bar"}, 206 {"foo.com/module@v1.2.3/bar", "foo.com/module", "v1.2.3", "bar"}, 207 {"foo.com@v1.2.3", "foo.com", "v1.2.3", ""}, 208 {"std@v1.14.0", "std", "v1.14.0", ""}, 209 {"another/module/path", "another/module/path", "", ""}, 210 } 211 212 for _, test := range tests { 213 module, version, suffix := splitModuleVersionPath(test.path) 214 if module != test.wantModule || version != test.wantVersion || suffix != test.wantSuffix { 215 t.Errorf("splitModuleVersionPath(%q) =\n\t(%q, %q, %q)\nwant\n\t(%q, %q, %q)", 216 test.path, module, version, suffix, test.wantModule, test.wantVersion, test.wantSuffix) 217 } 218 } 219 }