add go.mod

This commit is contained in:
idk
2019-11-01 17:12:33 -04:00
parent bb5449fa8b
commit 5cdfb05209
10 changed files with 18 additions and 750 deletions

18
go.mod Normal file
View File

@ -0,0 +1,18 @@
module github.com/eyedeekay/i2p-tools-1
go 1.13
require (
github.com/MDrollette/i2p-tools v0.0.0-20171015191648-e7d4585361c2
github.com/codegangsta/cli v1.20.0
github.com/cretz/bine v0.1.0
github.com/garyburd/redigo v1.1.0 // indirect
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/gorilla/handlers v1.2.1
github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad // indirect
github.com/justinas/alice v0.0.0-20160910103822-1051eaf52fca
github.com/throttled/throttled v2.2.4+incompatible
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf // indirect
golang.org/x/net v0.0.0-20191101175033-0deb6923b6d9 // indirect
gopkg.in/throttled/throttled.v2 v2.0.3 // indirect
)

View File

@ -1,59 +0,0 @@
package throttled_test
import (
"net/http"
"net/http/httptest"
"testing"
"gopkg.in/throttled/throttled.v2"
"gopkg.in/throttled/throttled.v2/store"
)
// Ensure that the current implementation remains compatible with the
// supported but deprecated usage until the next major version.
func TestDeprecatedUsage(t *testing.T) {
// Declare interfaces to statically check that names haven't changed
var st throttled.Store
var thr *throttled.Throttler
var q throttled.Quota
st = store.NewMemStore(100)
vary := &throttled.VaryBy{Path: true}
q = throttled.PerMin(2)
thr = throttled.RateLimit(q, vary, st)
handler := thr.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}))
cases := []struct {
path string
code int
headers map[string]string
}{
{"/foo", 200, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "1", "X-Ratelimit-Reset": "30"}},
{"/foo", 200, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "0", "X-Ratelimit-Reset": "60"}},
{"/foo", 429, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "0", "X-Ratelimit-Reset": "60", "Retry-After": "30"}},
{"/bar", 200, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "1", "X-Ratelimit-Reset": "30"}},
}
for i, c := range cases {
req, err := http.NewRequest("GET", c.path, nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if have, want := rr.Code, c.code; have != want {
t.Errorf("Expected request %d at %s to return %d but got %d",
i, c.path, want, have)
}
for name, want := range c.headers {
if have := rr.HeaderMap.Get(name); have != want {
t.Errorf("Expected request %d at %s to have header '%s: %s' but got '%s'",
i, c.path, name, want, have)
}
}
}
}

View File

