Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / mod@v0.3.0 / sumdb / client_test.go
1 // Copyright 2019 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 sumdb
6
7 import (
8         "bytes"
9         "fmt"
10         "strings"
11         "sync"
12         "testing"
13
14         "golang.org/x/mod/sumdb/note"
15         "golang.org/x/mod/sumdb/tlog"
16 )
17
18 const (
19         testName        = "localhost.localdev/sumdb"
20         testVerifierKey = "localhost.localdev/sumdb+00000c67+AcTrnkbUA+TU4heY3hkjiSES/DSQniBqIeQ/YppAUtK6"
21         testSignerKey   = "PRIVATE+KEY+localhost.localdev/sumdb+00000c67+AXu6+oaVaOYuQOFrf1V59JK1owcFlJcHwwXHDfDGxSPk"
22 )
23
24 func TestClientLookup(t *testing.T) {
25         tc := newTestClient(t)
26         tc.mustHaveLatest(1)
27
28         // Basic lookup.
29         tc.mustLookup("rsc.io/sampler", "v1.3.0", "rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=")
30         tc.mustHaveLatest(3)
31
32         // Everything should now be cached, both for the original package and its /go.mod.
33         tc.getOK = false
34         tc.mustLookup("rsc.io/sampler", "v1.3.0", "rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=")
35         tc.mustLookup("rsc.io/sampler", "v1.3.0/go.mod", "rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=")
36         tc.mustHaveLatest(3)
37         tc.getOK = true
38         tc.getTileOK = false // the cache has what we need
39
40         // Lookup with multiple returned lines.
41         tc.mustLookup("rsc.io/quote", "v1.5.2", "rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=\nrsc.io/quote v1.5.2 h2:xyzzy")
42         tc.mustHaveLatest(3)
43
44         // Lookup with need for !-encoding.
45         // rsc.io/Quote is the only record written after rsc.io/samper,
46         // so it is the only one that should need more tiles.
47         tc.getTileOK = true
48         tc.mustLookup("rsc.io/Quote", "v1.5.2", "rsc.io/Quote v1.5.2 h1:uppercase!=")
49         tc.mustHaveLatest(4)
50 }
51
52 func TestClientBadTiles(t *testing.T) {
53         tc := newTestClient(t)
54
55         flipBits := func() {
56                 for url, data := range tc.remote {
57                         if strings.Contains(url, "/tile/") {
58                                 for i := range data {
59                                         data[i] ^= 0x80
60                                 }
61                         }
62                 }
63         }
64
65         // Bad tiles in initial download.
66         tc.mustHaveLatest(1)
67         flipBits()
68         _, err := tc.client.Lookup("rsc.io/sampler", "v1.3.0")
69         tc.mustError(err, "rsc.io/sampler@v1.3.0: initializing sumdb.Client: checking tree#1: downloaded inconsistent tile")
70         flipBits()
71         tc.newClient()
72         tc.mustLookup("rsc.io/sampler", "v1.3.0", "rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=")
73
74         // Bad tiles after initial download.
75         flipBits()
76         _, err = tc.client.Lookup("rsc.io/Quote", "v1.5.2")
77         tc.mustError(err, "rsc.io/Quote@v1.5.2: checking tree#3 against tree#4: downloaded inconsistent tile")
78         flipBits()
79         tc.newClient()
80         tc.mustLookup("rsc.io/Quote", "v1.5.2", "rsc.io/Quote v1.5.2 h1:uppercase!=")
81
82         // Bad starting tree hash looks like bad tiles.
83         tc.newClient()
84         text := tlog.FormatTree(tlog.Tree{N: 1, Hash: tlog.Hash{}})
85         data, err := note.Sign(&note.Note{Text: string(text)}, tc.signer)
86         if err != nil {
87                 tc.t.Fatal(err)
88         }
89         tc.config[testName+"/latest"] = data
90         _, err = tc.client.Lookup("rsc.io/sampler", "v1.3.0")
91         tc.mustError(err, "rsc.io/sampler@v1.3.0: initializing sumdb.Client: checking tree#1: downloaded inconsistent tile")
92 }
93
94 func TestClientFork(t *testing.T) {
95         tc := newTestClient(t)
96         tc2 := tc.fork()
97
98         tc.addRecord("rsc.io/pkg1@v1.5.2", `rsc.io/pkg1 v1.5.2 h1:hash!=
99 `)
100         tc.addRecord("rsc.io/pkg1@v1.5.4", `rsc.io/pkg1 v1.5.4 h1:hash!=
101 `)
102         tc.mustLookup("rsc.io/pkg1", "v1.5.2", "rsc.io/pkg1 v1.5.2 h1:hash!=")
103
104         tc2.addRecord("rsc.io/pkg1@v1.5.3", `rsc.io/pkg1 v1.5.3 h1:hash!=
105 `)
106         tc2.addRecord("rsc.io/pkg1@v1.5.4", `rsc.io/pkg1 v1.5.4 h1:hash!=
107 `)
108         tc2.mustLookup("rsc.io/pkg1", "v1.5.4", "rsc.io/pkg1 v1.5.4 h1:hash!=")
109
110         key := "/lookup/rsc.io/pkg1@v1.5.2"
111         tc2.remote[key] = tc.remote[key]
112         _, err := tc2.client.Lookup("rsc.io/pkg1", "v1.5.2")
113         tc2.mustError(err, ErrSecurity.Error())
114
115         /*
116            SECURITY ERROR
117            go.sum database server misbehavior detected!
118
119            old database:
120                 go.sum database tree!
121                 5
122                 nWzN20+pwMt62p7jbv1/NlN95ePTlHijabv5zO/s36w=
123
124                 — localhost.localdev/sumdb AAAMZ5/2FVAdMH58kmnz/0h299pwyskEbzDzoa2/YaPdhvLya4YWDFQQxu2TQb5GpwAH4NdWnTwuhILafisyf3CNbgg=
125
126            new database:
127                 go.sum database tree
128                 6
129                 wc4SkQt52o5W2nQ8To2ARs+mWuUJjss+sdleoiqxMmM=
130
131                 — localhost.localdev/sumdb AAAMZ6oRNswlEZ6ZZhxrCvgl1MBy+nusq4JU+TG6Fe2NihWLqOzb+y2c2kzRLoCr4tvw9o36ucQEnhc20e4nA4Qc/wc=
132
133            proof of misbehavior:
134                 T7i+H/8ER4nXOiw4Bj0koZOkGjkxoNvlI34GpvhHhQg=
135                 Nsuejv72de9hYNM5bqFv8rv3gm3zJQwv/DT/WNbLDLA=
136                 mOmqqZ1aI/lzS94oq/JSbj7pD8Rv9S+xDyi12BtVSHo=
137                 /7Aw5jVSMM9sFjQhaMg+iiDYPMk6decH7QLOGrL9Lx0=
138         */
139
140         wants := []string{
141                 "SECURITY ERROR",
142                 "go.sum database server misbehavior detected!",
143                 "old database:\n\tgo.sum database tree\n\t5\n",
144                 "— localhost.localdev/sumdb AAAMZ5/2FVAd",
145                 "new database:\n\tgo.sum database tree\n\t6\n",
146                 "— localhost.localdev/sumdb AAAMZ6oRNswl",
147                 "proof of misbehavior:\n\tT7i+H/8ER4nXOiw4Bj0k",
148         }
149         text := tc2.security.String()
150         for _, want := range wants {
151                 if !strings.Contains(text, want) {
152                         t.Fatalf("cannot find %q in security text:\n%s", want, text)
153                 }
154         }
155 }
156
157 func TestClientGONOSUMDB(t *testing.T) {
158         tc := newTestClient(t)
159         tc.client.SetGONOSUMDB("p,*/q")
160         tc.client.Lookup("rsc.io/sampler", "v1.3.0") // initialize before we turn off network
161         tc.getOK = false
162
163         ok := []string{
164                 "abc",
165                 "a/p",
166                 "pq",
167                 "q",
168                 "n/o/p/q",
169         }
170         skip := []string{
171                 "p",
172                 "p/x",
173                 "x/q",
174                 "x/q/z",
175         }
176
177         for _, path := range ok {
178                 _, err := tc.client.Lookup(path, "v1.0.0")
179                 if err == ErrGONOSUMDB {
180                         t.Errorf("Lookup(%q): ErrGONOSUMDB, wanted failed actual lookup", path)
181                 }
182         }
183         for _, path := range skip {
184                 _, err := tc.client.Lookup(path, "v1.0.0")
185                 if err != ErrGONOSUMDB {
186                         t.Errorf("Lookup(%q): %v, wanted ErrGONOSUMDB", path, err)
187                 }
188         }
189 }
190
191 // A testClient is a self-contained client-side testing environment.
192 type testClient struct {
193         t          *testing.T // active test
194         client     *Client    // client being tested
195         tileHeight int        // tile height to use (default 2)
196         getOK      bool       // should tc.GetURL succeed?
197         getTileOK  bool       // should tc.GetURL of tiles succeed?
198         treeSize   int64
199         hashes     []tlog.Hash
200         remote     map[string][]byte
201         signer     note.Signer
202
203         // mu protects config, cache, log, security
204         // during concurrent use of the exported methods
205         // by the client itself (testClient is the Client's ClientOps,
206         // and the Client methods can both read and write these fields).
207         // Unexported methods invoked directly by the test
208         // (for example, addRecord) need not hold the mutex:
209         // for proper test execution those methods should only
210         // be called when the Client is idle and not using its ClientOps.
211         // Not holding the mutex in those methods ensures
212         // that if a mistake is made, go test -race will report it.
213         // (Holding the mutex would eliminate the race report but
214         // not the underlying problem.)
215         // Similarly, the get map is not protected by the mutex,
216         // because the Client methods only read it.
217         mu       sync.Mutex // prot
218         config   map[string][]byte
219         cache    map[string][]byte
220         security bytes.Buffer
221 }
222
223 // newTestClient returns a new testClient that will call t.Fatal on error
224 // and has a few records already available on the remote server.
225 func newTestClient(t *testing.T) *testClient {
226         tc := &testClient{
227                 t:          t,
228                 tileHeight: 2,
229                 getOK:      true,
230                 getTileOK:  true,
231                 config:     make(map[string][]byte),
232                 cache:      make(map[string][]byte),
233                 remote:     make(map[string][]byte),
234         }
235
236         tc.config["key"] = []byte(testVerifierKey + "\n")
237         var err error
238         tc.signer, err = note.NewSigner(testSignerKey)
239         if err != nil {
240                 t.Fatal(err)
241         }
242
243         tc.newClient()
244
245         tc.addRecord("rsc.io/quote@v1.5.2", `rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
246 rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
247 rsc.io/quote v1.5.2 h2:xyzzy
248 `)
249
250         tc.addRecord("golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c", `golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
251 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
252 `)
253         tc.addRecord("rsc.io/sampler@v1.3.0", `rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
254 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
255 `)
256         tc.config[testName+"/latest"] = tc.signTree(1)
257
258         tc.addRecord("rsc.io/!quote@v1.5.2", `rsc.io/Quote v1.5.2 h1:uppercase!=
259 `)
260         return tc
261 }
262
263 // newClient resets the Client associated with tc.
264 // This clears any in-memory cache from the Client
265 // but not tc's on-disk cache.
266 func (tc *testClient) newClient() {
267         tc.client = NewClient(tc)
268         tc.client.SetTileHeight(tc.tileHeight)
269 }
270
271 // mustLookup does a lookup for path@vers and checks that the lines that come back match want.
272 func (tc *testClient) mustLookup(path, vers, want string) {
273         tc.t.Helper()
274         lines, err := tc.client.Lookup(path, vers)
275         if err != nil {
276                 tc.t.Fatal(err)
277         }
278         if strings.Join(lines, "\n") != want {
279                 tc.t.Fatalf("Lookup(%q, %q):\n\t%s\nwant:\n\t%s", path, vers, strings.Join(lines, "\n\t"), strings.Replace(want, "\n", "\n\t", -1))
280         }
281 }
282
283 // mustHaveLatest checks that the on-disk configuration
284 // for latest is a tree of size n.
285 func (tc *testClient) mustHaveLatest(n int64) {
286         tc.t.Helper()
287
288         latest := tc.config[testName+"/latest"]
289         lines := strings.Split(string(latest), "\n")
290         if len(lines) < 2 || lines[1] != fmt.Sprint(n) {
291                 tc.t.Fatalf("/latest should have tree %d, but has:\n%s", n, latest)
292         }
293 }
294
295 // mustError checks that err's error string contains the text.
296 func (tc *testClient) mustError(err error, text string) {
297         tc.t.Helper()
298         if err == nil || !strings.Contains(err.Error(), text) {
299                 tc.t.Fatalf("err = %v, want %q", err, text)
300         }
301 }
302
303 // fork returns a copy of tc.
304 // Changes made to the new copy or to tc are not reflected in the other.
305 func (tc *testClient) fork() *testClient {
306         tc2 := &testClient{
307                 t:          tc.t,
308                 getOK:      tc.getOK,
309                 getTileOK:  tc.getTileOK,
310                 tileHeight: tc.tileHeight,
311                 treeSize:   tc.treeSize,
312                 hashes:     append([]tlog.Hash{}, tc.hashes...),
313                 signer:     tc.signer,
314                 config:     copyMap(tc.config),
315                 cache:      copyMap(tc.cache),
316                 remote:     copyMap(tc.remote),
317         }
318         tc2.newClient()
319         return tc2
320 }
321
322 func copyMap(m map[string][]byte) map[string][]byte {
323         m2 := make(map[string][]byte)
324         for k, v := range m {
325                 m2[k] = v
326         }
327         return m2
328 }
329
330 // ReadHashes is tc's implementation of tlog.HashReader, for use with
331 // tlog.TreeHash and so on.
332 func (tc *testClient) ReadHashes(indexes []int64) ([]tlog.Hash, error) {
333         var list []tlog.Hash
334         for _, id := range indexes {
335                 list = append(list, tc.hashes[id])
336         }
337         return list, nil
338 }
339
340 // addRecord adds a log record using the given (!-encoded) key and data.
341 func (tc *testClient) addRecord(key, data string) {
342         tc.t.Helper()
343
344         // Create record, add hashes to log tree.
345         id := tc.treeSize
346         tc.treeSize++
347         rec, err := tlog.FormatRecord(id, []byte(data))
348         if err != nil {
349                 tc.t.Fatal(err)
350         }
351         hashes, err := tlog.StoredHashesForRecordHash(id, tlog.RecordHash([]byte(data)), tc)
352         if err != nil {
353                 tc.t.Fatal(err)
354         }
355         tc.hashes = append(tc.hashes, hashes...)
356
357         // Create lookup result.
358         tc.remote["/lookup/"+key] = append(rec, tc.signTree(tc.treeSize)...)
359
360         // Create new tiles.
361         tiles := tlog.NewTiles(tc.tileHeight, id, tc.treeSize)
362         for _, tile := range tiles {
363                 data, err := tlog.ReadTileData(tile, tc)
364                 if err != nil {
365                         tc.t.Fatal(err)
366                 }
367                 tc.remote["/"+tile.Path()] = data
368                 // TODO delete old partial tiles
369         }
370 }
371
372 // signTree returns the signed head for the tree of the given size.
373 func (tc *testClient) signTree(size int64) []byte {
374         h, err := tlog.TreeHash(size, tc)
375         if err != nil {
376                 tc.t.Fatal(err)
377         }
378         text := tlog.FormatTree(tlog.Tree{N: size, Hash: h})
379         data, err := note.Sign(&note.Note{Text: string(text)}, tc.signer)
380         if err != nil {
381                 tc.t.Fatal(err)
382         }
383         return data
384 }
385
386 // ReadRemote is for tc's implementation of Client.
387 func (tc *testClient) ReadRemote(path string) ([]byte, error) {
388         // No mutex here because only the Client should be running
389         // and the Client cannot change tc.get.
390         if !tc.getOK {
391                 return nil, fmt.Errorf("disallowed remote read %s", path)
392         }
393         if strings.Contains(path, "/tile/") && !tc.getTileOK {
394                 return nil, fmt.Errorf("disallowed remote tile read %s", path)
395         }
396
397         data, ok := tc.remote[path]
398         if !ok {
399                 return nil, fmt.Errorf("no remote path %s", path)
400         }
401         return data, nil
402 }
403
404 // ReadConfig is for tc's implementation of Client.
405 func (tc *testClient) ReadConfig(file string) ([]byte, error) {
406         tc.mu.Lock()
407         defer tc.mu.Unlock()
408
409         data, ok := tc.config[file]
410         if !ok {
411                 return nil, fmt.Errorf("no config %s", file)
412         }
413         return data, nil
414 }
415
416 // WriteConfig is for tc's implementation of Client.
417 func (tc *testClient) WriteConfig(file string, old, new []byte) error {
418         tc.mu.Lock()
419         defer tc.mu.Unlock()
420
421         data := tc.config[file]
422         if !bytes.Equal(old, data) {
423                 return ErrWriteConflict
424         }
425         tc.config[file] = new
426         return nil
427 }
428
429 // ReadCache is for tc's implementation of Client.
430 func (tc *testClient) ReadCache(file string) ([]byte, error) {
431         tc.mu.Lock()
432         defer tc.mu.Unlock()
433
434         data, ok := tc.cache[file]
435         if !ok {
436                 return nil, fmt.Errorf("no cache %s", file)
437         }
438         return data, nil
439 }
440
441 // WriteCache is for tc's implementation of Client.
442 func (tc *testClient) WriteCache(file string, data []byte) {
443         tc.mu.Lock()
444         defer tc.mu.Unlock()
445
446         tc.cache[file] = data
447 }
448
449 // Log is for tc's implementation of Client.
450 func (tc *testClient) Log(msg string) {
451         tc.t.Log(msg)
452 }
453
454 // SecurityError is for tc's implementation of Client.
455 func (tc *testClient) SecurityError(msg string) {
456         tc.mu.Lock()
457         defer tc.mu.Unlock()
458
459         fmt.Fprintf(&tc.security, "%s\n", strings.TrimRight(msg, "\n"))
460 }