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 }