@ -1,103 +0,0 @@
package throttled_test
import (
"fmt"
"log"
"net/http"
"gopkg.in/throttled/throttled.v2"
"gopkg.in/throttled/throttled.v2/store/memstore"
)
var myHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hi there!"))
})
// ExampleHTTPRateLimiter demonstrates the usage of HTTPRateLimiter
// for rate-limiting access to an http.Handler to 20 requests per path
// per minute with a maximum burst of 5 requests.
func ExampleHTTPRateLimiter() {
store, err := memstore.New(65536)
if err != nil {
log.Fatal(err)
}
// Maximum burst of 5 which refills at 20 tokens per minute.
quota := throttled.RateQuota{throttled.PerMin(20), 5}
rateLimiter, err := throttled.NewGCRARateLimiter(store, quota)
if err != nil {
log.Fatal(err)
}
httpRateLimiter := throttled.HTTPRateLimiter{
RateLimiter: rateLimiter,
VaryBy: &throttled.VaryBy{Path: true},
}
http.ListenAndServe(":8080", httpRateLimiter.RateLimit(myHandler))
}
// Demonstrates direct use of GCRARateLimiter's RateLimit function (and the
// more general RateLimiter interface). This should be used anywhere where
// granular control over rate limiting is required.
func ExampleGCRARateLimiter() {
store, err := memstore.New(65536)
if err != nil {
log.Fatal(err)
}
// Maximum burst of 5 which refills at 1 token per hour.
quota := throttled.RateQuota{throttled.PerHour(1), 5}
rateLimiter, err := throttled.NewGCRARateLimiter(store, quota)
if err != nil {
log.Fatal(err)
}
// Bucket according to the number i / 10 (so 1 falls into the bucket 0
// while 11 falls into the bucket 1). This has the effect of allowing a
// burst of 5 plus 1 (a single emission interval) on every ten iterations
// of the loop. See the output for better clarity here.
//
// We also refill the bucket at 1 token per hour, but that has no effect
// for the purposes of this example.
for i := 0; i < 20; i++ {
bucket := fmt.Sprintf("by-order:%v", i/10)
limited, result, err := rateLimiter.RateLimit(bucket, 1)
if err != nil {
log.Fatal(err)
}
if limited {
fmt.Printf("Iteration %2v; bucket %v: FAILED. Rate limit exceeded.\n",
i, bucket)
} else {
fmt.Printf("Iteration %2v; bucket %v: Operation successful (remaining=%v).\n",
i, bucket, result.Remaining)
}
}
// Output:
// Iteration 0; bucket by-order:0: Operation successful (remaining=5).
// Iteration 1; bucket by-order:0: Operation successful (remaining=4).
// Iteration 2; bucket by-order:0: Operation successful (remaining=3).
// Iteration 3; bucket by-order:0: Operation successful (remaining=2).
// Iteration 4; bucket by-order:0: Operation successful (remaining=1).
// Iteration 5; bucket by-order:0: Operation successful (remaining=0).
// Iteration 6; bucket by-order:0: FAILED. Rate limit exceeded.
// Iteration 7; bucket by-order:0: FAILED. Rate limit exceeded.
// Iteration 8; bucket by-order:0: FAILED. Rate limit exceeded.
// Iteration 9; bucket by-order:0: FAILED. Rate limit exceeded.
// Iteration 10; bucket by-order:1: Operation successful (remaining=5).
// Iteration 11; bucket by-order:1: Operation successful (remaining=4).
// Iteration 12; bucket by-order:1: Operation successful (remaining=3).
// Iteration 13; bucket by-order:1: Operation successful (remaining=2).
// Iteration 14; bucket by-order:1: Operation successful (remaining=1).
// Iteration 15; bucket by-order:1: Operation successful (remaining=0).
// Iteration 16; bucket by-order:1: FAILED. Rate limit exceeded.
// Iteration 17; bucket by-order:1: FAILED. Rate limit exceeded.
// Iteration 18; bucket by-order:1: FAILED. Rate limit exceeded.
// Iteration 19; bucket by-order:1: FAILED. Rate limit exceeded.
}

View File

