OSDN Git Service

fix commands
[bytom/shuttle.git] / vendor / github.com / bytom / vendor / github.com / golang / groupcache / groupcache_test.go
diff --git a/vendor/github.com/bytom/vendor/github.com/golang/groupcache/groupcache_test.go b/vendor/github.com/bytom/vendor/github.com/golang/groupcache/groupcache_test.go
new file mode 100644 (file)
index 0000000..ea05cac
--- /dev/null
@@ -0,0 +1,456 @@
+/*
+Copyright 2012 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Tests for groupcache.
+
+package groupcache
+
+import (
+       "errors"
+       "fmt"
+       "hash/crc32"
+       "math/rand"
+       "reflect"
+       "sync"
+       "testing"
+       "time"
+       "unsafe"
+
+       "github.com/golang/protobuf/proto"
+
+       pb "github.com/golang/groupcache/groupcachepb"
+       testpb "github.com/golang/groupcache/testpb"
+)
+
+var (
+       once                    sync.Once
+       stringGroup, protoGroup Getter
+
+       stringc = make(chan string)
+
+       dummyCtx Context
+
+       // cacheFills is the number of times stringGroup or
+       // protoGroup's Getter have been called. Read using the
+       // cacheFills function.
+       cacheFills AtomicInt
+)
+
+const (
+       stringGroupName = "string-group"
+       protoGroupName  = "proto-group"
+       testMessageType = "google3/net/groupcache/go/test_proto.TestMessage"
+       fromChan        = "from-chan"
+       cacheSize       = 1 << 20
+)
+
+func testSetup() {
+       stringGroup = NewGroup(stringGroupName, cacheSize, GetterFunc(func(_ Context, key string, dest Sink) error {
+               if key == fromChan {
+                       key = <-stringc
+               }
+               cacheFills.Add(1)
+               return dest.SetString("ECHO:" + key)
+       }))
+
+       protoGroup = NewGroup(protoGroupName, cacheSize, GetterFunc(func(_ Context, key string, dest Sink) error {
+               if key == fromChan {
+                       key = <-stringc
+               }
+               cacheFills.Add(1)
+               return dest.SetProto(&testpb.TestMessage{
+                       Name: proto.String("ECHO:" + key),
+                       City: proto.String("SOME-CITY"),
+               })
+       }))
+}
+
+// tests that a Getter's Get method is only called once with two
+// outstanding callers.  This is the string variant.
+func TestGetDupSuppressString(t *testing.T) {
+       once.Do(testSetup)
+       // Start two getters. The first should block (waiting reading
+       // from stringc) and the second should latch on to the first
+       // one.
+       resc := make(chan string, 2)
+       for i := 0; i < 2; i++ {
+               go func() {
+                       var s string
+                       if err := stringGroup.Get(dummyCtx, fromChan, StringSink(&s)); err != nil {
+                               resc <- "ERROR:" + err.Error()
+                               return
+                       }
+                       resc <- s
+               }()
+       }
+
+       // Wait a bit so both goroutines get merged together via
+       // singleflight.
+       // TODO(bradfitz): decide whether there are any non-offensive
+       // debug/test hooks that could be added to singleflight to
+       // make a sleep here unnecessary.
+       time.Sleep(250 * time.Millisecond)
+
+       // Unblock the first getter, which should unblock the second
+       // as well.
+       stringc <- "foo"
+
+       for i := 0; i < 2; i++ {
+               select {
+               case v := <-resc:
+                       if v != "ECHO:foo" {
+                               t.Errorf("got %q; want %q", v, "ECHO:foo")
+                       }
+               case <-time.After(5 * time.Second):
+                       t.Errorf("timeout waiting on getter #%d of 2", i+1)
+               }
+       }
+}
+
+// tests that a Getter's Get method is only called once with two
+// outstanding callers.  This is the proto variant.
+func TestGetDupSuppressProto(t *testing.T) {
+       once.Do(testSetup)
+       // Start two getters. The first should block (waiting reading
+       // from stringc) and the second should latch on to the first
+       // one.
+       resc := make(chan *testpb.TestMessage, 2)
+       for i := 0; i < 2; i++ {
+               go func() {
+                       tm := new(testpb.TestMessage)
+                       if err := protoGroup.Get(dummyCtx, fromChan, ProtoSink(tm)); err != nil {
+                               tm.Name = proto.String("ERROR:" + err.Error())
+                       }
+                       resc <- tm
+               }()
+       }
+
+       // Wait a bit so both goroutines get merged together via
+       // singleflight.
+       // TODO(bradfitz): decide whether there are any non-offensive
+       // debug/test hooks that could be added to singleflight to
+       // make a sleep here unnecessary.
+       time.Sleep(250 * time.Millisecond)
+
+       // Unblock the first getter, which should unblock the second
+       // as well.
+       stringc <- "Fluffy"
+       want := &testpb.TestMessage{
+               Name: proto.String("ECHO:Fluffy"),
+               City: proto.String("SOME-CITY"),
+       }
+       for i := 0; i < 2; i++ {
+               select {
+               case v := <-resc:
+                       if !reflect.DeepEqual(v, want) {
+                               t.Errorf(" Got: %v\nWant: %v", proto.CompactTextString(v), proto.CompactTextString(want))
+                       }
+               case <-time.After(5 * time.Second):
+                       t.Errorf("timeout waiting on getter #%d of 2", i+1)
+               }
+       }
+}
+
+func countFills(f func()) int64 {
+       fills0 := cacheFills.Get()
+       f()
+       return cacheFills.Get() - fills0
+}
+
+func TestCaching(t *testing.T) {
+       once.Do(testSetup)
+       fills := countFills(func() {
+               for i := 0; i < 10; i++ {
+                       var s string
+                       if err := stringGroup.Get(dummyCtx, "TestCaching-key", StringSink(&s)); err != nil {
+                               t.Fatal(err)
+                       }
+               }
+       })
+       if fills != 1 {
+               t.Errorf("expected 1 cache fill; got %d", fills)
+       }
+}
+
+func TestCacheEviction(t *testing.T) {
+       once.Do(testSetup)
+       testKey := "TestCacheEviction-key"
+       getTestKey := func() {
+               var res string
+               for i := 0; i < 10; i++ {
+                       if err := stringGroup.Get(dummyCtx, testKey, StringSink(&res)); err != nil {
+                               t.Fatal(err)
+                       }
+               }
+       }
+       fills := countFills(getTestKey)
+       if fills != 1 {
+               t.Fatalf("expected 1 cache fill; got %d", fills)
+       }
+
+       g := stringGroup.(*Group)
+       evict0 := g.mainCache.nevict
+
+       // Trash the cache with other keys.
+       var bytesFlooded int64
+       // cacheSize/len(testKey) is approximate
+       for bytesFlooded < cacheSize+1024 {
+               var res string
+               key := fmt.Sprintf("dummy-key-%d", bytesFlooded)
+               stringGroup.Get(dummyCtx, key, StringSink(&res))
+               bytesFlooded += int64(len(key) + len(res))
+       }
+       evicts := g.mainCache.nevict - evict0
+       if evicts <= 0 {
+               t.Errorf("evicts = %v; want more than 0", evicts)
+       }
+
+       // Test that the key is gone.
+       fills = countFills(getTestKey)
+       if fills != 1 {
+               t.Fatalf("expected 1 cache fill after cache trashing; got %d", fills)
+       }
+}
+
+type fakePeer struct {
+       hits int
+       fail bool
+}
+
+func (p *fakePeer) Get(_ Context, in *pb.GetRequest, out *pb.GetResponse) error {
+       p.hits++
+       if p.fail {
+               return errors.New("simulated error from peer")
+       }
+       out.Value = []byte("got:" + in.GetKey())
+       return nil
+}
+
+type fakePeers []ProtoGetter
+
+func (p fakePeers) PickPeer(key string) (peer ProtoGetter, ok bool) {
+       if len(p) == 0 {
+               return
+       }
+       n := crc32.Checksum([]byte(key), crc32.IEEETable) % uint32(len(p))
+       return p[n], p[n] != nil
+}
+
+// tests that peers (virtual, in-process) are hit, and how much.
+func TestPeers(t *testing.T) {
+       once.Do(testSetup)
+       rand.Seed(123)
+       peer0 := &fakePeer{}
+       peer1 := &fakePeer{}
+       peer2 := &fakePeer{}
+       peerList := fakePeers([]ProtoGetter{peer0, peer1, peer2, nil})
+       const cacheSize = 0 // disabled
+       localHits := 0
+       getter := func(_ Context, key string, dest Sink) error {
+               localHits++
+               return dest.SetString("got:" + key)
+       }
+       testGroup := newGroup("TestPeers-group", cacheSize, GetterFunc(getter), peerList)
+       run := func(name string, n int, wantSummary string) {
+               // Reset counters
+               localHits = 0
+               for _, p := range []*fakePeer{peer0, peer1, peer2} {
+                       p.hits = 0
+               }
+
+               for i := 0; i < n; i++ {
+                       key := fmt.Sprintf("key-%d", i)
+                       want := "got:" + key
+                       var got string
+                       err := testGroup.Get(dummyCtx, key, StringSink(&got))
+                       if err != nil {
+                               t.Errorf("%s: error on key %q: %v", name, key, err)
+                               continue
+                       }
+                       if got != want {
+                               t.Errorf("%s: for key %q, got %q; want %q", name, key, got, want)
+                       }
+               }
+               summary := func() string {
+                       return fmt.Sprintf("localHits = %d, peers = %d %d %d", localHits, peer0.hits, peer1.hits, peer2.hits)
+               }
+               if got := summary(); got != wantSummary {
+                       t.Errorf("%s: got %q; want %q", name, got, wantSummary)
+               }
+       }
+       resetCacheSize := func(maxBytes int64) {
+               g := testGroup
+               g.cacheBytes = maxBytes
+               g.mainCache = cache{}
+               g.hotCache = cache{}
+       }
+
+       // Base case; peers all up, with no problems.
+       resetCacheSize(1 << 20)
+       run("base", 200, "localHits = 49, peers = 51 49 51")
+
+       // Verify cache was hit.  All localHits are gone, and some of
+       // the peer hits (the ones randomly selected to be maybe hot)
+       run("cached_base", 200, "localHits = 0, peers = 49 47 48")
+       resetCacheSize(0)
+
+       // With one of the peers being down.
+       // TODO(bradfitz): on a peer number being unavailable, the
+       // consistent hashing should maybe keep trying others to
+       // spread the load out. Currently it fails back to local
+       // execution if the first consistent-hash slot is unavailable.
+       peerList[0] = nil
+       run("one_peer_down", 200, "localHits = 100, peers = 0 49 51")
+
+       // Failing peer
+       peerList[0] = peer0
+       peer0.fail = true
+       run("peer0_failing", 200, "localHits = 100, peers = 51 49 51")
+}
+
+func TestTruncatingByteSliceTarget(t *testing.T) {
+       var buf [100]byte
+       s := buf[:]
+       if err := stringGroup.Get(dummyCtx, "short", TruncatingByteSliceSink(&s)); err != nil {
+               t.Fatal(err)
+       }
+       if want := "ECHO:short"; string(s) != want {
+               t.Errorf("short key got %q; want %q", s, want)
+       }
+
+       s = buf[:6]
+       if err := stringGroup.Get(dummyCtx, "truncated", TruncatingByteSliceSink(&s)); err != nil {
+               t.Fatal(err)
+       }
+       if want := "ECHO:t"; string(s) != want {
+               t.Errorf("truncated key got %q; want %q", s, want)
+       }
+}
+
+func TestAllocatingByteSliceTarget(t *testing.T) {
+       var dst []byte
+       sink := AllocatingByteSliceSink(&dst)
+
+       inBytes := []byte("some bytes")
+       sink.SetBytes(inBytes)
+       if want := "some bytes"; string(dst) != want {
+               t.Errorf("SetBytes resulted in %q; want %q", dst, want)
+       }
+       v, err := sink.view()
+       if err != nil {
+               t.Fatalf("view after SetBytes failed: %v", err)
+       }
+       if &inBytes[0] == &dst[0] {
+               t.Error("inBytes and dst share memory")
+       }
+       if &inBytes[0] == &v.b[0] {
+               t.Error("inBytes and view share memory")
+       }
+       if &dst[0] == &v.b[0] {
+               t.Error("dst and view share memory")
+       }
+}
+
+// orderedFlightGroup allows the caller to force the schedule of when
+// orig.Do will be called.  This is useful to serialize calls such
+// that singleflight cannot dedup them.
+type orderedFlightGroup struct {
+       mu     sync.Mutex
+       stage1 chan bool
+       stage2 chan bool
+       orig   flightGroup
+}
+
+func (g *orderedFlightGroup) Do(key string, fn func() (interface{}, error)) (interface{}, error) {
+       <-g.stage1
+       <-g.stage2
+       g.mu.Lock()
+       defer g.mu.Unlock()
+       return g.orig.Do(key, fn)
+}
+
+// TestNoDedup tests invariants on the cache size when singleflight is
+// unable to dedup calls.
+func TestNoDedup(t *testing.T) {
+       const testkey = "testkey"
+       const testval = "testval"
+       g := newGroup("testgroup", 1024, GetterFunc(func(_ Context, key string, dest Sink) error {
+               return dest.SetString(testval)
+       }), nil)
+
+       orderedGroup := &orderedFlightGroup{
+               stage1: make(chan bool),
+               stage2: make(chan bool),
+               orig:   g.loadGroup,
+       }
+       // Replace loadGroup with our wrapper so we can control when
+       // loadGroup.Do is entered for each concurrent request.
+       g.loadGroup = orderedGroup
+
+       // Issue two idential requests concurrently.  Since the cache is
+       // empty, it will miss.  Both will enter load(), but we will only
+       // allow one at a time to enter singleflight.Do, so the callback
+       // function will be called twice.
+       resc := make(chan string, 2)
+       for i := 0; i < 2; i++ {
+               go func() {
+                       var s string
+                       if err := g.Get(dummyCtx, testkey, StringSink(&s)); err != nil {
+                               resc <- "ERROR:" + err.Error()
+                               return
+                       }
+                       resc <- s
+               }()
+       }
+
+       // Ensure both goroutines have entered the Do routine.  This implies
+       // both concurrent requests have checked the cache, found it empty,
+       // and called load().
+       orderedGroup.stage1 <- true
+       orderedGroup.stage1 <- true
+       orderedGroup.stage2 <- true
+       orderedGroup.stage2 <- true
+
+       for i := 0; i < 2; i++ {
+               if s := <-resc; s != testval {
+                       t.Errorf("result is %s want %s", s, testval)
+               }
+       }
+
+       const wantItems = 1
+       if g.mainCache.items() != wantItems {
+               t.Errorf("mainCache has %d items, want %d", g.mainCache.items(), wantItems)
+       }
+
+       // If the singleflight callback doesn't double-check the cache again
+       // upon entry, we would increment nbytes twice but the entry would
+       // only be in the cache once.
+       const wantBytes = int64(len(testkey) + len(testval))
+       if g.mainCache.nbytes != wantBytes {
+               t.Errorf("cache has %d bytes, want %d", g.mainCache.nbytes, wantBytes)
+       }
+}
+
+func TestGroupStatsAlignment(t *testing.T) {
+       var g Group
+       off := unsafe.Offsetof(g.Stats)
+       if off%8 != 0 {
+               t.Fatal("Stats structure is not 8-byte aligned.")
+       }
+}
+
+// TODO(bradfitz): port the Google-internal full integration test into here,
+// using HTTP requests instead of our RPC system.