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  }