@ -1,99 +0,0 @@
package throttled_test
import (
"errors"
"net/http"
"net/http/httptest"
"testing"
"time"
"gopkg.in/throttled/throttled.v2"
)
type stubLimiter struct {
}
func (sl *stubLimiter) RateLimit(key string, quantity int) (bool, throttled.RateLimitResult, error) {
switch key {
case "limit":
return true, throttled.RateLimitResult{-1, -1, -1, time.Minute}, nil
case "error":
return false, throttled.RateLimitResult{}, errors.New("stubLimiter error")
default:
return false, throttled.RateLimitResult{1, 2, time.Minute, -1}, nil
}
}
type pathGetter struct{}
func (*pathGetter) Key(r *http.Request) string {
return r.URL.Path
}
type httpTestCase struct {
path string
code int
headers map[string]string
}
func TestHTTPRateLimiter(t *testing.T) {
limiter := throttled.HTTPRateLimiter{
RateLimiter: &stubLimiter{},
VaryBy: &pathGetter{},
}
handler := limiter.RateLimit(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}))
runHTTPTestCases(t, handler, []httpTestCase{
{"ok", 200, map[string]string{"X-Ratelimit-Limit": "1", "X-Ratelimit-Remaining": "2", "X-Ratelimit-Reset": "60"}},
{"error", 500, map[string]string{}},
{"limit", 429, map[string]string{"Retry-After": "60"}},
})
}
func TestCustomHTTPRateLimiterHandlers(t *testing.T) {
limiter := throttled.HTTPRateLimiter{
RateLimiter: &stubLimiter{},
VaryBy: &pathGetter{},
DeniedHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "custom limit exceeded", 400)
}),
Error: func(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, "custom internal error", 501)
},
}
handler := limiter.RateLimit(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}))
runHTTPTestCases(t, handler, []httpTestCase{
{"limit", 400, map[string]string{}},
{"error", 501, map[string]string{}},
})
}
func runHTTPTestCases(t *testing.T, h http.Handler, cs []httpTestCase) {
for i, c := range cs {
req, err := http.NewRequest("GET", c.path, nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
h.ServeHTTP(rr, req)
if have, want := rr.Code, c.code; have != want {
t.Errorf("Expected request %d at %s to return %d but got %d",
i, c.path, want, have)
}
for name, want := range c.headers {
if have := rr.HeaderMap.Get(name); have != want {
t.Errorf("Expected request %d at %s to have header '%s: %s' but got '%s'",
i, c.path, name, want, have)
}
}
}
}

View File

@ -1,128 +0,0 @@
package throttled_test
import (
"testing"
"time"
"gopkg.in/throttled/throttled.v2"
"gopkg.in/throttled/throttled.v2/store/memstore"
)
const deniedStatus = 429
type testStore struct {
store throttled.GCRAStore
clock time.Time
failUpdates bool
}
func (ts *testStore) GetWithTime(key string) (int64, time.Time, error) {
v, _, e := ts.store.GetWithTime(key)
return v, ts.clock, e
}
func (ts *testStore) SetIfNotExistsWithTTL(key string, value int64, ttl time.Duration) (bool, error) {
if ts.failUpdates {
return false, nil
}
return ts.store.SetIfNotExistsWithTTL(key, value, ttl)
}
func (ts *testStore) CompareAndSwapWithTTL(key string, old, new int64, ttl time.Duration) (bool, error) {
if ts.failUpdates {
return false, nil
}
return ts.store.CompareAndSwapWithTTL(key, old, new, ttl)
}
func TestRateLimit(t *testing.T) {
limit := 5
rq := throttled.RateQuota{throttled.PerSec(1), limit - 1}
start := time.Unix(0, 0)
cases := []struct {
now time.Time
volume, remaining int
reset, retry time.Duration
limited bool
}{
// You can never make a request larger than the maximum
0: {start, 6, 5, 0, -1, true},
// Rate limit normal requests appropriately
1: {start, 1, 4, time.Second, -1, false},
2: {start, 1, 3, 2 * time.Second, -1, false},
3: {start, 1, 2, 3 * time.Second, -1, false},
4: {start, 1, 1, 4 * time.Second, -1, false},
5: {start, 1, 0, 5 * time.Second, -1, false},
6: {start, 1, 0, 5 * time.Second, time.Second, true},
7: {start.Add(3000 * time.Millisecond), 1, 2, 3000 * time.Millisecond, -1, false},
8: {start.Add(3100 * time.Millisecond), 1, 1, 3900 * time.Millisecond, -1, false},
9: {start.Add(4000 * time.Millisecond), 1, 1, 4000 * time.Millisecond, -1, false},
10: {start.Add(8000 * time.Millisecond), 1, 4, 1000 * time.Millisecond, -1, false},
11: {start.Add(9500 * time.Millisecond), 1, 4, 1000 * time.Millisecond, -1, false},
// Zero-volume request just peeks at the state
12: {start.Add(9500 * time.Millisecond), 0, 4, time.Second, -1, false},
// High-volume request uses up more of the limit
13: {start.Add(9500 * time.Millisecond), 2, 2, 3 * time.Second, -1, false},
// Large requests cannot exceed limits
14: {start.Add(9500 * time.Millisecond), 5, 2, 3 * time.Second, 3 * time.Second, true},
}
mst, err := memstore.New(0)
if err != nil {
t.Fatal(err)
}
st := testStore{store: mst}
rl, err := throttled.NewGCRARateLimiter(&st, rq)
if err != nil {
t.Fatal(err)
}
// Start the server
for i, c := range cases {
st.clock = c.now
limited, context, err := rl.RateLimit("foo", c.volume)
if err != nil {
t.Fatalf("%d: %#v", i, err)
}
if limited != c.limited {
t.Errorf("%d: expected Limited to be %t but got %t", i, c.limited, limited)
}
if have, want := context.Limit, limit; have != want {
t.Errorf("%d: expected Limit to be %d but got %d", i, want, have)
}
if have, want := context.Remaining, c.remaining; have != want {
t.Errorf("%d: expected Remaining to be %d but got %d", i, want, have)
}
if have, want := context.ResetAfter, c.reset; have != want {
t.Errorf("%d: expected ResetAfter to be %s but got %s", i, want, have)
}
if have, want := context.RetryAfter, c.retry; have != want {
t.Errorf("%d: expected RetryAfter to be %d but got %d", i, want, have)
}
}
}
func TestRateLimitUpdateFailures(t *testing.T) {
rq := throttled.RateQuota{throttled.PerSec(1), 1}
mst, err := memstore.New(0)
if err != nil {
t.Fatal(err)
}
st := testStore{store: mst, failUpdates: true}
rl, err := throttled.NewGCRARateLimiter(&st, rq)
if err != nil {
t.Fatal(err)
}
if _, _, err := rl.RateLimit("foo", 1); err == nil {
t.Error("Expected limiting to fail when store updates fail")
}
}

View File

@ -1,40 +0,0 @@
package memstore_test
import (
"testing"
"gopkg.in/throttled/throttled.v2/store/memstore"
"gopkg.in/throttled/throttled.v2/store/storetest"
)
func TestMemStoreLRU(t *testing.T) {
st, err := memstore.New(10)
if err != nil {
t.Fatal(err)
}
storetest.TestGCRAStore(t, st)
}
func TestMemStoreUnlimited(t *testing.T) {
st, err := memstore.New(10)
if err != nil {
t.Fatal(err)
}
storetest.TestGCRAStore(t, st)
}
func BenchmarkMemStoreLRU(b *testing.B) {
st, err := memstore.New(10)
if err != nil {
b.Fatal(err)
}
storetest.BenchmarkGCRAStore(b, st)
}
func BenchmarkMemStoreUnlimited(b *testing.B) {
st, err := memstore.New(0)
if err != nil {
b.Fatal(err)
}
storetest.BenchmarkGCRAStore(b, st)
}

View File

@ -1,85 +0,0 @@
package redigostore_test
import (
"testing"
"time"
"github.com/garyburd/redigo/redis"
"gopkg.in/throttled/throttled.v2/store/redigostore"
"gopkg.in/throttled/throttled.v2/store/storetest"
)
const (
redisTestDB = 1
redisTestPrefix = "throttled:"
)
func getPool() *redis.Pool {
pool := &redis.Pool{
MaxIdle: 3,
IdleTimeout: 30 * time.Second,
Dial: func() (redis.Conn, error) {
return redis.Dial("tcp", ":6379")
},
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
return err
},
}
return pool
}
func TestRedisStore(t *testing.T) {
c, st := setupRedis(t, 0)
defer c.Close()
defer clearRedis(c)
clearRedis(c)
storetest.TestGCRAStore(t, st)
storetest.TestGCRAStoreTTL(t, st)
}
func BenchmarkRedisStore(b *testing.B) {
c, st := setupRedis(b, 0)
defer c.Close()
defer clearRedis(c)
storetest.BenchmarkGCRAStore(b, st)
}
func clearRedis(c redis.Conn) error {
keys, err := redis.Values(c.Do("KEYS", redisTestPrefix+"*"))
if err != nil {
return err
}
if _, err := redis.Int(c.Do("DEL", keys...)); err != nil {
return err
}
return nil
}
func setupRedis(tb testing.TB, ttl time.Duration) (redis.Conn, *redigostore.RedigoStore) {
pool := getPool()
c := pool.Get()
if _, err := redis.String(c.Do("PING")); err != nil {
c.Close()
tb.Skip("redis server not available on localhost port 6379")
}
if _, err := redis.String(c.Do("SELECT", redisTestDB)); err != nil {
c.Close()
tb.Fatal(err)
}
st, err := redigostore.New(pool, redisTestPrefix, redisTestDB)
if err != nil {
c.Close()
tb.Fatal(err)
}
return c, st
}

