golang.org/x/tools/gopls@v0.15.3/internal/filecache/filecache_test.go (about)

     1  // Copyright 2022 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 filecache_test
     6  
     7  // This file defines tests of the API of the filecache package.
     8  //
     9  // Some properties (e.g. garbage collection) cannot be exercised
    10  // through the API, so this test does not attempt to do so.
    11  
    12  import (
    13  	"bytes"
    14  	cryptorand "crypto/rand"
    15  	"fmt"
    16  	"log"
    17  	mathrand "math/rand"
    18  	"os"
    19  	"os/exec"
    20  	"strconv"
    21  	"strings"
    22  	"testing"
    23  
    24  	"golang.org/x/sync/errgroup"
    25  	"golang.org/x/tools/gopls/internal/filecache"
    26  	"golang.org/x/tools/internal/testenv"
    27  )
    28  
    29  func TestBasics(t *testing.T) {
    30  	const kind = "TestBasics"
    31  	key := uniqueKey() // never used before
    32  	value := []byte("hello")
    33  
    34  	// Get of a never-seen key returns not found.
    35  	if _, err := filecache.Get(kind, key); err != filecache.ErrNotFound {
    36  		if strings.Contains(err.Error(), "operation not supported") ||
    37  			strings.Contains(err.Error(), "not implemented") {
    38  			t.Skipf("skipping: %v", err)
    39  		}
    40  		t.Errorf("Get of random key returned err=%q, want not found", err)
    41  	}
    42  
    43  	// Set of a never-seen key and a small value succeeds.
    44  	if err := filecache.Set(kind, key, value); err != nil {
    45  		t.Errorf("Set failed: %v", err)
    46  	}
    47  
    48  	// Get of the key returns a copy of the value.
    49  	if got, err := filecache.Get(kind, key); err != nil {
    50  		t.Errorf("Get after Set failed: %v", err)
    51  	} else if string(got) != string(value) {
    52  		t.Errorf("Get after Set returned different value: got %q, want %q", got, value)
    53  	}
    54  
    55  	// The kind is effectively part of the key.
    56  	if _, err := filecache.Get("different-kind", key); err != filecache.ErrNotFound {
    57  		t.Errorf("Get with wrong kind returned err=%q, want not found", err)
    58  	}
    59  }
    60  
    61  // TestConcurrency exercises concurrent access to the same entry.
    62  func TestConcurrency(t *testing.T) {
    63  	if os.Getenv("GO_BUILDER_NAME") == "plan9-arm" {
    64  		t.Skip(`skipping on plan9-arm builder due to golang/go#58748: failing with 'mount rpc error'`)
    65  	}
    66  	const kind = "TestConcurrency"
    67  	key := uniqueKey()
    68  	const N = 100 // concurrency level
    69  
    70  	// Construct N distinct values, each larger
    71  	// than a typical 4KB OS file buffer page.
    72  	var values [N][8192]byte
    73  	for i := range values {
    74  		if _, err := mathrand.Read(values[i][:]); err != nil {
    75  			t.Fatalf("rand: %v", err)
    76  		}
    77  	}
    78  
    79  	// get calls Get and verifies that the cache entry
    80  	// matches one of the values passed to Set.
    81  	get := func(mustBeFound bool) error {
    82  		got, err := filecache.Get(kind, key)
    83  		if err != nil {
    84  			if err == filecache.ErrNotFound && !mustBeFound {
    85  				return nil // not found
    86  			}
    87  			return err
    88  		}
    89  		for _, want := range values {
    90  			if bytes.Equal(want[:], got) {
    91  				return nil // a match
    92  			}
    93  		}
    94  		return fmt.Errorf("Get returned a value that was never Set")
    95  	}
    96  
    97  	// Perform N concurrent calls to Set and Get.
    98  	// All sets must succeed.
    99  	// All gets must return nothing, or one of the Set values;
   100  	// there is no third possibility.
   101  	var group errgroup.Group
   102  	for i := range values {
   103  		i := i
   104  		group.Go(func() error { return filecache.Set(kind, key, values[i][:]) })
   105  		group.Go(func() error { return get(false) })
   106  	}
   107  	if err := group.Wait(); err != nil {
   108  		if strings.Contains(err.Error(), "operation not supported") ||
   109  			strings.Contains(err.Error(), "not implemented") {
   110  			t.Skipf("skipping: %v", err)
   111  		}
   112  		t.Fatal(err)
   113  	}
   114  
   115  	// A final Get must report one of the values that was Set.
   116  	if err := get(true); err != nil {
   117  		t.Fatalf("final Get failed: %v", err)
   118  	}
   119  }
   120  
   121  const (
   122  	testIPCKind   = "TestIPC"
   123  	testIPCValueA = "hello"
   124  	testIPCValueB = "world"
   125  )
   126  
   127  // TestIPC exercises interprocess communication through the cache.
   128  // It calls Set(A) in the parent, { Get(A); Set(B) } in the child
   129  // process, then Get(B) in the parent.
   130  func TestIPC(t *testing.T) {
   131  	testenv.NeedsExec(t)
   132  
   133  	keyA := uniqueKey()
   134  	keyB := uniqueKey()
   135  	value := []byte(testIPCValueA)
   136  
   137  	// Set keyA.
   138  	if err := filecache.Set(testIPCKind, keyA, value); err != nil {
   139  		if strings.Contains(err.Error(), "operation not supported") {
   140  			t.Skipf("skipping: %v", err)
   141  		}
   142  		t.Fatalf("Set: %v", err)
   143  	}
   144  
   145  	// Call ipcChild in a child process,
   146  	// passing it the keys in the environment
   147  	// (quoted, to avoid NUL termination of C strings).
   148  	// It will Get(A) then Set(B).
   149  	cmd := exec.Command(os.Args[0], os.Args[1:]...)
   150  	cmd.Env = append(os.Environ(),
   151  		"ENTRYPOINT=ipcChild",
   152  		fmt.Sprintf("KEYA=%q", keyA),
   153  		fmt.Sprintf("KEYB=%q", keyB))
   154  	cmd.Stdout = os.Stderr
   155  	cmd.Stderr = os.Stderr
   156  	if err := cmd.Run(); err != nil {
   157  		t.Fatal(err)
   158  	}
   159  
   160  	// Verify keyB.
   161  	got, err := filecache.Get(testIPCKind, keyB)
   162  	if err != nil {
   163  		t.Fatal(err)
   164  	}
   165  	if string(got) != "world" {
   166  		t.Fatalf("Get(keyB) = %q, want %q", got, "world")
   167  	}
   168  }
   169  
   170  // We define our own main function so that portions of
   171  // some tests can run in a separate (child) process.
   172  func TestMain(m *testing.M) {
   173  	switch os.Getenv("ENTRYPOINT") {
   174  	case "ipcChild":
   175  		ipcChild()
   176  	default:
   177  		os.Exit(m.Run())
   178  	}
   179  }
   180  
   181  // ipcChild is the portion of TestIPC that runs in a child process.
   182  func ipcChild() {
   183  	getenv := func(name string) (key [32]byte) {
   184  		s, _ := strconv.Unquote(os.Getenv(name))
   185  		copy(key[:], []byte(s))
   186  		return
   187  	}
   188  
   189  	// Verify key A.
   190  	got, err := filecache.Get(testIPCKind, getenv("KEYA"))
   191  	if err != nil || string(got) != testIPCValueA {
   192  		log.Fatalf("child: Get(key) = %q, %v; want %q", got, err, testIPCValueA)
   193  	}
   194  
   195  	// Set key B.
   196  	if err := filecache.Set(testIPCKind, getenv("KEYB"), []byte(testIPCValueB)); err != nil {
   197  		log.Fatalf("child: Set(keyB) failed: %v", err)
   198  	}
   199  }
   200  
   201  // uniqueKey returns a key that has never been used before.
   202  func uniqueKey() (key [32]byte) {
   203  	if _, err := cryptorand.Read(key[:]); err != nil {
   204  		log.Fatalf("rand: %v", err)
   205  	}
   206  	return
   207  }
   208  
   209  func BenchmarkUncontendedGet(b *testing.B) {
   210  	const kind = "BenchmarkUncontendedGet"
   211  	key := uniqueKey()
   212  
   213  	var value [8192]byte
   214  	if _, err := mathrand.Read(value[:]); err != nil {
   215  		b.Fatalf("rand: %v", err)
   216  	}
   217  	if err := filecache.Set(kind, key, value[:]); err != nil {
   218  		b.Fatal(err)
   219  	}
   220  	b.ResetTimer()
   221  	b.SetBytes(int64(len(value)))
   222  
   223  	var group errgroup.Group
   224  	group.SetLimit(50)
   225  	for i := 0; i < b.N; i++ {
   226  		group.Go(func() error {
   227  			_, err := filecache.Get(kind, key)
   228  			return err
   229  		})
   230  	}
   231  	if err := group.Wait(); err != nil {
   232  		b.Fatal(err)
   233  	}
   234  }
   235  
   236  // These two benchmarks are asymmetric: the one for Get imposes a
   237  // modest bound on concurrency (50) whereas the one for Set imposes a
   238  // much higher concurrency (1000) to test the implementation's
   239  // self-imposed bound.
   240  
   241  func BenchmarkUncontendedSet(b *testing.B) {
   242  	const kind = "BenchmarkUncontendedSet"
   243  	key := uniqueKey()
   244  	var value [8192]byte
   245  
   246  	const P = 1000 // parallelism
   247  	b.SetBytes(P * int64(len(value)))
   248  
   249  	for i := 0; i < b.N; i++ {
   250  		// Perform P concurrent calls to Set. All must succeed.
   251  		var group errgroup.Group
   252  		for range [P]bool{} {
   253  			group.Go(func() error {
   254  				return filecache.Set(kind, key, value[:])
   255  			})
   256  		}
   257  		if err := group.Wait(); err != nil {
   258  			if strings.Contains(err.Error(), "operation not supported") ||
   259  				strings.Contains(err.Error(), "not implemented") {
   260  				b.Skipf("skipping: %v", err)
   261  			}
   262  			b.Fatal(err)
   263  		}
   264  	}
   265  }