golang.org/x/tools/gopls@v0.15.3/internal/cache/parse_cache_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 "fmt" 10 "go/token" 11 "math/bits" 12 "testing" 13 "time" 14 15 "golang.org/x/tools/gopls/internal/file" 16 "golang.org/x/tools/gopls/internal/protocol" 17 ) 18 19 func skipIfNoParseCache(t *testing.T) { 20 if bits.UintSize == 32 { 21 t.Skip("the parse cache is not supported on 32-bit systems") 22 } 23 } 24 25 func TestParseCache(t *testing.T) { 26 skipIfNoParseCache(t) 27 28 ctx := context.Background() 29 uri := protocol.DocumentURI("file:///myfile") 30 fh := makeFakeFileHandle(uri, []byte("package p\n\nconst _ = \"foo\"")) 31 fset := token.NewFileSet() 32 33 cache := newParseCache(0) 34 pgfs1, err := cache.parseFiles(ctx, fset, ParseFull, false, fh) 35 if err != nil { 36 t.Fatal(err) 37 } 38 pgf1 := pgfs1[0] 39 pgfs2, err := cache.parseFiles(ctx, fset, ParseFull, false, fh) 40 pgf2 := pgfs2[0] 41 if err != nil { 42 t.Fatal(err) 43 } 44 if pgf1 != pgf2 { 45 t.Errorf("parseFiles(%q): unexpected cache miss on repeated call", uri) 46 } 47 48 // Fill up the cache with other files, but don't evict the file above. 49 cache.gcOnce() 50 files := []file.Handle{fh} 51 files = append(files, dummyFileHandles(parseCacheMinFiles-1)...) 52 53 pgfs3, err := cache.parseFiles(ctx, fset, ParseFull, false, files...) 54 if err != nil { 55 t.Fatal(err) 56 } 57 pgf3 := pgfs3[0] 58 if pgf3 != pgf1 { 59 t.Errorf("parseFiles(%q, ...): unexpected cache miss", uri) 60 } 61 if pgf3.Tok.Base() != pgf1.Tok.Base() || pgf3.Tok.Size() != pgf1.Tok.Size() { 62 t.Errorf("parseFiles(%q, ...): result.Tok has base: %d, size: %d, want (%d, %d)", uri, pgf3.Tok.Base(), pgf3.Tok.Size(), pgf1.Tok.Base(), pgf1.Tok.Size()) 63 } 64 if tok := fset.File(token.Pos(pgf3.Tok.Base())); tok != pgf3.Tok { 65 t.Errorf("parseFiles(%q, ...): result.Tok not contained in FileSet", uri) 66 } 67 68 // Now overwrite the cache, after which we should get new results. 69 cache.gcOnce() 70 files = dummyFileHandles(parseCacheMinFiles) 71 _, err = cache.parseFiles(ctx, fset, ParseFull, false, files...) 72 if err != nil { 73 t.Fatal(err) 74 } 75 // force a GC, which should collect the recently parsed files 76 cache.gcOnce() 77 pgfs4, err := cache.parseFiles(ctx, fset, ParseFull, false, fh) 78 if err != nil { 79 t.Fatal(err) 80 } 81 if pgfs4[0] == pgf1 { 82 t.Errorf("parseFiles(%q): unexpected cache hit after overwriting cache", uri) 83 } 84 } 85 86 func TestParseCache_Reparsing(t *testing.T) { 87 skipIfNoParseCache(t) 88 89 defer func(padding int) { 90 parsePadding = padding 91 }(parsePadding) 92 parsePadding = 0 93 94 files := dummyFileHandles(parseCacheMinFiles) 95 danglingSelector := []byte("package p\nfunc _() {\n\tx.\n}") 96 files = append(files, makeFakeFileHandle("file:///bad1", danglingSelector)) 97 files = append(files, makeFakeFileHandle("file:///bad2", danglingSelector)) 98 99 // Parsing should succeed even though we overflow the padding. 100 cache := newParseCache(0) 101 _, err := cache.parseFiles(context.Background(), token.NewFileSet(), ParseFull, false, files...) 102 if err != nil { 103 t.Fatal(err) 104 } 105 } 106 107 // Re-parsing the first file should not panic. 108 func TestParseCache_Issue59097(t *testing.T) { 109 skipIfNoParseCache(t) 110 111 defer func(padding int) { 112 parsePadding = padding 113 }(parsePadding) 114 parsePadding = 0 115 116 danglingSelector := []byte("package p\nfunc _() {\n\tx.\n}") 117 files := []file.Handle{makeFakeFileHandle("file:///bad", danglingSelector)} 118 119 // Parsing should succeed even though we overflow the padding. 120 cache := newParseCache(0) 121 _, err := cache.parseFiles(context.Background(), token.NewFileSet(), ParseFull, false, files...) 122 if err != nil { 123 t.Fatal(err) 124 } 125 } 126 127 func TestParseCache_TimeEviction(t *testing.T) { 128 skipIfNoParseCache(t) 129 130 ctx := context.Background() 131 fset := token.NewFileSet() 132 uri := protocol.DocumentURI("file:///myfile") 133 fh := makeFakeFileHandle(uri, []byte("package p\n\nconst _ = \"foo\"")) 134 135 const gcDuration = 10 * time.Millisecond 136 cache := newParseCache(gcDuration) 137 cache.stop() // we'll manage GC manually, for testing. 138 139 pgfs0, err := cache.parseFiles(ctx, fset, ParseFull, false, fh, fh) 140 if err != nil { 141 t.Fatal(err) 142 } 143 144 files := dummyFileHandles(parseCacheMinFiles) 145 _, err = cache.parseFiles(ctx, fset, ParseFull, false, files...) 146 if err != nil { 147 t.Fatal(err) 148 } 149 150 // Even after filling up the 'min' files, we get a cache hit for our original file. 151 pgfs1, err := cache.parseFiles(ctx, fset, ParseFull, false, fh, fh) 152 if err != nil { 153 t.Fatal(err) 154 } 155 156 if pgfs0[0] != pgfs1[0] { 157 t.Errorf("before GC, got unexpected cache miss") 158 } 159 160 // But after GC, we get a cache miss. 161 _, err = cache.parseFiles(ctx, fset, ParseFull, false, files...) // mark dummy files as newer 162 if err != nil { 163 t.Fatal(err) 164 } 165 time.Sleep(gcDuration) 166 cache.gcOnce() 167 168 pgfs2, err := cache.parseFiles(ctx, fset, ParseFull, false, fh, fh) 169 if err != nil { 170 t.Fatal(err) 171 } 172 173 if pgfs0[0] == pgfs2[0] { 174 t.Errorf("after GC, got unexpected cache hit for %s", pgfs0[0].URI) 175 } 176 } 177 178 func TestParseCache_Duplicates(t *testing.T) { 179 skipIfNoParseCache(t) 180 181 ctx := context.Background() 182 uri := protocol.DocumentURI("file:///myfile") 183 fh := makeFakeFileHandle(uri, []byte("package p\n\nconst _ = \"foo\"")) 184 185 cache := newParseCache(0) 186 pgfs, err := cache.parseFiles(ctx, token.NewFileSet(), ParseFull, false, fh, fh) 187 if err != nil { 188 t.Fatal(err) 189 } 190 if pgfs[0] != pgfs[1] { 191 t.Errorf("parseFiles(fh, fh): = [%p, %p], want duplicate files", pgfs[0].File, pgfs[1].File) 192 } 193 } 194 195 func dummyFileHandles(n int) []file.Handle { 196 var fhs []file.Handle 197 for i := 0; i < n; i++ { 198 uri := protocol.DocumentURI(fmt.Sprintf("file:///_%d", i)) 199 src := []byte(fmt.Sprintf("package p\nvar _ = %d", i)) 200 fhs = append(fhs, makeFakeFileHandle(uri, src)) 201 } 202 return fhs 203 } 204 205 func makeFakeFileHandle(uri protocol.DocumentURI, src []byte) fakeFileHandle { 206 return fakeFileHandle{ 207 uri: uri, 208 data: src, 209 hash: file.HashOf(src), 210 } 211 } 212 213 type fakeFileHandle struct { 214 file.Handle 215 uri protocol.DocumentURI 216 data []byte 217 hash file.Hash 218 } 219 220 func (h fakeFileHandle) URI() protocol.DocumentURI { 221 return h.uri 222 } 223 224 func (h fakeFileHandle) Content() ([]byte, error) { 225 return h.data, nil 226 } 227 228 func (h fakeFileHandle) Identity() file.Identity { 229 return file.Identity{ 230 URI: h.uri, 231 Hash: h.hash, 232 } 233 }