View File

@ -1,2 +0,0 @@
// Package storetest provides a helper for testing throttled stores.
package storetest // import "gopkg.in/throttled/throttled.v2/store/storetest"

View File

@ -1,176 +0,0 @@
// Package storetest provides a helper for testing throttled stores.
package storetest // import "gopkg.in/throttled/throttled.v2/store/storetest"
import (
"math/rand"
"strconv"
"sync/atomic"
"testing"
"time"
"gopkg.in/throttled/throttled.v2"
)
// TestGCRAStore tests the behavior of a GCRAStore implementation for
// compliance with the throttled API. It does not require support
// for TTLs.
func TestGCRAStore(t *testing.T, st throttled.GCRAStore) {
// GetWithTime a missing key
if have, _, err := st.GetWithTime("foo"); err != nil {
t.Fatal(err)
} else if have != -1 {
t.Errorf("expected GetWithTime to return -1 for a missing key but got %d", have)
}
// SetIfNotExists on a new key
want := int64(1)
if set, err := st.SetIfNotExistsWithTTL("foo", want, 0); err != nil {
t.Fatal(err)
} else if !set {
t.Errorf("expected SetIfNotExists on an empty key to succeed")
}
before := time.Now()
if have, now, err := st.GetWithTime("foo"); err != nil {
t.Fatal(err)
} else if have != want {
t.Errorf("expected GetWithTime to return %d but got %d", want, have)
} else if now.UnixNano() <= 0 {
t.Errorf("expected GetWithTime to return a time representable representable as a positive int64 of nanoseconds since the epoch")
} else if now.Before(before) || now.After(time.Now()) {
// Note that we make the assumption here that the store is running on
// the same machine as this test and thus shares a clock. This can be a
// little tricky in the case of Redis, which could be running
// elsewhere. The test assumes that it's running either locally on on
// Travis (where currently the Redis is available on localhost). If new
// test environments are procured, this may need to be revisited.
t.Errorf("expected GetWithTime to return a time between the time before the call and the time after the call")
}
// SetIfNotExists on an existing key
if set, err := st.SetIfNotExistsWithTTL("foo", 123, 0); err != nil {
t.Fatal(err)
} else if set {
t.Errorf("expected SetIfNotExists on an existing key to fail")
}
if have, _, err := st.GetWithTime("foo"); err != nil {
t.Fatal(err)
} else if have != want {
t.Errorf("expected GetWithTime to return %d but got %d", want, have)
}
// SetIfNotExists on a different key
if set, err := st.SetIfNotExistsWithTTL("bar", 456, 0); err != nil {
t.Fatal(err)
} else if !set {
t.Errorf("expected SetIfNotExists on an empty key to succeed")
}
// Returns the false on a missing key
if swapped, err := st.CompareAndSwapWithTTL("baz", 1, 2, 0); err != nil {
t.Fatal(err)
} else if swapped {
t.Errorf("expected CompareAndSwap to fail on a missing key")
}
// Test a successful CAS
want = int64(2)
if swapped, err := st.CompareAndSwapWithTTL("foo", 1, want, 0); err != nil {
t.Fatal(err)
} else if !swapped {
t.Errorf("expected CompareAndSwap to succeed")
}
if have, _, err := st.GetWithTime("foo"); err != nil {
t.Fatal(err)
} else if have != want {
t.Errorf("expected GetWithTime to return %d but got %d", want, have)
}
// Test an unsuccessful CAS
if swapped, err := st.CompareAndSwapWithTTL("foo", 1, 2, 0); err != nil {
t.Fatal(err)
} else if swapped {
t.Errorf("expected CompareAndSwap to fail")
}
if have, _, err := st.GetWithTime("foo"); err != nil {
t.Fatal(err)
} else if have != want {
t.Errorf("expected GetWithTime to return %d but got %d", want, have)
}
}
// TestGCRAStoreTTL tests the behavior of TTLs in a GCRAStore implementation.
func TestGCRAStoreTTL(t *testing.T, st throttled.GCRAStore) {
ttl := time.Second
want := int64(1)
key := "ttl"
if _, err := st.SetIfNotExistsWithTTL(key, want, ttl); err != nil {
t.Fatal(err)
}
if have, _, err := st.GetWithTime(key); err != nil {
t.Fatal(err)
} else if have != want {
t.Errorf("expected GetWithTime to return %d, got %d", want, have)
}
// I can't think of a generic way to test expiration without a sleep
time.Sleep(ttl + time.Millisecond)
if have, _, err := st.GetWithTime(key); err != nil {
t.Fatal(err)
} else if have != -1 {
t.Errorf("expected GetWithTime to fail on an expired key but got %d", have)
}
}
// BenchmarkGCRAStore runs parallel benchmarks against a GCRAStore implementation.
// Aside from being useful for performance testing, this is useful for finding
// race conditions with the Go race detector.
func BenchmarkGCRAStore(b *testing.B, st throttled.GCRAStore) {
seed := int64(42)
var attempts, updates int64
b.RunParallel(func(pb *testing.PB) {
// We need atomic behavior around the RNG or go detects a race in the test
delta := int64(1)
seedValue := atomic.AddInt64(&seed, delta) - delta
gen := rand.New(rand.NewSource(seedValue))
for pb.Next() {
key := strconv.FormatInt(gen.Int63n(50), 10)
var v int64
var updated bool
v, _, err := st.GetWithTime(key)
if v == -1 {
updated, err = st.SetIfNotExistsWithTTL(key, gen.Int63(), 0)
if err != nil {
b.Error(err)
}
} else if err != nil {
b.Error(err)
} else {
updated, err = st.CompareAndSwapWithTTL(key, v, gen.Int63(), 0)
if err != nil {
b.Error(err)
}
}
atomic.AddInt64(&attempts, 1)
if updated {
atomic.AddInt64(&updates, 1)
}
}
})
b.Logf("%d/%d update operations succeeed", updates, attempts)
}

