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
diff --git a/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/mod@v0.3.0/sumdb/client_test.go b/.config/coc/extensions/coc-go-data/tools/pkg/mod/golang.org/x/mod@v0.3.0/sumdb/client_test.go
new file mode 100644 (file)
index 0000000..0f3c481
--- /dev/null
@@ -0,0 +1,460 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package sumdb
+
+import (
+       "bytes"
+       "fmt"
+       "strings"
+       "sync"
+       "testing"
+
+       "golang.org/x/mod/sumdb/note"
+       "golang.org/x/mod/sumdb/tlog"
+)
+
+const (
+       testName        = "localhost.localdev/sumdb"
+       testVerifierKey = "localhost.localdev/sumdb+00000c67+AcTrnkbUA+TU4heY3hkjiSES/DSQniBqIeQ/YppAUtK6"
+       testSignerKey   = "PRIVATE+KEY+localhost.localdev/sumdb+00000c67+AXu6+oaVaOYuQOFrf1V59JK1owcFlJcHwwXHDfDGxSPk"
+)
+
+func TestClientLookup(t *testing.T) {
+       tc := newTestClient(t)
+       tc.mustHaveLatest(1)
+
+       // Basic lookup.
+       tc.mustLookup("rsc.io/sampler", "v1.3.0", "rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=")
+       tc.mustHaveLatest(3)
+
+       // Everything should now be cached, both for the original package and its /go.mod.
+       tc.getOK = false
+       tc.mustLookup("rsc.io/sampler", "v1.3.0", "rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=")
+       tc.mustLookup("rsc.io/sampler", "v1.3.0/go.mod", "rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=")
+       tc.mustHaveLatest(3)
+       tc.getOK = true
+       tc.getTileOK = false // the cache has what we need
+
+       // Lookup with multiple returned lines.
+       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")
+       tc.mustHaveLatest(3)
+
+       // Lookup with need for !-encoding.
+       // rsc.io/Quote is the only record written after rsc.io/samper,
+       // so it is the only one that should need more tiles.
+       tc.getTileOK = true
+       tc.mustLookup("rsc.io/Quote", "v1.5.2", "rsc.io/Quote v1.5.2 h1:uppercase!=")
+       tc.mustHaveLatest(4)
+}
+
+func TestClientBadTiles(t *testing.T) {
+       tc := newTestClient(t)
+
+       flipBits := func() {
+               for url, data := range tc.remote {
+                       if strings.Contains(url, "/tile/") {
+                               for i := range data {
+                                       data[i] ^= 0x80
+                               }
+                       }
+               }
+       }
+
+       // Bad tiles in initial download.
+       tc.mustHaveLatest(1)
+       flipBits()
+       _, err := tc.client.Lookup("rsc.io/sampler", "v1.3.0")
+       tc.mustError(err, "rsc.io/sampler@v1.3.0: initializing sumdb.Client: checking tree#1: downloaded inconsistent tile")
+       flipBits()
+       tc.newClient()
+       tc.mustLookup("rsc.io/sampler", "v1.3.0", "rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=")
+
+       // Bad tiles after initial download.
+       flipBits()
+       _, err = tc.client.Lookup("rsc.io/Quote", "v1.5.2")
+       tc.mustError(err, "rsc.io/Quote@v1.5.2: checking tree#3 against tree#4: downloaded inconsistent tile")
+       flipBits()
+       tc.newClient()
+       tc.mustLookup("rsc.io/Quote", "v1.5.2", "rsc.io/Quote v1.5.2 h1:uppercase!=")
+
+       // Bad starting tree hash looks like bad tiles.
+       tc.newClient()
+       text := tlog.FormatTree(tlog.Tree{N: 1, Hash: tlog.Hash{}})
+       data, err := note.Sign(&note.Note{Text: string(text)}, tc.signer)
+       if err != nil {
+               tc.t.Fatal(err)
+       }
+       tc.config[testName+"/latest"] = data
+       _, err = tc.client.Lookup("rsc.io/sampler", "v1.3.0")
+       tc.mustError(err, "rsc.io/sampler@v1.3.0: initializing sumdb.Client: checking tree#1: downloaded inconsistent tile")
+}
+
+func TestClientFork(t *testing.T) {
+       tc := newTestClient(t)
+       tc2 := tc.fork()
+
+       tc.addRecord("rsc.io/pkg1@v1.5.2", `rsc.io/pkg1 v1.5.2 h1:hash!=
+`)
+       tc.addRecord("rsc.io/pkg1@v1.5.4", `rsc.io/pkg1 v1.5.4 h1:hash!=
+`)
+       tc.mustLookup("rsc.io/pkg1", "v1.5.2", "rsc.io/pkg1 v1.5.2 h1:hash!=")
+
+       tc2.addRecord("rsc.io/pkg1@v1.5.3", `rsc.io/pkg1 v1.5.3 h1:hash!=
+`)
+       tc2.addRecord("rsc.io/pkg1@v1.5.4", `rsc.io/pkg1 v1.5.4 h1:hash!=
+`)
+       tc2.mustLookup("rsc.io/pkg1", "v1.5.4", "rsc.io/pkg1 v1.5.4 h1:hash!=")
+
+       key := "/lookup/rsc.io/pkg1@v1.5.2"
+       tc2.remote[key] = tc.remote[key]
+       _, err := tc2.client.Lookup("rsc.io/pkg1", "v1.5.2")
+       tc2.mustError(err, ErrSecurity.Error())
+
+       /*
+          SECURITY ERROR
+          go.sum database server misbehavior detected!
+
+          old database:
+               go.sum database tree!
+               5
+               nWzN20+pwMt62p7jbv1/NlN95ePTlHijabv5zO/s36w=
+
+               — localhost.localdev/sumdb AAAMZ5/2FVAdMH58kmnz/0h299pwyskEbzDzoa2/YaPdhvLya4YWDFQQxu2TQb5GpwAH4NdWnTwuhILafisyf3CNbgg=
+
+          new database:
+               go.sum database tree
+               6
+               wc4SkQt52o5W2nQ8To2ARs+mWuUJjss+sdleoiqxMmM=
+
+               — localhost.localdev/sumdb AAAMZ6oRNswlEZ6ZZhxrCvgl1MBy+nusq4JU+TG6Fe2NihWLqOzb+y2c2kzRLoCr4tvw9o36ucQEnhc20e4nA4Qc/wc=
+
+          proof of misbehavior:
+               T7i+H/8ER4nXOiw4Bj0koZOkGjkxoNvlI34GpvhHhQg=
+               Nsuejv72de9hYNM5bqFv8rv3gm3zJQwv/DT/WNbLDLA=
+               mOmqqZ1aI/lzS94oq/JSbj7pD8Rv9S+xDyi12BtVSHo=
+               /7Aw5jVSMM9sFjQhaMg+iiDYPMk6decH7QLOGrL9Lx0=
+       */
+
+       wants := []string{
+               "SECURITY ERROR",
+               "go.sum database server misbehavior detected!",
+               "old database:\n\tgo.sum database tree\n\t5\n",
+               "— localhost.localdev/sumdb AAAMZ5/2FVAd",
+               "new database:\n\tgo.sum database tree\n\t6\n",
+               "— localhost.localdev/sumdb AAAMZ6oRNswl",
+               "proof of misbehavior:\n\tT7i+H/8ER4nXOiw4Bj0k",
+       }
+       text := tc2.security.String()
+       for _, want := range wants {
+               if !strings.Contains(text, want) {
+                       t.Fatalf("cannot find %q in security text:\n%s", want, text)
+               }
+       }
+}
+
+func TestClientGONOSUMDB(t *testing.T) {
+       tc := newTestClient(t)
+       tc.client.SetGONOSUMDB("p,*/q")
+       tc.client.Lookup("rsc.io/sampler", "v1.3.0") // initialize before we turn off network
+       tc.getOK = false
+
+       ok := []string{
+               "abc",
+               "a/p",
+               "pq",
+               "q",
+               "n/o/p/q",
+       }
+       skip := []string{
+               "p",
+               "p/x",
+               "x/q",
+               "x/q/z",
+       }
+
+       for _, path := range ok {
+               _, err := tc.client.Lookup(path, "v1.0.0")
+               if err == ErrGONOSUMDB {
+                       t.Errorf("Lookup(%q): ErrGONOSUMDB, wanted failed actual lookup", path)
+               }
+       }
+       for _, path := range skip {
+               _, err := tc.client.Lookup(path, "v1.0.0")
+               if err != ErrGONOSUMDB {
+                       t.Errorf("Lookup(%q): %v, wanted ErrGONOSUMDB", path, err)
+               }
+       }
+}
+
+// A testClient is a self-contained client-side testing environment.
+type testClient struct {
+       t          *testing.T // active test
+       client     *Client    // client being tested
+       tileHeight int        // tile height to use (default 2)
+       getOK      bool       // should tc.GetURL succeed?
+       getTileOK  bool       // should tc.GetURL of tiles succeed?
+       treeSize   int64
+       hashes     []tlog.Hash
+       remote     map[string][]byte
+       signer     note.Signer
+
+       // mu protects config, cache, log, security
+       // during concurrent use of the exported methods
+       // by the client itself (testClient is the Client's ClientOps,
+       // and the Client methods can both read and write these fields).
+       // Unexported methods invoked directly by the test
+       // (for example, addRecord) need not hold the mutex:
+       // for proper test execution those methods should only
+       // be called when the Client is idle and not using its ClientOps.
+       // Not holding the mutex in those methods ensures
+       // that if a mistake is made, go test -race will report it.
+       // (Holding the mutex would eliminate the race report but
+       // not the underlying problem.)
+       // Similarly, the get map is not protected by the mutex,
+       // because the Client methods only read it.
+       mu       sync.Mutex // prot
+       config   map[string][]byte
+       cache    map[string][]byte
+       security bytes.Buffer
+}
+
+// newTestClient returns a new testClient that will call t.Fatal on error
+// and has a few records already available on the remote server.
+func newTestClient(t *testing.T) *testClient {
+       tc := &testClient{
+               t:          t,
+               tileHeight: 2,
+               getOK:      true,
+               getTileOK:  true,
+               config:     make(map[string][]byte),
+               cache:      make(map[string][]byte),
+               remote:     make(map[string][]byte),
+       }
+
+       tc.config["key"] = []byte(testVerifierKey + "\n")
+       var err error
+       tc.signer, err = note.NewSigner(testSignerKey)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       tc.newClient()
+
+       tc.addRecord("rsc.io/quote@v1.5.2", `rsc.io/quote v1.5.2 h1:w5fcysjrx7yqtD/aO+QwRjYZOKnaM9Uh2b40tElTs3Y=
+rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
+rsc.io/quote v1.5.2 h2:xyzzy
+`)
+
+       tc.addRecord("golang.org/x/text@v0.0.0-20170915032832-14c0d48ead0c", `golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+`)
+       tc.addRecord("rsc.io/sampler@v1.3.0", `rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+`)
+       tc.config[testName+"/latest"] = tc.signTree(1)
+
+       tc.addRecord("rsc.io/!quote@v1.5.2", `rsc.io/Quote v1.5.2 h1:uppercase!=
+`)
+       return tc
+}
+
+// newClient resets the Client associated with tc.
+// This clears any in-memory cache from the Client
+// but not tc's on-disk cache.
+func (tc *testClient) newClient() {
+       tc.client = NewClient(tc)
+       tc.client.SetTileHeight(tc.tileHeight)
+}
+
+// mustLookup does a lookup for path@vers and checks that the lines that come back match want.
+func (tc *testClient) mustLookup(path, vers, want string) {
+       tc.t.Helper()
+       lines, err := tc.client.Lookup(path, vers)
+       if err != nil {
+               tc.t.Fatal(err)
+       }
+       if strings.Join(lines, "\n") != want {
+               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))
+       }
+}
+
+// mustHaveLatest checks that the on-disk configuration
+// for latest is a tree of size n.
+func (tc *testClient) mustHaveLatest(n int64) {
+       tc.t.Helper()
+
+       latest := tc.config[testName+"/latest"]
+       lines := strings.Split(string(latest), "\n")
+       if len(lines) < 2 || lines[1] != fmt.Sprint(n) {
+               tc.t.Fatalf("/latest should have tree %d, but has:\n%s", n, latest)
+       }
+}
+
+// mustError checks that err's error string contains the text.
+func (tc *testClient) mustError(err error, text string) {
+       tc.t.Helper()
+       if err == nil || !strings.Contains(err.Error(), text) {
+               tc.t.Fatalf("err = %v, want %q", err, text)
+       }
+}
+
+// fork returns a copy of tc.
+// Changes made to the new copy or to tc are not reflected in the other.
+func (tc *testClient) fork() *testClient {
+       tc2 := &testClient{
+               t:          tc.t,
+               getOK:      tc.getOK,
+               getTileOK:  tc.getTileOK,
+               tileHeight: tc.tileHeight,
+               treeSize:   tc.treeSize,
+               hashes:     append([]tlog.Hash{}, tc.hashes...),
+               signer:     tc.signer,
+               config:     copyMap(tc.config),
+               cache:      copyMap(tc.cache),
+               remote:     copyMap(tc.remote),
+       }
+       tc2.newClient()
+       return tc2
+}
+
+func copyMap(m map[string][]byte) map[string][]byte {
+       m2 := make(map[string][]byte)
+       for k, v := range m {
+               m2[k] = v
+       }
+       return m2
+}
+
+// ReadHashes is tc's implementation of tlog.HashReader, for use with
+// tlog.TreeHash and so on.
+func (tc *testClient) ReadHashes(indexes []int64) ([]tlog.Hash, error) {
+       var list []tlog.Hash
+       for _, id := range indexes {
+               list = append(list, tc.hashes[id])
+       }
+       return list, nil
+}
+
+// addRecord adds a log record using the given (!-encoded) key and data.
+func (tc *testClient) addRecord(key, data string) {
+       tc.t.Helper()
+
+       // Create record, add hashes to log tree.
+       id := tc.treeSize
+       tc.treeSize++
+       rec, err := tlog.FormatRecord(id, []byte(data))
+       if err != nil {
+               tc.t.Fatal(err)
+       }
+       hashes, err := tlog.StoredHashesForRecordHash(id, tlog.RecordHash([]byte(data)), tc)
+       if err != nil {
+               tc.t.Fatal(err)
+       }
+       tc.hashes = append(tc.hashes, hashes...)
+
+       // Create lookup result.
+       tc.remote["/lookup/"+key] = append(rec, tc.signTree(tc.treeSize)...)
+
+       // Create new tiles.
+       tiles := tlog.NewTiles(tc.tileHeight, id, tc.treeSize)
+       for _, tile := range tiles {
+               data, err := tlog.ReadTileData(tile, tc)
+               if err != nil {
+                       tc.t.Fatal(err)
+               }
+               tc.remote["/"+tile.Path()] = data
+               // TODO delete old partial tiles
+       }
+}
+
+// signTree returns the signed head for the tree of the given size.
+func (tc *testClient) signTree(size int64) []byte {
+       h, err := tlog.TreeHash(size, tc)
+       if err != nil {
+               tc.t.Fatal(err)
+       }
+       text := tlog.FormatTree(tlog.Tree{N: size, Hash: h})
+       data, err := note.Sign(&note.Note{Text: string(text)}, tc.signer)
+       if err != nil {
+               tc.t.Fatal(err)
+       }
+       return data
+}
+
+// ReadRemote is for tc's implementation of Client.
+func (tc *testClient) ReadRemote(path string) ([]byte, error) {
+       // No mutex here because only the Client should be running
+       // and the Client cannot change tc.get.
+       if !tc.getOK {
+               return nil, fmt.Errorf("disallowed remote read %s", path)
+       }
+       if strings.Contains(path, "/tile/") && !tc.getTileOK {
+               return nil, fmt.Errorf("disallowed remote tile read %s", path)
+       }
+
+       data, ok := tc.remote[path]
+       if !ok {
+               return nil, fmt.Errorf("no remote path %s", path)
+       }
+       return data, nil
+}
+
+// ReadConfig is for tc's implementation of Client.
+func (tc *testClient) ReadConfig(file string) ([]byte, error) {
+       tc.mu.Lock()
+       defer tc.mu.Unlock()
+
+       data, ok := tc.config[file]
+       if !ok {
+               return nil, fmt.Errorf("no config %s", file)
+       }
+       return data, nil
+}
+
+// WriteConfig is for tc's implementation of Client.
+func (tc *testClient) WriteConfig(file string, old, new []byte) error {
+       tc.mu.Lock()
+       defer tc.mu.Unlock()
+
+       data := tc.config[file]
+       if !bytes.Equal(old, data) {
+               return ErrWriteConflict
+       }
+       tc.config[file] = new
+       return nil
+}
+
+// ReadCache is for tc's implementation of Client.
+func (tc *testClient) ReadCache(file string) ([]byte, error) {
+       tc.mu.Lock()
+       defer tc.mu.Unlock()
+
+       data, ok := tc.cache[file]
+       if !ok {
+               return nil, fmt.Errorf("no cache %s", file)
+       }
+       return data, nil
+}
+
+// WriteCache is for tc's implementation of Client.
+func (tc *testClient) WriteCache(file string, data []byte) {
+       tc.mu.Lock()
+       defer tc.mu.Unlock()
+
+       tc.cache[file] = data
+}
+
+// Log is for tc's implementation of Client.
+func (tc *testClient) Log(msg string) {
+       tc.t.Log(msg)
+}
+
+// SecurityError is for tc's implementation of Client.
+func (tc *testClient) SecurityError(msg string) {
+       tc.mu.Lock()
+       defer tc.mu.Unlock()
+
+       fmt.Fprintf(&tc.security, "%s\n", strings.TrimRight(msg, "\n"))
+}