View File

@ -1,58 +0,0 @@
package throttled_test
import (
"net/http"
"net/url"
"testing"
"gopkg.in/throttled/throttled.v2"
)
func TestVaryBy(t *testing.T) {
u, err := url.Parse("http://localhost/test/path?q=s")
if err != nil {
panic(err)
}
ck := &http.Cookie{Name: "ssn", Value: "test"}
cases := []struct {
vb *throttled.VaryBy
r *http.Request
k string
}{
0: {nil, &http.Request{}, ""},
1: {&throttled.VaryBy{RemoteAddr: true}, &http.Request{RemoteAddr: "::"}, "::\n"},
2: {
&throttled.VaryBy{Method: true, Path: true},
&http.Request{Method: "POST", URL: u},
"post\n/test/path\n",
},
3: {
&throttled.VaryBy{Headers: []string{"Content-length"}},
&http.Request{Header: http.Header{"Content-Type": []string{"text/plain"}, "Content-Length": []string{"123"}}},
"123\n",
},
4: {
&throttled.VaryBy{Separator: ",", Method: true, Headers: []string{"Content-length"}, Params: []string{"q", "user"}},
&http.Request{Method: "GET", Header: http.Header{"Content-Type": []string{"text/plain"}, "Content-Length": []string{"123"}}, Form: url.Values{"q": []string{"s"}, "pwd": []string{"secret"}, "user": []string{"test"}}},
"get,123,s,test,",
},
5: {
&throttled.VaryBy{Cookies: []string{"ssn"}},
&http.Request{Header: http.Header{"Cookie": []string{ck.String()}}},
"test\n",
},
6: {
&throttled.VaryBy{Cookies: []string{"ssn"}, RemoteAddr: true, Custom: func(r *http.Request) string {
return "blah"
}},
&http.Request{Header: http.Header{"Cookie": []string{ck.String()}}},
"blah",
},
}
for i, c := range cases {
got := c.vb.Key(c.r)
if got != c.k {
t.Errorf("%d: expected '%s' (%d), got '%s' (%d)", i, c.k, len(c.k), got, len(got))
}
}
}