update lmiter and switch to tollbooth_chi
This commit is contained in:
+1
@@ -911,5 +911,6 @@ Below is a list of public, open source projects that use Bolt:
|
||||
* [dcrwallet](https://github.com/decred/dcrwallet) - A wallet for the Decred cryptocurrency.
|
||||
* [Ironsmith](https://github.com/timshannon/ironsmith) - A simple, script-driven continuous integration (build - > test -> release) tool, with no external dependencies
|
||||
* [BoltHold](https://github.com/timshannon/bolthold) - An embeddable NoSQL store for Go types built on BoltDB
|
||||
* [Ponzu CMS](https://ponzu-cms.org) - Headless CMS + automatic JSON API with auto-HTTPS, HTTP/2 Server Push, and flexible server framework.
|
||||
|
||||
If you are using Bolt in a project please send a pull request to add it to the list.
|
||||
|
||||
-1909
File diff suppressed because it is too large
Load Diff
-817
@@ -1,817 +0,0 @@
|
||||
package bolt_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
// Ensure that a cursor can return a reference to the bucket that created it.
|
||||
func TestCursor_Bucket(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cb := b.Cursor().Bucket(); !reflect.DeepEqual(cb, b) {
|
||||
t.Fatal("cursor bucket mismatch")
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a Tx cursor can seek to the appropriate keys.
|
||||
func TestCursor_Seek(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("foo"), []byte("0001")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("bar"), []byte("0002")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("baz"), []byte("0003")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := b.CreateBucket([]byte("bkt")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||
|
||||
// Exact match should go to the key.
|
||||
if k, v := c.Seek([]byte("bar")); !bytes.Equal(k, []byte("bar")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if !bytes.Equal(v, []byte("0002")) {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
|
||||
// Inexact match should go to the next key.
|
||||
if k, v := c.Seek([]byte("bas")); !bytes.Equal(k, []byte("baz")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if !bytes.Equal(v, []byte("0003")) {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
|
||||
// Low key should go to the first key.
|
||||
if k, v := c.Seek([]byte("")); !bytes.Equal(k, []byte("bar")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if !bytes.Equal(v, []byte("0002")) {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
|
||||
// High key should return no key.
|
||||
if k, v := c.Seek([]byte("zzz")); k != nil {
|
||||
t.Fatalf("expected nil key: %v", k)
|
||||
} else if v != nil {
|
||||
t.Fatalf("expected nil value: %v", v)
|
||||
}
|
||||
|
||||
// Buckets should return their key but no value.
|
||||
if k, v := c.Seek([]byte("bkt")); !bytes.Equal(k, []byte("bkt")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if v != nil {
|
||||
t.Fatalf("expected nil value: %v", v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCursor_Delete(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
const count = 1000
|
||||
|
||||
// Insert every other key between 0 and $count.
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i := 0; i < count; i += 1 {
|
||||
k := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(k, uint64(i))
|
||||
if err := b.Put(k, make([]byte, 100)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if _, err := b.CreateBucket([]byte("sub")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||
bound := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(bound, uint64(count/2))
|
||||
for key, _ := c.First(); bytes.Compare(key, bound) < 0; key, _ = c.Next() {
|
||||
if err := c.Delete(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
c.Seek([]byte("sub"))
|
||||
if err := c.Delete(); err != bolt.ErrIncompatibleValue {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
stats := tx.Bucket([]byte("widgets")).Stats()
|
||||
if stats.KeyN != count/2+1 {
|
||||
t.Fatalf("unexpected KeyN: %d", stats.KeyN)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a Tx cursor can seek to the appropriate keys when there are a
|
||||
// large number of keys. This test also checks that seek will always move
|
||||
// forward to the next key.
|
||||
//
|
||||
// Related: https://github.com/boltdb/bolt/pull/187
|
||||
func TestCursor_Seek_Large(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
var count = 10000
|
||||
|
||||
// Insert every other key between 0 and $count.
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := 0; i < count; i += 100 {
|
||||
for j := i; j < i+100; j += 2 {
|
||||
k := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(k, uint64(j))
|
||||
if err := b.Put(k, make([]byte, 100)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||
for i := 0; i < count; i++ {
|
||||
seek := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(seek, uint64(i))
|
||||
|
||||
k, _ := c.Seek(seek)
|
||||
|
||||
// The last seek is beyond the end of the the range so
|
||||
// it should return nil.
|
||||
if i == count-1 {
|
||||
if k != nil {
|
||||
t.Fatal("expected nil key")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise we should seek to the exact key or the next key.
|
||||
num := binary.BigEndian.Uint64(k)
|
||||
if i%2 == 0 {
|
||||
if num != uint64(i) {
|
||||
t.Fatalf("unexpected num: %d", num)
|
||||
}
|
||||
} else {
|
||||
if num != uint64(i+1) {
|
||||
t.Fatalf("unexpected num: %d", num)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a cursor can iterate over an empty bucket without error.
|
||||
func TestCursor_EmptyBucket(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucket([]byte("widgets"))
|
||||
return err
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||
k, v := c.First()
|
||||
if k != nil {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if v != nil {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a Tx cursor can reverse iterate over an empty bucket without error.
|
||||
func TestCursor_EmptyBucketReverse(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucket([]byte("widgets"))
|
||||
return err
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||
k, v := c.Last()
|
||||
if k != nil {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if v != nil {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a Tx cursor can iterate over a single root with a couple elements.
|
||||
func TestCursor_Iterate_Leaf(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("baz"), []byte{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("foo"), []byte{0}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("bar"), []byte{1}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tx, err := db.Begin(false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||
|
||||
k, v := c.First()
|
||||
if !bytes.Equal(k, []byte("bar")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if !bytes.Equal(v, []byte{1}) {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
|
||||
k, v = c.Next()
|
||||
if !bytes.Equal(k, []byte("baz")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if !bytes.Equal(v, []byte{}) {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
|
||||
k, v = c.Next()
|
||||
if !bytes.Equal(k, []byte("foo")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if !bytes.Equal(v, []byte{0}) {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
|
||||
k, v = c.Next()
|
||||
if k != nil {
|
||||
t.Fatalf("expected nil key: %v", k)
|
||||
} else if v != nil {
|
||||
t.Fatalf("expected nil value: %v", v)
|
||||
}
|
||||
|
||||
k, v = c.Next()
|
||||
if k != nil {
|
||||
t.Fatalf("expected nil key: %v", k)
|
||||
} else if v != nil {
|
||||
t.Fatalf("expected nil value: %v", v)
|
||||
}
|
||||
|
||||
if err := tx.Rollback(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a Tx cursor can iterate in reverse over a single root with a couple elements.
|
||||
func TestCursor_LeafRootReverse(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("baz"), []byte{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("foo"), []byte{0}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("bar"), []byte{1}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tx, err := db.Begin(false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||
|
||||
if k, v := c.Last(); !bytes.Equal(k, []byte("foo")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if !bytes.Equal(v, []byte{0}) {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
|
||||
if k, v := c.Prev(); !bytes.Equal(k, []byte("baz")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if !bytes.Equal(v, []byte{}) {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
|
||||
if k, v := c.Prev(); !bytes.Equal(k, []byte("bar")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if !bytes.Equal(v, []byte{1}) {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
|
||||
if k, v := c.Prev(); k != nil {
|
||||
t.Fatalf("expected nil key: %v", k)
|
||||
} else if v != nil {
|
||||
t.Fatalf("expected nil value: %v", v)
|
||||
}
|
||||
|
||||
if k, v := c.Prev(); k != nil {
|
||||
t.Fatalf("expected nil key: %v", k)
|
||||
} else if v != nil {
|
||||
t.Fatalf("expected nil value: %v", v)
|
||||
}
|
||||
|
||||
if err := tx.Rollback(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a Tx cursor can restart from the beginning.
|
||||
func TestCursor_Restart(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("bar"), []byte{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("foo"), []byte{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tx, err := db.Begin(false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||
|
||||
if k, _ := c.First(); !bytes.Equal(k, []byte("bar")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
}
|
||||
if k, _ := c.Next(); !bytes.Equal(k, []byte("foo")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
}
|
||||
|
||||
if k, _ := c.First(); !bytes.Equal(k, []byte("bar")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
}
|
||||
if k, _ := c.Next(); !bytes.Equal(k, []byte("foo")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
}
|
||||
|
||||
if err := tx.Rollback(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a cursor can skip over empty pages that have been deleted.
|
||||
func TestCursor_First_EmptyPages(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
// Create 1000 keys in the "widgets" bucket.
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
if err := b.Put(u64tob(uint64(i)), []byte{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Delete half the keys and then try to iterate.
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket([]byte("widgets"))
|
||||
for i := 0; i < 600; i++ {
|
||||
if err := b.Delete(u64tob(uint64(i))); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
c := b.Cursor()
|
||||
var n int
|
||||
for k, _ := c.First(); k != nil; k, _ = c.Next() {
|
||||
n++
|
||||
}
|
||||
if n != 400 {
|
||||
t.Fatalf("unexpected key count: %d", n)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a Tx can iterate over all elements in a bucket.
|
||||
func TestCursor_QuickCheck(t *testing.T) {
|
||||
f := func(items testdata) bool {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
// Bulk insert all values.
|
||||
tx, err := db.Begin(true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, item := range items {
|
||||
if err := b.Put(item.Key, item.Value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Sort test data.
|
||||
sort.Sort(items)
|
||||
|
||||
// Iterate over all items and check consistency.
|
||||
var index = 0
|
||||
tx, err = db.Begin(false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||
for k, v := c.First(); k != nil && index < len(items); k, v = c.Next() {
|
||||
if !bytes.Equal(k, items[index].Key) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if !bytes.Equal(v, items[index].Value) {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
index++
|
||||
}
|
||||
if len(items) != index {
|
||||
t.Fatalf("unexpected item count: %v, expected %v", len(items), index)
|
||||
}
|
||||
|
||||
if err := tx.Rollback(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
if err := quick.Check(f, qconfig()); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a transaction can iterate over all elements in a bucket in reverse.
|
||||
func TestCursor_QuickCheck_Reverse(t *testing.T) {
|
||||
f := func(items testdata) bool {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
// Bulk insert all values.
|
||||
tx, err := db.Begin(true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, item := range items {
|
||||
if err := b.Put(item.Key, item.Value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Sort test data.
|
||||
sort.Sort(revtestdata(items))
|
||||
|
||||
// Iterate over all items and check consistency.
|
||||
var index = 0
|
||||
tx, err = db.Begin(false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||
for k, v := c.Last(); k != nil && index < len(items); k, v = c.Prev() {
|
||||
if !bytes.Equal(k, items[index].Key) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if !bytes.Equal(v, items[index].Value) {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
index++
|
||||
}
|
||||
if len(items) != index {
|
||||
t.Fatalf("unexpected item count: %v, expected %v", len(items), index)
|
||||
}
|
||||
|
||||
if err := tx.Rollback(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
if err := quick.Check(f, qconfig()); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a Tx cursor can iterate over subbuckets.
|
||||
func TestCursor_QuickCheck_BucketsOnly(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := b.CreateBucket([]byte("foo")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := b.CreateBucket([]byte("bar")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := b.CreateBucket([]byte("baz")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
var names []string
|
||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
names = append(names, string(k))
|
||||
if v != nil {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(names, []string{"bar", "baz", "foo"}) {
|
||||
t.Fatalf("unexpected names: %+v", names)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a Tx cursor can reverse iterate over subbuckets.
|
||||
func TestCursor_QuickCheck_BucketsOnly_Reverse(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := b.CreateBucket([]byte("foo")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := b.CreateBucket([]byte("bar")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := b.CreateBucket([]byte("baz")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
var names []string
|
||||
c := tx.Bucket([]byte("widgets")).Cursor()
|
||||
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
||||
names = append(names, string(k))
|
||||
if v != nil {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(names, []string{"foo", "baz", "bar"}) {
|
||||
t.Fatalf("unexpected names: %+v", names)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleCursor() {
|
||||
// Open the database.
|
||||
db, err := bolt.Open(tempfile(), 0666, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(db.Path())
|
||||
|
||||
// Start a read-write transaction.
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
// Create a new bucket.
|
||||
b, err := tx.CreateBucket([]byte("animals"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Insert data into a bucket.
|
||||
if err := b.Put([]byte("dog"), []byte("fun")); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("cat"), []byte("lame")); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("liger"), []byte("awesome")); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a cursor for iteration.
|
||||
c := b.Cursor()
|
||||
|
||||
// Iterate over items in sorted key order. This starts from the
|
||||
// first key/value pair and updates the k/v variables to the
|
||||
// next key/value on each iteration.
|
||||
//
|
||||
// The loop finishes at the end of the cursor when a nil key is returned.
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
fmt.Printf("A %s is %s.\n", k, v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// A cat is lame.
|
||||
// A dog is fun.
|
||||
// A liger is awesome.
|
||||
}
|
||||
|
||||
func ExampleCursor_reverse() {
|
||||
// Open the database.
|
||||
db, err := bolt.Open(tempfile(), 0666, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(db.Path())
|
||||
|
||||
// Start a read-write transaction.
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
// Create a new bucket.
|
||||
b, err := tx.CreateBucket([]byte("animals"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Insert data into a bucket.
|
||||
if err := b.Put([]byte("dog"), []byte("fun")); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("cat"), []byte("lame")); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("liger"), []byte("awesome")); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a cursor for iteration.
|
||||
c := b.Cursor()
|
||||
|
||||
// Iterate over items in reverse sorted key order. This starts
|
||||
// from the last key/value pair and updates the k/v variables to
|
||||
// the previous key/value on each iteration.
|
||||
//
|
||||
// The loop finishes at the beginning of the cursor when a nil key
|
||||
// is returned.
|
||||
for k, v := c.Last(); k != nil; k, v = c.Prev() {
|
||||
fmt.Printf("A %s is %s.\n", k, v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Close the database to release the file lock.
|
||||
if err := db.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// A liger is awesome.
|
||||
// A dog is fun.
|
||||
// A cat is lame.
|
||||
}
|
||||
-1545
File diff suppressed because it is too large
Load Diff
-158
@@ -1,158 +0,0 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Ensure that a page is added to a transaction's freelist.
|
||||
func TestFreelist_free(t *testing.T) {
|
||||
f := newFreelist()
|
||||
f.free(100, &page{id: 12})
|
||||
if !reflect.DeepEqual([]pgid{12}, f.pending[100]) {
|
||||
t.Fatalf("exp=%v; got=%v", []pgid{12}, f.pending[100])
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a page and its overflow is added to a transaction's freelist.
|
||||
func TestFreelist_free_overflow(t *testing.T) {
|
||||
f := newFreelist()
|
||||
f.free(100, &page{id: 12, overflow: 3})
|
||||
if exp := []pgid{12, 13, 14, 15}; !reflect.DeepEqual(exp, f.pending[100]) {
|
||||
t.Fatalf("exp=%v; got=%v", exp, f.pending[100])
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a transaction's free pages can be released.
|
||||
func TestFreelist_release(t *testing.T) {
|
||||
f := newFreelist()
|
||||
f.free(100, &page{id: 12, overflow: 1})
|
||||
f.free(100, &page{id: 9})
|
||||
f.free(102, &page{id: 39})
|
||||
f.release(100)
|
||||
f.release(101)
|
||||
if exp := []pgid{9, 12, 13}; !reflect.DeepEqual(exp, f.ids) {
|
||||
t.Fatalf("exp=%v; got=%v", exp, f.ids)
|
||||
}
|
||||
|
||||
f.release(102)
|
||||
if exp := []pgid{9, 12, 13, 39}; !reflect.DeepEqual(exp, f.ids) {
|
||||
t.Fatalf("exp=%v; got=%v", exp, f.ids)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a freelist can find contiguous blocks of pages.
|
||||
func TestFreelist_allocate(t *testing.T) {
|
||||
f := &freelist{ids: []pgid{3, 4, 5, 6, 7, 9, 12, 13, 18}}
|
||||
if id := int(f.allocate(3)); id != 3 {
|
||||
t.Fatalf("exp=3; got=%v", id)
|
||||
}
|
||||
if id := int(f.allocate(1)); id != 6 {
|
||||
t.Fatalf("exp=6; got=%v", id)
|
||||
}
|
||||
if id := int(f.allocate(3)); id != 0 {
|
||||
t.Fatalf("exp=0; got=%v", id)
|
||||
}
|
||||
if id := int(f.allocate(2)); id != 12 {
|
||||
t.Fatalf("exp=12; got=%v", id)
|
||||
}
|
||||
if id := int(f.allocate(1)); id != 7 {
|
||||
t.Fatalf("exp=7; got=%v", id)
|
||||
}
|
||||
if id := int(f.allocate(0)); id != 0 {
|
||||
t.Fatalf("exp=0; got=%v", id)
|
||||
}
|
||||
if id := int(f.allocate(0)); id != 0 {
|
||||
t.Fatalf("exp=0; got=%v", id)
|
||||
}
|
||||
if exp := []pgid{9, 18}; !reflect.DeepEqual(exp, f.ids) {
|
||||
t.Fatalf("exp=%v; got=%v", exp, f.ids)
|
||||
}
|
||||
|
||||
if id := int(f.allocate(1)); id != 9 {
|
||||
t.Fatalf("exp=9; got=%v", id)
|
||||
}
|
||||
if id := int(f.allocate(1)); id != 18 {
|
||||
t.Fatalf("exp=18; got=%v", id)
|
||||
}
|
||||
if id := int(f.allocate(1)); id != 0 {
|
||||
t.Fatalf("exp=0; got=%v", id)
|
||||
}
|
||||
if exp := []pgid{}; !reflect.DeepEqual(exp, f.ids) {
|
||||
t.Fatalf("exp=%v; got=%v", exp, f.ids)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a freelist can deserialize from a freelist page.
|
||||
func TestFreelist_read(t *testing.T) {
|
||||
// Create a page.
|
||||
var buf [4096]byte
|
||||
page := (*page)(unsafe.Pointer(&buf[0]))
|
||||
page.flags = freelistPageFlag
|
||||
page.count = 2
|
||||
|
||||
// Insert 2 page ids.
|
||||
ids := (*[3]pgid)(unsafe.Pointer(&page.ptr))
|
||||
ids[0] = 23
|
||||
ids[1] = 50
|
||||
|
||||
// Deserialize page into a freelist.
|
||||
f := newFreelist()
|
||||
f.read(page)
|
||||
|
||||
// Ensure that there are two page ids in the freelist.
|
||||
if exp := []pgid{23, 50}; !reflect.DeepEqual(exp, f.ids) {
|
||||
t.Fatalf("exp=%v; got=%v", exp, f.ids)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a freelist can serialize into a freelist page.
|
||||
func TestFreelist_write(t *testing.T) {
|
||||
// Create a freelist and write it to a page.
|
||||
var buf [4096]byte
|
||||
f := &freelist{ids: []pgid{12, 39}, pending: make(map[txid][]pgid)}
|
||||
f.pending[100] = []pgid{28, 11}
|
||||
f.pending[101] = []pgid{3}
|
||||
p := (*page)(unsafe.Pointer(&buf[0]))
|
||||
if err := f.write(p); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Read the page back out.
|
||||
f2 := newFreelist()
|
||||
f2.read(p)
|
||||
|
||||
// Ensure that the freelist is correct.
|
||||
// All pages should be present and in reverse order.
|
||||
if exp := []pgid{3, 11, 12, 28, 39}; !reflect.DeepEqual(exp, f2.ids) {
|
||||
t.Fatalf("exp=%v; got=%v", exp, f2.ids)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_FreelistRelease10K(b *testing.B) { benchmark_FreelistRelease(b, 10000) }
|
||||
func Benchmark_FreelistRelease100K(b *testing.B) { benchmark_FreelistRelease(b, 100000) }
|
||||
func Benchmark_FreelistRelease1000K(b *testing.B) { benchmark_FreelistRelease(b, 1000000) }
|
||||
func Benchmark_FreelistRelease10000K(b *testing.B) { benchmark_FreelistRelease(b, 10000000) }
|
||||
|
||||
func benchmark_FreelistRelease(b *testing.B, size int) {
|
||||
ids := randomPgids(size)
|
||||
pending := randomPgids(len(ids) / 400)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
f := &freelist{ids: ids, pending: map[txid][]pgid{1: pending}}
|
||||
f.release(1)
|
||||
}
|
||||
}
|
||||
|
||||
func randomPgids(n int) []pgid {
|
||||
rand.Seed(42)
|
||||
pgids := make(pgids, n)
|
||||
for i := range pgids {
|
||||
pgids[i] = pgid(rand.Int63())
|
||||
}
|
||||
sort.Sort(pgids)
|
||||
return pgids
|
||||
}
|
||||
-156
@@ -1,156 +0,0 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Ensure that a node can insert a key/value.
|
||||
func TestNode_put(t *testing.T) {
|
||||
n := &node{inodes: make(inodes, 0), bucket: &Bucket{tx: &Tx{meta: &meta{pgid: 1}}}}
|
||||
n.put([]byte("baz"), []byte("baz"), []byte("2"), 0, 0)
|
||||
n.put([]byte("foo"), []byte("foo"), []byte("0"), 0, 0)
|
||||
n.put([]byte("bar"), []byte("bar"), []byte("1"), 0, 0)
|
||||
n.put([]byte("foo"), []byte("foo"), []byte("3"), 0, leafPageFlag)
|
||||
|
||||
if len(n.inodes) != 3 {
|
||||
t.Fatalf("exp=3; got=%d", len(n.inodes))
|
||||
}
|
||||
if k, v := n.inodes[0].key, n.inodes[0].value; string(k) != "bar" || string(v) != "1" {
|
||||
t.Fatalf("exp=<bar,1>; got=<%s,%s>", k, v)
|
||||
}
|
||||
if k, v := n.inodes[1].key, n.inodes[1].value; string(k) != "baz" || string(v) != "2" {
|
||||
t.Fatalf("exp=<baz,2>; got=<%s,%s>", k, v)
|
||||
}
|
||||
if k, v := n.inodes[2].key, n.inodes[2].value; string(k) != "foo" || string(v) != "3" {
|
||||
t.Fatalf("exp=<foo,3>; got=<%s,%s>", k, v)
|
||||
}
|
||||
if n.inodes[2].flags != uint32(leafPageFlag) {
|
||||
t.Fatalf("not a leaf: %d", n.inodes[2].flags)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a node can deserialize from a leaf page.
|
||||
func TestNode_read_LeafPage(t *testing.T) {
|
||||
// Create a page.
|
||||
var buf [4096]byte
|
||||
page := (*page)(unsafe.Pointer(&buf[0]))
|
||||
page.flags = leafPageFlag
|
||||
page.count = 2
|
||||
|
||||
// Insert 2 elements at the beginning. sizeof(leafPageElement) == 16
|
||||
nodes := (*[3]leafPageElement)(unsafe.Pointer(&page.ptr))
|
||||
nodes[0] = leafPageElement{flags: 0, pos: 32, ksize: 3, vsize: 4} // pos = sizeof(leafPageElement) * 2
|
||||
nodes[1] = leafPageElement{flags: 0, pos: 23, ksize: 10, vsize: 3} // pos = sizeof(leafPageElement) + 3 + 4
|
||||
|
||||
// Write data for the nodes at the end.
|
||||
data := (*[4096]byte)(unsafe.Pointer(&nodes[2]))
|
||||
copy(data[:], []byte("barfooz"))
|
||||
copy(data[7:], []byte("helloworldbye"))
|
||||
|
||||
// Deserialize page into a leaf.
|
||||
n := &node{}
|
||||
n.read(page)
|
||||
|
||||
// Check that there are two inodes with correct data.
|
||||
if !n.isLeaf {
|
||||
t.Fatal("expected leaf")
|
||||
}
|
||||
if len(n.inodes) != 2 {
|
||||
t.Fatalf("exp=2; got=%d", len(n.inodes))
|
||||
}
|
||||
if k, v := n.inodes[0].key, n.inodes[0].value; string(k) != "bar" || string(v) != "fooz" {
|
||||
t.Fatalf("exp=<bar,fooz>; got=<%s,%s>", k, v)
|
||||
}
|
||||
if k, v := n.inodes[1].key, n.inodes[1].value; string(k) != "helloworld" || string(v) != "bye" {
|
||||
t.Fatalf("exp=<helloworld,bye>; got=<%s,%s>", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a node can serialize into a leaf page.
|
||||
func TestNode_write_LeafPage(t *testing.T) {
|
||||
// Create a node.
|
||||
n := &node{isLeaf: true, inodes: make(inodes, 0), bucket: &Bucket{tx: &Tx{db: &DB{}, meta: &meta{pgid: 1}}}}
|
||||
n.put([]byte("susy"), []byte("susy"), []byte("que"), 0, 0)
|
||||
n.put([]byte("ricki"), []byte("ricki"), []byte("lake"), 0, 0)
|
||||
n.put([]byte("john"), []byte("john"), []byte("johnson"), 0, 0)
|
||||
|
||||
// Write it to a page.
|
||||
var buf [4096]byte
|
||||
p := (*page)(unsafe.Pointer(&buf[0]))
|
||||
n.write(p)
|
||||
|
||||
// Read the page back in.
|
||||
n2 := &node{}
|
||||
n2.read(p)
|
||||
|
||||
// Check that the two pages are the same.
|
||||
if len(n2.inodes) != 3 {
|
||||
t.Fatalf("exp=3; got=%d", len(n2.inodes))
|
||||
}
|
||||
if k, v := n2.inodes[0].key, n2.inodes[0].value; string(k) != "john" || string(v) != "johnson" {
|
||||
t.Fatalf("exp=<john,johnson>; got=<%s,%s>", k, v)
|
||||
}
|
||||
if k, v := n2.inodes[1].key, n2.inodes[1].value; string(k) != "ricki" || string(v) != "lake" {
|
||||
t.Fatalf("exp=<ricki,lake>; got=<%s,%s>", k, v)
|
||||
}
|
||||
if k, v := n2.inodes[2].key, n2.inodes[2].value; string(k) != "susy" || string(v) != "que" {
|
||||
t.Fatalf("exp=<susy,que>; got=<%s,%s>", k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a node can split into appropriate subgroups.
|
||||
func TestNode_split(t *testing.T) {
|
||||
// Create a node.
|
||||
n := &node{inodes: make(inodes, 0), bucket: &Bucket{tx: &Tx{db: &DB{}, meta: &meta{pgid: 1}}}}
|
||||
n.put([]byte("00000001"), []byte("00000001"), []byte("0123456701234567"), 0, 0)
|
||||
n.put([]byte("00000002"), []byte("00000002"), []byte("0123456701234567"), 0, 0)
|
||||
n.put([]byte("00000003"), []byte("00000003"), []byte("0123456701234567"), 0, 0)
|
||||
n.put([]byte("00000004"), []byte("00000004"), []byte("0123456701234567"), 0, 0)
|
||||
n.put([]byte("00000005"), []byte("00000005"), []byte("0123456701234567"), 0, 0)
|
||||
|
||||
// Split between 2 & 3.
|
||||
n.split(100)
|
||||
|
||||
var parent = n.parent
|
||||
if len(parent.children) != 2 {
|
||||
t.Fatalf("exp=2; got=%d", len(parent.children))
|
||||
}
|
||||
if len(parent.children[0].inodes) != 2 {
|
||||
t.Fatalf("exp=2; got=%d", len(parent.children[0].inodes))
|
||||
}
|
||||
if len(parent.children[1].inodes) != 3 {
|
||||
t.Fatalf("exp=3; got=%d", len(parent.children[1].inodes))
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a page with the minimum number of inodes just returns a single node.
|
||||
func TestNode_split_MinKeys(t *testing.T) {
|
||||
// Create a node.
|
||||
n := &node{inodes: make(inodes, 0), bucket: &Bucket{tx: &Tx{db: &DB{}, meta: &meta{pgid: 1}}}}
|
||||
n.put([]byte("00000001"), []byte("00000001"), []byte("0123456701234567"), 0, 0)
|
||||
n.put([]byte("00000002"), []byte("00000002"), []byte("0123456701234567"), 0, 0)
|
||||
|
||||
// Split.
|
||||
n.split(20)
|
||||
if n.parent != nil {
|
||||
t.Fatalf("expected nil parent")
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a node that has keys that all fit on a page just returns one leaf.
|
||||
func TestNode_split_SinglePage(t *testing.T) {
|
||||
// Create a node.
|
||||
n := &node{inodes: make(inodes, 0), bucket: &Bucket{tx: &Tx{db: &DB{}, meta: &meta{pgid: 1}}}}
|
||||
n.put([]byte("00000001"), []byte("00000001"), []byte("0123456701234567"), 0, 0)
|
||||
n.put([]byte("00000002"), []byte("00000002"), []byte("0123456701234567"), 0, 0)
|
||||
n.put([]byte("00000003"), []byte("00000003"), []byte("0123456701234567"), 0, 0)
|
||||
n.put([]byte("00000004"), []byte("00000004"), []byte("0123456701234567"), 0, 0)
|
||||
n.put([]byte("00000005"), []byte("00000005"), []byte("0123456701234567"), 0, 0)
|
||||
|
||||
// Split.
|
||||
n.split(4096)
|
||||
if n.parent != nil {
|
||||
t.Fatalf("expected nil parent")
|
||||
}
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
)
|
||||
|
||||
// Ensure that the page type can be returned in human readable format.
|
||||
func TestPage_typ(t *testing.T) {
|
||||
if typ := (&page{flags: branchPageFlag}).typ(); typ != "branch" {
|
||||
t.Fatalf("exp=branch; got=%v", typ)
|
||||
}
|
||||
if typ := (&page{flags: leafPageFlag}).typ(); typ != "leaf" {
|
||||
t.Fatalf("exp=leaf; got=%v", typ)
|
||||
}
|
||||
if typ := (&page{flags: metaPageFlag}).typ(); typ != "meta" {
|
||||
t.Fatalf("exp=meta; got=%v", typ)
|
||||
}
|
||||
if typ := (&page{flags: freelistPageFlag}).typ(); typ != "freelist" {
|
||||
t.Fatalf("exp=freelist; got=%v", typ)
|
||||
}
|
||||
if typ := (&page{flags: 20000}).typ(); typ != "unknown<4e20>" {
|
||||
t.Fatalf("exp=unknown<4e20>; got=%v", typ)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the hexdump debugging function doesn't blow up.
|
||||
func TestPage_dump(t *testing.T) {
|
||||
(&page{id: 256}).hexdump(16)
|
||||
}
|
||||
|
||||
func TestPgids_merge(t *testing.T) {
|
||||
a := pgids{4, 5, 6, 10, 11, 12, 13, 27}
|
||||
b := pgids{1, 3, 8, 9, 25, 30}
|
||||
c := a.merge(b)
|
||||
if !reflect.DeepEqual(c, pgids{1, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 25, 27, 30}) {
|
||||
t.Errorf("mismatch: %v", c)
|
||||
}
|
||||
|
||||
a = pgids{4, 5, 6, 10, 11, 12, 13, 27, 35, 36}
|
||||
b = pgids{8, 9, 25, 30}
|
||||
c = a.merge(b)
|
||||
if !reflect.DeepEqual(c, pgids{4, 5, 6, 8, 9, 10, 11, 12, 13, 25, 27, 30, 35, 36}) {
|
||||
t.Errorf("mismatch: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPgids_merge_quick(t *testing.T) {
|
||||
if err := quick.Check(func(a, b pgids) bool {
|
||||
// Sort incoming lists.
|
||||
sort.Sort(a)
|
||||
sort.Sort(b)
|
||||
|
||||
// Merge the two lists together.
|
||||
got := a.merge(b)
|
||||
|
||||
// The expected value should be the two lists combined and sorted.
|
||||
exp := append(a, b...)
|
||||
sort.Sort(exp)
|
||||
|
||||
if !reflect.DeepEqual(exp, got) {
|
||||
t.Errorf("\nexp=%+v\ngot=%+v\n", exp, got)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
package bolt_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing/quick"
|
||||
"time"
|
||||
)
|
||||
|
||||
// testing/quick defaults to 5 iterations and a random seed.
|
||||
// You can override these settings from the command line:
|
||||
//
|
||||
// -quick.count The number of iterations to perform.
|
||||
// -quick.seed The seed to use for randomizing.
|
||||
// -quick.maxitems The maximum number of items to insert into a DB.
|
||||
// -quick.maxksize The maximum size of a key.
|
||||
// -quick.maxvsize The maximum size of a value.
|
||||
//
|
||||
|
||||
var qcount, qseed, qmaxitems, qmaxksize, qmaxvsize int
|
||||
|
||||
func init() {
|
||||
flag.IntVar(&qcount, "quick.count", 5, "")
|
||||
flag.IntVar(&qseed, "quick.seed", int(time.Now().UnixNano())%100000, "")
|
||||
flag.IntVar(&qmaxitems, "quick.maxitems", 1000, "")
|
||||
flag.IntVar(&qmaxksize, "quick.maxksize", 1024, "")
|
||||
flag.IntVar(&qmaxvsize, "quick.maxvsize", 1024, "")
|
||||
flag.Parse()
|
||||
fmt.Fprintln(os.Stderr, "seed:", qseed)
|
||||
fmt.Fprintf(os.Stderr, "quick settings: count=%v, items=%v, ksize=%v, vsize=%v\n", qcount, qmaxitems, qmaxksize, qmaxvsize)
|
||||
}
|
||||
|
||||
func qconfig() *quick.Config {
|
||||
return &quick.Config{
|
||||
MaxCount: qcount,
|
||||
Rand: rand.New(rand.NewSource(int64(qseed))),
|
||||
}
|
||||
}
|
||||
|
||||
type testdata []testdataitem
|
||||
|
||||
func (t testdata) Len() int { return len(t) }
|
||||
func (t testdata) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
func (t testdata) Less(i, j int) bool { return bytes.Compare(t[i].Key, t[j].Key) == -1 }
|
||||
|
||||
func (t testdata) Generate(rand *rand.Rand, size int) reflect.Value {
|
||||
n := rand.Intn(qmaxitems-1) + 1
|
||||
items := make(testdata, n)
|
||||
used := make(map[string]bool)
|
||||
for i := 0; i < n; i++ {
|
||||
item := &items[i]
|
||||
// Ensure that keys are unique by looping until we find one that we have not already used.
|
||||
for {
|
||||
item.Key = randByteSlice(rand, 1, qmaxksize)
|
||||
if !used[string(item.Key)] {
|
||||
used[string(item.Key)] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
item.Value = randByteSlice(rand, 0, qmaxvsize)
|
||||
}
|
||||
return reflect.ValueOf(items)
|
||||
}
|
||||
|
||||
type revtestdata []testdataitem
|
||||
|
||||
func (t revtestdata) Len() int { return len(t) }
|
||||
func (t revtestdata) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
func (t revtestdata) Less(i, j int) bool { return bytes.Compare(t[i].Key, t[j].Key) == 1 }
|
||||
|
||||
type testdataitem struct {
|
||||
Key []byte
|
||||
Value []byte
|
||||
}
|
||||
|
||||
func randByteSlice(rand *rand.Rand, minSize, maxSize int) []byte {
|
||||
n := rand.Intn(maxSize-minSize) + minSize
|
||||
b := make([]byte, n)
|
||||
for i := 0; i < n; i++ {
|
||||
b[i] = byte(rand.Intn(255))
|
||||
}
|
||||
return b
|
||||
}
|
||||
-329
@@ -1,329 +0,0 @@
|
||||
package bolt_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
func TestSimulate_1op_1p(t *testing.T) { testSimulate(t, 1, 1) }
|
||||
func TestSimulate_10op_1p(t *testing.T) { testSimulate(t, 10, 1) }
|
||||
func TestSimulate_100op_1p(t *testing.T) { testSimulate(t, 100, 1) }
|
||||
func TestSimulate_1000op_1p(t *testing.T) { testSimulate(t, 1000, 1) }
|
||||
func TestSimulate_10000op_1p(t *testing.T) { testSimulate(t, 10000, 1) }
|
||||
|
||||
func TestSimulate_10op_10p(t *testing.T) { testSimulate(t, 10, 10) }
|
||||
func TestSimulate_100op_10p(t *testing.T) { testSimulate(t, 100, 10) }
|
||||
func TestSimulate_1000op_10p(t *testing.T) { testSimulate(t, 1000, 10) }
|
||||
func TestSimulate_10000op_10p(t *testing.T) { testSimulate(t, 10000, 10) }
|
||||
|
||||
func TestSimulate_100op_100p(t *testing.T) { testSimulate(t, 100, 100) }
|
||||
func TestSimulate_1000op_100p(t *testing.T) { testSimulate(t, 1000, 100) }
|
||||
func TestSimulate_10000op_100p(t *testing.T) { testSimulate(t, 10000, 100) }
|
||||
|
||||
func TestSimulate_10000op_1000p(t *testing.T) { testSimulate(t, 10000, 1000) }
|
||||
|
||||
// Randomly generate operations on a given database with multiple clients to ensure consistency and thread safety.
|
||||
func testSimulate(t *testing.T, threadCount, parallelism int) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping test in short mode.")
|
||||
}
|
||||
|
||||
rand.Seed(int64(qseed))
|
||||
|
||||
// A list of operations that readers and writers can perform.
|
||||
var readerHandlers = []simulateHandler{simulateGetHandler}
|
||||
var writerHandlers = []simulateHandler{simulateGetHandler, simulatePutHandler}
|
||||
|
||||
var versions = make(map[int]*QuickDB)
|
||||
versions[1] = NewQuickDB()
|
||||
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
var mutex sync.Mutex
|
||||
|
||||
// Run n threads in parallel, each with their own operation.
|
||||
var wg sync.WaitGroup
|
||||
var threads = make(chan bool, parallelism)
|
||||
var i int
|
||||
for {
|
||||
threads <- true
|
||||
wg.Add(1)
|
||||
writable := ((rand.Int() % 100) < 20) // 20% writers
|
||||
|
||||
// Choose an operation to execute.
|
||||
var handler simulateHandler
|
||||
if writable {
|
||||
handler = writerHandlers[rand.Intn(len(writerHandlers))]
|
||||
} else {
|
||||
handler = readerHandlers[rand.Intn(len(readerHandlers))]
|
||||
}
|
||||
|
||||
// Execute a thread for the given operation.
|
||||
go func(writable bool, handler simulateHandler) {
|
||||
defer wg.Done()
|
||||
|
||||
// Start transaction.
|
||||
tx, err := db.Begin(writable)
|
||||
if err != nil {
|
||||
t.Fatal("tx begin: ", err)
|
||||
}
|
||||
|
||||
// Obtain current state of the dataset.
|
||||
mutex.Lock()
|
||||
var qdb = versions[tx.ID()]
|
||||
if writable {
|
||||
qdb = versions[tx.ID()-1].Copy()
|
||||
}
|
||||
mutex.Unlock()
|
||||
|
||||
// Make sure we commit/rollback the tx at the end and update the state.
|
||||
if writable {
|
||||
defer func() {
|
||||
mutex.Lock()
|
||||
versions[tx.ID()] = qdb
|
||||
mutex.Unlock()
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
}
|
||||
|
||||
// Ignore operation if we don't have data yet.
|
||||
if qdb == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Execute handler.
|
||||
handler(tx, qdb)
|
||||
|
||||
// Release a thread back to the scheduling loop.
|
||||
<-threads
|
||||
}(writable, handler)
|
||||
|
||||
i++
|
||||
if i > threadCount {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Wait until all threads are done.
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
type simulateHandler func(tx *bolt.Tx, qdb *QuickDB)
|
||||
|
||||
// Retrieves a key from the database and verifies that it is what is expected.
|
||||
func simulateGetHandler(tx *bolt.Tx, qdb *QuickDB) {
|
||||
// Randomly retrieve an existing exist.
|
||||
keys := qdb.Rand()
|
||||
if len(keys) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Retrieve root bucket.
|
||||
b := tx.Bucket(keys[0])
|
||||
if b == nil {
|
||||
panic(fmt.Sprintf("bucket[0] expected: %08x\n", trunc(keys[0], 4)))
|
||||
}
|
||||
|
||||
// Drill into nested buckets.
|
||||
for _, key := range keys[1 : len(keys)-1] {
|
||||
b = b.Bucket(key)
|
||||
if b == nil {
|
||||
panic(fmt.Sprintf("bucket[n] expected: %v -> %v\n", keys, key))
|
||||
}
|
||||
}
|
||||
|
||||
// Verify key/value on the final bucket.
|
||||
expected := qdb.Get(keys)
|
||||
actual := b.Get(keys[len(keys)-1])
|
||||
if !bytes.Equal(actual, expected) {
|
||||
fmt.Println("=== EXPECTED ===")
|
||||
fmt.Println(expected)
|
||||
fmt.Println("=== ACTUAL ===")
|
||||
fmt.Println(actual)
|
||||
fmt.Println("=== END ===")
|
||||
panic("value mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
// Inserts a key into the database.
|
||||
func simulatePutHandler(tx *bolt.Tx, qdb *QuickDB) {
|
||||
var err error
|
||||
keys, value := randKeys(), randValue()
|
||||
|
||||
// Retrieve root bucket.
|
||||
b := tx.Bucket(keys[0])
|
||||
if b == nil {
|
||||
b, err = tx.CreateBucket(keys[0])
|
||||
if err != nil {
|
||||
panic("create bucket: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Create nested buckets, if necessary.
|
||||
for _, key := range keys[1 : len(keys)-1] {
|
||||
child := b.Bucket(key)
|
||||
if child != nil {
|
||||
b = child
|
||||
} else {
|
||||
b, err = b.CreateBucket(key)
|
||||
if err != nil {
|
||||
panic("create bucket: " + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Insert into database.
|
||||
if err := b.Put(keys[len(keys)-1], value); err != nil {
|
||||
panic("put: " + err.Error())
|
||||
}
|
||||
|
||||
// Insert into in-memory database.
|
||||
qdb.Put(keys, value)
|
||||
}
|
||||
|
||||
// QuickDB is an in-memory database that replicates the functionality of the
|
||||
// Bolt DB type except that it is entirely in-memory. It is meant for testing
|
||||
// that the Bolt database is consistent.
|
||||
type QuickDB struct {
|
||||
sync.RWMutex
|
||||
m map[string]interface{}
|
||||
}
|
||||
|
||||
// NewQuickDB returns an instance of QuickDB.
|
||||
func NewQuickDB() *QuickDB {
|
||||
return &QuickDB{m: make(map[string]interface{})}
|
||||
}
|
||||
|
||||
// Get retrieves the value at a key path.
|
||||
func (db *QuickDB) Get(keys [][]byte) []byte {
|
||||
db.RLock()
|
||||
defer db.RUnlock()
|
||||
|
||||
m := db.m
|
||||
for _, key := range keys[:len(keys)-1] {
|
||||
value := m[string(key)]
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
switch value := value.(type) {
|
||||
case map[string]interface{}:
|
||||
m = value
|
||||
case []byte:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Only return if it's a simple value.
|
||||
if value, ok := m[string(keys[len(keys)-1])].([]byte); ok {
|
||||
return value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Put inserts a value into a key path.
|
||||
func (db *QuickDB) Put(keys [][]byte, value []byte) {
|
||||
db.Lock()
|
||||
defer db.Unlock()
|
||||
|
||||
// Build buckets all the way down the key path.
|
||||
m := db.m
|
||||
for _, key := range keys[:len(keys)-1] {
|
||||
if _, ok := m[string(key)].([]byte); ok {
|
||||
return // Keypath intersects with a simple value. Do nothing.
|
||||
}
|
||||
|
||||
if m[string(key)] == nil {
|
||||
m[string(key)] = make(map[string]interface{})
|
||||
}
|
||||
m = m[string(key)].(map[string]interface{})
|
||||
}
|
||||
|
||||
// Insert value into the last key.
|
||||
m[string(keys[len(keys)-1])] = value
|
||||
}
|
||||
|
||||
// Rand returns a random key path that points to a simple value.
|
||||
func (db *QuickDB) Rand() [][]byte {
|
||||
db.RLock()
|
||||
defer db.RUnlock()
|
||||
if len(db.m) == 0 {
|
||||
return nil
|
||||
}
|
||||
var keys [][]byte
|
||||
db.rand(db.m, &keys)
|
||||
return keys
|
||||
}
|
||||
|
||||
func (db *QuickDB) rand(m map[string]interface{}, keys *[][]byte) {
|
||||
i, index := 0, rand.Intn(len(m))
|
||||
for k, v := range m {
|
||||
if i == index {
|
||||
*keys = append(*keys, []byte(k))
|
||||
if v, ok := v.(map[string]interface{}); ok {
|
||||
db.rand(v, keys)
|
||||
}
|
||||
return
|
||||
}
|
||||
i++
|
||||
}
|
||||
panic("quickdb rand: out-of-range")
|
||||
}
|
||||
|
||||
// Copy copies the entire database.
|
||||
func (db *QuickDB) Copy() *QuickDB {
|
||||
db.RLock()
|
||||
defer db.RUnlock()
|
||||
return &QuickDB{m: db.copy(db.m)}
|
||||
}
|
||||
|
||||
func (db *QuickDB) copy(m map[string]interface{}) map[string]interface{} {
|
||||
clone := make(map[string]interface{}, len(m))
|
||||
for k, v := range m {
|
||||
switch v := v.(type) {
|
||||
case map[string]interface{}:
|
||||
clone[k] = db.copy(v)
|
||||
default:
|
||||
clone[k] = v
|
||||
}
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
func randKey() []byte {
|
||||
var min, max = 1, 1024
|
||||
n := rand.Intn(max-min) + min
|
||||
b := make([]byte, n)
|
||||
for i := 0; i < n; i++ {
|
||||
b[i] = byte(rand.Intn(255))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func randKeys() [][]byte {
|
||||
var keys [][]byte
|
||||
var count = rand.Intn(2) + 2
|
||||
for i := 0; i < count; i++ {
|
||||
keys = append(keys, randKey())
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func randValue() []byte {
|
||||
n := rand.Intn(8192)
|
||||
b := make([]byte, n)
|
||||
for i := 0; i < n; i++ {
|
||||
b[i] = byte(rand.Intn(255))
|
||||
}
|
||||
return b
|
||||
}
|
||||
-716
@@ -1,716 +0,0 @@
|
||||
package bolt_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
// Ensure that committing a closed transaction returns an error.
|
||||
func TestTx_Commit_ErrTxClosed(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
tx, err := db.Begin(true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := tx.CreateBucket([]byte("foo")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != bolt.ErrTxClosed {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that rolling back a closed transaction returns an error.
|
||||
func TestTx_Rollback_ErrTxClosed(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
tx, err := db.Begin(true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := tx.Rollback(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tx.Rollback(); err != bolt.ErrTxClosed {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that committing a read-only transaction returns an error.
|
||||
func TestTx_Commit_ErrTxNotWritable(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
tx, err := db.Begin(false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tx.Commit(); err != bolt.ErrTxNotWritable {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a transaction can retrieve a cursor on the root bucket.
|
||||
func TestTx_Cursor(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := tx.CreateBucket([]byte("woojits")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
c := tx.Cursor()
|
||||
if k, v := c.First(); !bytes.Equal(k, []byte("widgets")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if v != nil {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
|
||||
if k, v := c.Next(); !bytes.Equal(k, []byte("woojits")) {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if v != nil {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
|
||||
if k, v := c.Next(); k != nil {
|
||||
t.Fatalf("unexpected key: %v", k)
|
||||
} else if v != nil {
|
||||
t.Fatalf("unexpected value: %v", k)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that creating a bucket with a read-only transaction returns an error.
|
||||
func TestTx_CreateBucket_ErrTxNotWritable(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucket([]byte("foo"))
|
||||
if err != bolt.ErrTxNotWritable {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that creating a bucket on a closed transaction returns an error.
|
||||
func TestTx_CreateBucket_ErrTxClosed(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
tx, err := db.Begin(true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := tx.CreateBucket([]byte("foo")); err != bolt.ErrTxClosed {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a Tx can retrieve a bucket.
|
||||
func TestTx_Bucket(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tx.Bucket([]byte("widgets")) == nil {
|
||||
t.Fatal("expected bucket")
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a Tx retrieving a non-existent key returns nil.
|
||||
func TestTx_Get_NotFound(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if b.Get([]byte("no_such_key")) != nil {
|
||||
t.Fatal("expected nil value")
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a bucket can be created and retrieved.
|
||||
func TestTx_CreateBucket(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
// Create a bucket.
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if b == nil {
|
||||
t.Fatal("expected bucket")
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Read the bucket through a separate transaction.
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
if tx.Bucket([]byte("widgets")) == nil {
|
||||
t.Fatal("expected bucket")
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a bucket can be created if it doesn't already exist.
|
||||
func TestTx_CreateBucketIfNotExists(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
// Create bucket.
|
||||
if b, err := tx.CreateBucketIfNotExists([]byte("widgets")); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if b == nil {
|
||||
t.Fatal("expected bucket")
|
||||
}
|
||||
|
||||
// Create bucket again.
|
||||
if b, err := tx.CreateBucketIfNotExists([]byte("widgets")); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if b == nil {
|
||||
t.Fatal("expected bucket")
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Read the bucket through a separate transaction.
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
if tx.Bucket([]byte("widgets")) == nil {
|
||||
t.Fatal("expected bucket")
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure transaction returns an error if creating an unnamed bucket.
|
||||
func TestTx_CreateBucketIfNotExists_ErrBucketNameRequired(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
if _, err := tx.CreateBucketIfNotExists([]byte{}); err != bolt.ErrBucketNameRequired {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if _, err := tx.CreateBucketIfNotExists(nil); err != bolt.ErrBucketNameRequired {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a bucket cannot be created twice.
|
||||
func TestTx_CreateBucket_ErrBucketExists(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
// Create a bucket.
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create the same bucket again.
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
if _, err := tx.CreateBucket([]byte("widgets")); err != bolt.ErrBucketExists {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a bucket is created with a non-blank name.
|
||||
func TestTx_CreateBucket_ErrBucketNameRequired(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
if _, err := tx.CreateBucket(nil); err != bolt.ErrBucketNameRequired {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that a bucket can be deleted.
|
||||
func TestTx_DeleteBucket(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
// Create a bucket and add a value.
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Delete the bucket and make sure we can't get the value.
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
if err := tx.DeleteBucket([]byte("widgets")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tx.Bucket([]byte("widgets")) != nil {
|
||||
t.Fatal("unexpected bucket")
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
// Create the bucket again and make sure there's not a phantom value.
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v := b.Get([]byte("foo")); v != nil {
|
||||
t.Fatalf("unexpected phantom value: %v", v)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that deleting a bucket on a closed transaction returns an error.
|
||||
func TestTx_DeleteBucket_ErrTxClosed(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
tx, err := db.Begin(true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tx.DeleteBucket([]byte("foo")); err != bolt.ErrTxClosed {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that deleting a bucket with a read-only transaction returns an error.
|
||||
func TestTx_DeleteBucket_ReadOnly(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
if err := tx.DeleteBucket([]byte("foo")); err != bolt.ErrTxNotWritable {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that nothing happens when deleting a bucket that doesn't exist.
|
||||
func TestTx_DeleteBucket_NotFound(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
if err := tx.DeleteBucket([]byte("widgets")); err != bolt.ErrBucketNotFound {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that no error is returned when a tx.ForEach function does not return
|
||||
// an error.
|
||||
func TestTx_ForEach_NoError(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := tx.ForEach(func(name []byte, b *bolt.Bucket) error {
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that an error is returned when a tx.ForEach function returns an error.
|
||||
func TestTx_ForEach_WithError(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
marker := errors.New("marker")
|
||||
if err := tx.ForEach(func(name []byte, b *bolt.Bucket) error {
|
||||
return marker
|
||||
}); err != marker {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that Tx commit handlers are called after a transaction successfully commits.
|
||||
func TestTx_OnCommit(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
var x int
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
tx.OnCommit(func() { x += 1 })
|
||||
tx.OnCommit(func() { x += 2 })
|
||||
if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if x != 3 {
|
||||
t.Fatalf("unexpected x: %d", x)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that Tx commit handlers are NOT called after a transaction rolls back.
|
||||
func TestTx_OnCommit_Rollback(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
var x int
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
tx.OnCommit(func() { x += 1 })
|
||||
tx.OnCommit(func() { x += 2 })
|
||||
if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return errors.New("rollback this commit")
|
||||
}); err == nil || err.Error() != "rollback this commit" {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
} else if x != 0 {
|
||||
t.Fatalf("unexpected x: %d", x)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the database can be copied to a file path.
|
||||
func TestTx_CopyFile(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
|
||||
path := tempfile()
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("baz"), []byte("bat")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
return tx.CopyFile(path, 0600)
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db2, err := bolt.Open(path, 0600, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db2.View(func(tx *bolt.Tx) error {
|
||||
if v := tx.Bucket([]byte("widgets")).Get([]byte("foo")); !bytes.Equal(v, []byte("bar")) {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
if v := tx.Bucket([]byte("widgets")).Get([]byte("baz")); !bytes.Equal(v, []byte("bat")) {
|
||||
t.Fatalf("unexpected value: %v", v)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db2.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
type failWriterError struct{}
|
||||
|
||||
func (failWriterError) Error() string {
|
||||
return "error injected for tests"
|
||||
}
|
||||
|
||||
type failWriter struct {
|
||||
// fail after this many bytes
|
||||
After int
|
||||
}
|
||||
|
||||
func (f *failWriter) Write(p []byte) (n int, err error) {
|
||||
n = len(p)
|
||||
if n > f.After {
|
||||
n = f.After
|
||||
err = failWriterError{}
|
||||
}
|
||||
f.After -= n
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Ensure that Copy handles write errors right.
|
||||
func TestTx_CopyFile_Error_Meta(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("baz"), []byte("bat")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
return tx.Copy(&failWriter{})
|
||||
}); err == nil || err.Error() != "meta 0 copy: error injected for tests" {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that Copy handles write errors right.
|
||||
func TestTx_CopyFile_Error_Normal(t *testing.T) {
|
||||
db := MustOpenDB()
|
||||
defer db.MustClose()
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.Put([]byte("baz"), []byte("bat")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
return tx.Copy(&failWriter{3 * db.Info().PageSize})
|
||||
}); err == nil || err.Error() != "error injected for tests" {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleTx_Rollback() {
|
||||
// Open the database.
|
||||
db, err := bolt.Open(tempfile(), 0666, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(db.Path())
|
||||
|
||||
// Create a bucket.
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucket([]byte("widgets"))
|
||||
return err
|
||||
}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Set a value for a key.
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
return tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar"))
|
||||
}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Update the key but rollback the transaction so it never saves.
|
||||
tx, err := db.Begin(true)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
b := tx.Bucket([]byte("widgets"))
|
||||
if err := b.Put([]byte("foo"), []byte("baz")); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := tx.Rollback(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Ensure that our original value is still set.
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
|
||||
fmt.Printf("The value for 'foo' is still: %s\n", value)
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Close database to release file lock.
|
||||
if err := db.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// The value for 'foo' is still: bar
|
||||
}
|
||||
|
||||
func ExampleTx_CopyFile() {
|
||||
// Open the database.
|
||||
db, err := bolt.Open(tempfile(), 0666, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(db.Path())
|
||||
|
||||
// Create a bucket and a key.
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
b, err := tx.CreateBucket([]byte("widgets"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Copy the database to another file.
|
||||
toFile := tempfile()
|
||||
if err := db.View(func(tx *bolt.Tx) error {
|
||||
return tx.CopyFile(toFile, 0666)
|
||||
}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.Remove(toFile)
|
||||
|
||||
// Open the cloned database.
|
||||
db2, err := bolt.Open(toFile, 0666, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Ensure that the key exists in the copy.
|
||||
if err := db2.View(func(tx *bolt.Tx) error {
|
||||
value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
|
||||
fmt.Printf("The value for 'foo' in the clone is: %s\n", value)
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Close database to release file lock.
|
||||
if err := db.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := db2.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// The value for 'foo' in the clone is: bar
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.5.4
|
||||
- 1.6.3
|
||||
- 1.7
|
||||
install:
|
||||
- go get -v golang.org/x/tools/cmd/cover
|
||||
script:
|
||||
- go test -v -tags=safe ./spew
|
||||
- go test -v -tags=testcgo ./spew -covermode=count -coverprofile=profile.cov
|
||||
after_success:
|
||||
- go get -v github.com/mattn/goveralls
|
||||
- export PATH=$PATH:$HOME/gopath/bin
|
||||
- goveralls -coverprofile=profile.cov -service=travis-ci
|
||||
-205
@@ -1,205 +0,0 @@
|
||||
go-spew
|
||||
=======
|
||||
|
||||
[]
|
||||
(https://travis-ci.org/davecgh/go-spew) [![ISC License]
|
||||
(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) [![Coverage Status]
|
||||
(https://img.shields.io/coveralls/davecgh/go-spew.svg)]
|
||||
(https://coveralls.io/r/davecgh/go-spew?branch=master)
|
||||
|
||||
|
||||
Go-spew implements a deep pretty printer for Go data structures to aid in
|
||||
debugging. A comprehensive suite of tests with 100% test coverage is provided
|
||||
to ensure proper functionality. See `test_coverage.txt` for the gocov coverage
|
||||
report. Go-spew is licensed under the liberal ISC license, so it may be used in
|
||||
open source or commercial projects.
|
||||
|
||||
If you're interested in reading about how this package came to life and some
|
||||
of the challenges involved in providing a deep pretty printer, there is a blog
|
||||
post about it
|
||||
[here](https://web.archive.org/web/20160304013555/https://blog.cyphertite.com/go-spew-a-journey-into-dumping-go-data-structures/).
|
||||
|
||||
## Documentation
|
||||
|
||||
[]
|
||||
(http://godoc.org/github.com/davecgh/go-spew/spew)
|
||||
|
||||
Full `go doc` style documentation for the project can be viewed online without
|
||||
installing this package by using the excellent GoDoc site here:
|
||||
http://godoc.org/github.com/davecgh/go-spew/spew
|
||||
|
||||
You can also view the documentation locally once the package is installed with
|
||||
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||
http://localhost:6060/pkg/github.com/davecgh/go-spew/spew
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ go get -u github.com/davecgh/go-spew/spew
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
Add this import line to the file you're working in:
|
||||
|
||||
```Go
|
||||
import "github.com/davecgh/go-spew/spew"
|
||||
```
|
||||
|
||||
To dump a variable with full newlines, indentation, type, and pointer
|
||||
information use Dump, Fdump, or Sdump:
|
||||
|
||||
```Go
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
```
|
||||
|
||||
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||
printing style, use the convenience wrappers Printf, Fprintf, etc with %v (most
|
||||
compact), %+v (adds pointer addresses), %#v (adds types), or %#+v (adds types
|
||||
and pointer addresses):
|
||||
|
||||
```Go
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
```
|
||||
|
||||
## Debugging a Web Application Example
|
||||
|
||||
Here is an example of how you can use `spew.Sdump()` to help debug a web application. Please be sure to wrap your output using the `html.EscapeString()` function for safety reasons. You should also only use this debugging technique in a development environment, never in production.
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
fmt.Fprintf(w, "Hi there, %s!", r.URL.Path[1:])
|
||||
fmt.Fprintf(w, "<!--\n" + html.EscapeString(spew.Sdump(w)) + "\n-->")
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", handler)
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
```
|
||||
|
||||
## Sample Dump Output
|
||||
|
||||
```
|
||||
(main.Foo) {
|
||||
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||
flag: (main.Flag) flagTwo,
|
||||
data: (uintptr) <nil>
|
||||
}),
|
||||
ExportedField: (map[interface {}]interface {}) {
|
||||
(string) "one": (bool) true
|
||||
}
|
||||
}
|
||||
([]uint8) {
|
||||
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||
00000020 31 32 |12|
|
||||
}
|
||||
```
|
||||
|
||||
## Sample Formatter Output
|
||||
|
||||
Double pointer to a uint8:
|
||||
```
|
||||
%v: <**>5
|
||||
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||
%#v: (**uint8)5
|
||||
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||
```
|
||||
|
||||
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||
```
|
||||
%v: <*>{1 <*><shown>}
|
||||
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
Configuration of spew is handled by fields in the ConfigState type. For
|
||||
convenience, all of the top-level functions use a global state available via the
|
||||
spew.Config global.
|
||||
|
||||
It is also possible to create a ConfigState instance that provides methods
|
||||
equivalent to the top-level functions. This allows concurrent configuration
|
||||
options. See the ConfigState documentation for more details.
|
||||
|
||||
```
|
||||
* Indent
|
||||
String to use for each indentation level for Dump functions.
|
||||
It is a single space by default. A popular alternative is "\t".
|
||||
|
||||
* MaxDepth
|
||||
Maximum number of levels to descend into nested data structures.
|
||||
There is no limit by default.
|
||||
|
||||
* DisableMethods
|
||||
Disables invocation of error and Stringer interface methods.
|
||||
Method invocation is enabled by default.
|
||||
|
||||
* DisablePointerMethods
|
||||
Disables invocation of error and Stringer interface methods on types
|
||||
which only accept pointer receivers from non-pointer variables. This option
|
||||
relies on access to the unsafe package, so it will not have any effect when
|
||||
running in environments without access to the unsafe package such as Google
|
||||
App Engine or with the "safe" build tag specified.
|
||||
Pointer method invocation is enabled by default.
|
||||
|
||||
* DisablePointerAddresses
|
||||
DisablePointerAddresses specifies whether to disable the printing of
|
||||
pointer addresses. This is useful when diffing data structures in tests.
|
||||
|
||||
* DisableCapacities
|
||||
DisableCapacities specifies whether to disable the printing of capacities
|
||||
for arrays, slices, maps and channels. This is useful when diffing data
|
||||
structures in tests.
|
||||
|
||||
* ContinueOnMethod
|
||||
Enables recursion into types after invoking error and Stringer interface
|
||||
methods. Recursion after method invocation is disabled by default.
|
||||
|
||||
* SortKeys
|
||||
Specifies map keys should be sorted before being printed. Use
|
||||
this to have a more deterministic, diffable output. Note that
|
||||
only native types (bool, int, uint, floats, uintptr and string)
|
||||
and types which implement error or Stringer interfaces are supported,
|
||||
with other types sorted according to the reflect.Value.String() output
|
||||
which guarantees display stability. Natural map order is used by
|
||||
default.
|
||||
|
||||
* SpewKeys
|
||||
SpewKeys specifies that, as a last resort attempt, map keys should be
|
||||
spewed to strings and sorted by those strings. This is only considered
|
||||
if SortKeys is true.
|
||||
|
||||
```
|
||||
|
||||
## Unsafe Package Dependency
|
||||
|
||||
This package relies on the unsafe package to perform some of the more advanced
|
||||
features, however it also supports a "limited" mode which allows it to work in
|
||||
environments where the unsafe package is not available. By default, it will
|
||||
operate in this mode on Google App Engine and when compiled with GopherJS. The
|
||||
"safe" build tag may also be specified to force the package to build without
|
||||
using the unsafe package.
|
||||
|
||||
## License
|
||||
|
||||
Go-spew is licensed under the [copyfree](http://copyfree.org) ISC License.
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This script uses gocov to generate a test coverage report.
|
||||
# The gocov tool my be obtained with the following command:
|
||||
# go get github.com/axw/gocov/gocov
|
||||
#
|
||||
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
|
||||
|
||||
# Check for gocov.
|
||||
if ! type gocov >/dev/null 2>&1; then
|
||||
echo >&2 "This script requires the gocov tool."
|
||||
echo >&2 "You may obtain it with the following command:"
|
||||
echo >&2 "go get github.com/axw/gocov/gocov"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Only run the cgo tests if gcc is installed.
|
||||
if type gcc >/dev/null 2>&1; then
|
||||
(cd spew && gocov test -tags testcgo | gocov report)
|
||||
else
|
||||
(cd spew && gocov test | gocov report)
|
||||
fi
|
||||
-298
@@ -1,298 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// custom type to test Stinger interface on non-pointer receiver.
|
||||
type stringer string
|
||||
|
||||
// String implements the Stringer interface for testing invocation of custom
|
||||
// stringers on types with non-pointer receivers.
|
||||
func (s stringer) String() string {
|
||||
return "stringer " + string(s)
|
||||
}
|
||||
|
||||
// custom type to test Stinger interface on pointer receiver.
|
||||
type pstringer string
|
||||
|
||||
// String implements the Stringer interface for testing invocation of custom
|
||||
// stringers on types with only pointer receivers.
|
||||
func (s *pstringer) String() string {
|
||||
return "stringer " + string(*s)
|
||||
}
|
||||
|
||||
// xref1 and xref2 are cross referencing structs for testing circular reference
|
||||
// detection.
|
||||
type xref1 struct {
|
||||
ps2 *xref2
|
||||
}
|
||||
type xref2 struct {
|
||||
ps1 *xref1
|
||||
}
|
||||
|
||||
// indirCir1, indirCir2, and indirCir3 are used to generate an indirect circular
|
||||
// reference for testing detection.
|
||||
type indirCir1 struct {
|
||||
ps2 *indirCir2
|
||||
}
|
||||
type indirCir2 struct {
|
||||
ps3 *indirCir3
|
||||
}
|
||||
type indirCir3 struct {
|
||||
ps1 *indirCir1
|
||||
}
|
||||
|
||||
// embed is used to test embedded structures.
|
||||
type embed struct {
|
||||
a string
|
||||
}
|
||||
|
||||
// embedwrap is used to test embedded structures.
|
||||
type embedwrap struct {
|
||||
*embed
|
||||
e *embed
|
||||
}
|
||||
|
||||
// panicer is used to intentionally cause a panic for testing spew properly
|
||||
// handles them
|
||||
type panicer int
|
||||
|
||||
func (p panicer) String() string {
|
||||
panic("test panic")
|
||||
}
|
||||
|
||||
// customError is used to test custom error interface invocation.
|
||||
type customError int
|
||||
|
||||
func (e customError) Error() string {
|
||||
return fmt.Sprintf("error: %d", int(e))
|
||||
}
|
||||
|
||||
// stringizeWants converts a slice of wanted test output into a format suitable
|
||||
// for a test error message.
|
||||
func stringizeWants(wants []string) string {
|
||||
s := ""
|
||||
for i, want := range wants {
|
||||
if i > 0 {
|
||||
s += fmt.Sprintf("want%d: %s", i+1, want)
|
||||
} else {
|
||||
s += "want: " + want
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// testFailed returns whether or not a test failed by checking if the result
|
||||
// of the test is in the slice of wanted strings.
|
||||
func testFailed(result string, wants []string) bool {
|
||||
for _, want := range wants {
|
||||
if result == want {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type sortableStruct struct {
|
||||
x int
|
||||
}
|
||||
|
||||
func (ss sortableStruct) String() string {
|
||||
return fmt.Sprintf("ss.%d", ss.x)
|
||||
}
|
||||
|
||||
type unsortableStruct struct {
|
||||
x int
|
||||
}
|
||||
|
||||
type sortTestCase struct {
|
||||
input []reflect.Value
|
||||
expected []reflect.Value
|
||||
}
|
||||
|
||||
func helpTestSortValues(tests []sortTestCase, cs *spew.ConfigState, t *testing.T) {
|
||||
getInterfaces := func(values []reflect.Value) []interface{} {
|
||||
interfaces := []interface{}{}
|
||||
for _, v := range values {
|
||||
interfaces = append(interfaces, v.Interface())
|
||||
}
|
||||
return interfaces
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
spew.SortValues(test.input, cs)
|
||||
// reflect.DeepEqual cannot really make sense of reflect.Value,
|
||||
// probably because of all the pointer tricks. For instance,
|
||||
// v(2.0) != v(2.0) on a 32-bits system. Turn them into interface{}
|
||||
// instead.
|
||||
input := getInterfaces(test.input)
|
||||
expected := getInterfaces(test.expected)
|
||||
if !reflect.DeepEqual(input, expected) {
|
||||
t.Errorf("Sort mismatch:\n %v != %v", input, expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestSortValues ensures the sort functionality for relect.Value based sorting
|
||||
// works as intended.
|
||||
func TestSortValues(t *testing.T) {
|
||||
v := reflect.ValueOf
|
||||
|
||||
a := v("a")
|
||||
b := v("b")
|
||||
c := v("c")
|
||||
embedA := v(embed{"a"})
|
||||
embedB := v(embed{"b"})
|
||||
embedC := v(embed{"c"})
|
||||
tests := []sortTestCase{
|
||||
// No values.
|
||||
{
|
||||
[]reflect.Value{},
|
||||
[]reflect.Value{},
|
||||
},
|
||||
// Bools.
|
||||
{
|
||||
[]reflect.Value{v(false), v(true), v(false)},
|
||||
[]reflect.Value{v(false), v(false), v(true)},
|
||||
},
|
||||
// Ints.
|
||||
{
|
||||
[]reflect.Value{v(2), v(1), v(3)},
|
||||
[]reflect.Value{v(1), v(2), v(3)},
|
||||
},
|
||||
// Uints.
|
||||
{
|
||||
[]reflect.Value{v(uint8(2)), v(uint8(1)), v(uint8(3))},
|
||||
[]reflect.Value{v(uint8(1)), v(uint8(2)), v(uint8(3))},
|
||||
},
|
||||
// Floats.
|
||||
{
|
||||
[]reflect.Value{v(2.0), v(1.0), v(3.0)},
|
||||
[]reflect.Value{v(1.0), v(2.0), v(3.0)},
|
||||
},
|
||||
// Strings.
|
||||
{
|
||||
[]reflect.Value{b, a, c},
|
||||
[]reflect.Value{a, b, c},
|
||||
},
|
||||
// Array
|
||||
{
|
||||
[]reflect.Value{v([3]int{3, 2, 1}), v([3]int{1, 3, 2}), v([3]int{1, 2, 3})},
|
||||
[]reflect.Value{v([3]int{1, 2, 3}), v([3]int{1, 3, 2}), v([3]int{3, 2, 1})},
|
||||
},
|
||||
// Uintptrs.
|
||||
{
|
||||
[]reflect.Value{v(uintptr(2)), v(uintptr(1)), v(uintptr(3))},
|
||||
[]reflect.Value{v(uintptr(1)), v(uintptr(2)), v(uintptr(3))},
|
||||
},
|
||||
// SortableStructs.
|
||||
{
|
||||
// Note: not sorted - DisableMethods is set.
|
||||
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||
},
|
||||
// UnsortableStructs.
|
||||
{
|
||||
// Note: not sorted - SpewKeys is false.
|
||||
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||
},
|
||||
// Invalid.
|
||||
{
|
||||
[]reflect.Value{embedB, embedA, embedC},
|
||||
[]reflect.Value{embedB, embedA, embedC},
|
||||
},
|
||||
}
|
||||
cs := spew.ConfigState{DisableMethods: true, SpewKeys: false}
|
||||
helpTestSortValues(tests, &cs, t)
|
||||
}
|
||||
|
||||
// TestSortValuesWithMethods ensures the sort functionality for relect.Value
|
||||
// based sorting works as intended when using string methods.
|
||||
func TestSortValuesWithMethods(t *testing.T) {
|
||||
v := reflect.ValueOf
|
||||
|
||||
a := v("a")
|
||||
b := v("b")
|
||||
c := v("c")
|
||||
tests := []sortTestCase{
|
||||
// Ints.
|
||||
{
|
||||
[]reflect.Value{v(2), v(1), v(3)},
|
||||
[]reflect.Value{v(1), v(2), v(3)},
|
||||
},
|
||||
// Strings.
|
||||
{
|
||||
[]reflect.Value{b, a, c},
|
||||
[]reflect.Value{a, b, c},
|
||||
},
|
||||
// SortableStructs.
|
||||
{
|
||||
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
|
||||
},
|
||||
// UnsortableStructs.
|
||||
{
|
||||
// Note: not sorted - SpewKeys is false.
|
||||
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||
},
|
||||
}
|
||||
cs := spew.ConfigState{DisableMethods: false, SpewKeys: false}
|
||||
helpTestSortValues(tests, &cs, t)
|
||||
}
|
||||
|
||||
// TestSortValuesWithSpew ensures the sort functionality for relect.Value
|
||||
// based sorting works as intended when using spew to stringify keys.
|
||||
func TestSortValuesWithSpew(t *testing.T) {
|
||||
v := reflect.ValueOf
|
||||
|
||||
a := v("a")
|
||||
b := v("b")
|
||||
c := v("c")
|
||||
tests := []sortTestCase{
|
||||
// Ints.
|
||||
{
|
||||
[]reflect.Value{v(2), v(1), v(3)},
|
||||
[]reflect.Value{v(1), v(2), v(3)},
|
||||
},
|
||||
// Strings.
|
||||
{
|
||||
[]reflect.Value{b, a, c},
|
||||
[]reflect.Value{a, b, c},
|
||||
},
|
||||
// SortableStructs.
|
||||
{
|
||||
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
|
||||
},
|
||||
// UnsortableStructs.
|
||||
{
|
||||
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||
[]reflect.Value{v(unsortableStruct{1}), v(unsortableStruct{2}), v(unsortableStruct{3})},
|
||||
},
|
||||
}
|
||||
cs := spew.ConfigState{DisableMethods: true, SpewKeys: true}
|
||||
helpTestSortValues(tests, &cs, t)
|
||||
}
|
||||
-1042
File diff suppressed because it is too large
Load Diff
-99
@@ -1,99 +0,0 @@
|
||||
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when both cgo is supported and "-tags testcgo" is added to the go test
|
||||
// command line. This means the cgo tests are only added (and hence run) when
|
||||
// specifially requested. This configuration is used because spew itself
|
||||
// does not require cgo to run even though it does handle certain cgo types
|
||||
// specially. Rather than forcing all clients to require cgo and an external
|
||||
// C compiler just to run the tests, this scheme makes them optional.
|
||||
// +build cgo,testcgo
|
||||
|
||||
package spew_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/davecgh/go-spew/spew/testdata"
|
||||
)
|
||||
|
||||
func addCgoDumpTests() {
|
||||
// C char pointer.
|
||||
v := testdata.GetCgoCharPointer()
|
||||
nv := testdata.GetCgoNullCharPointer()
|
||||
pv := &v
|
||||
vcAddr := fmt.Sprintf("%p", v)
|
||||
vAddr := fmt.Sprintf("%p", pv)
|
||||
pvAddr := fmt.Sprintf("%p", &pv)
|
||||
vt := "*testdata._Ctype_char"
|
||||
vs := "116"
|
||||
addDumpTest(v, "("+vt+")("+vcAddr+")("+vs+")\n")
|
||||
addDumpTest(pv, "(*"+vt+")("+vAddr+"->"+vcAddr+")("+vs+")\n")
|
||||
addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+"->"+vcAddr+")("+vs+")\n")
|
||||
addDumpTest(nv, "("+vt+")(<nil>)\n")
|
||||
|
||||
// C char array.
|
||||
v2, v2l, v2c := testdata.GetCgoCharArray()
|
||||
v2Len := fmt.Sprintf("%d", v2l)
|
||||
v2Cap := fmt.Sprintf("%d", v2c)
|
||||
v2t := "[6]testdata._Ctype_char"
|
||||
v2s := "(len=" + v2Len + " cap=" + v2Cap + ") " +
|
||||
"{\n 00000000 74 65 73 74 32 00 " +
|
||||
" |test2.|\n}"
|
||||
addDumpTest(v2, "("+v2t+") "+v2s+"\n")
|
||||
|
||||
// C unsigned char array.
|
||||
v3, v3l, v3c := testdata.GetCgoUnsignedCharArray()
|
||||
v3Len := fmt.Sprintf("%d", v3l)
|
||||
v3Cap := fmt.Sprintf("%d", v3c)
|
||||
v3t := "[6]testdata._Ctype_unsignedchar"
|
||||
v3t2 := "[6]testdata._Ctype_uchar"
|
||||
v3s := "(len=" + v3Len + " cap=" + v3Cap + ") " +
|
||||
"{\n 00000000 74 65 73 74 33 00 " +
|
||||
" |test3.|\n}"
|
||||
addDumpTest(v3, "("+v3t+") "+v3s+"\n", "("+v3t2+") "+v3s+"\n")
|
||||
|
||||
// C signed char array.
|
||||
v4, v4l, v4c := testdata.GetCgoSignedCharArray()
|
||||
v4Len := fmt.Sprintf("%d", v4l)
|
||||
v4Cap := fmt.Sprintf("%d", v4c)
|
||||
v4t := "[6]testdata._Ctype_schar"
|
||||
v4t2 := "testdata._Ctype_schar"
|
||||
v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " +
|
||||
"{\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 101,\n (" + v4t2 +
|
||||
") 115,\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 52,\n (" + v4t2 +
|
||||
") 0\n}"
|
||||
addDumpTest(v4, "("+v4t+") "+v4s+"\n")
|
||||
|
||||
// C uint8_t array.
|
||||
v5, v5l, v5c := testdata.GetCgoUint8tArray()
|
||||
v5Len := fmt.Sprintf("%d", v5l)
|
||||
v5Cap := fmt.Sprintf("%d", v5c)
|
||||
v5t := "[6]testdata._Ctype_uint8_t"
|
||||
v5s := "(len=" + v5Len + " cap=" + v5Cap + ") " +
|
||||
"{\n 00000000 74 65 73 74 35 00 " +
|
||||
" |test5.|\n}"
|
||||
addDumpTest(v5, "("+v5t+") "+v5s+"\n")
|
||||
|
||||
// C typedefed unsigned char array.
|
||||
v6, v6l, v6c := testdata.GetCgoTypdefedUnsignedCharArray()
|
||||
v6Len := fmt.Sprintf("%d", v6l)
|
||||
v6Cap := fmt.Sprintf("%d", v6c)
|
||||
v6t := "[6]testdata._Ctype_custom_uchar_t"
|
||||
v6s := "(len=" + v6Len + " cap=" + v6Cap + ") " +
|
||||
"{\n 00000000 74 65 73 74 36 00 " +
|
||||
" |test6.|\n}"
|
||||
addDumpTest(v6, "("+v6t+") "+v6s+"\n")
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when either cgo is not supported or "-tags testcgo" is not added to the go
|
||||
// test command line. This file intentionally does not setup any cgo tests in
|
||||
// this scenario.
|
||||
// +build !cgo !testcgo
|
||||
|
||||
package spew_test
|
||||
|
||||
func addCgoDumpTests() {
|
||||
// Don't add any tests for cgo since this file is only compiled when
|
||||
// there should not be any cgo tests.
|
||||
}
|
||||
-226
@@ -1,226 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
type Flag int
|
||||
|
||||
const (
|
||||
flagOne Flag = iota
|
||||
flagTwo
|
||||
)
|
||||
|
||||
var flagStrings = map[Flag]string{
|
||||
flagOne: "flagOne",
|
||||
flagTwo: "flagTwo",
|
||||
}
|
||||
|
||||
func (f Flag) String() string {
|
||||
if s, ok := flagStrings[f]; ok {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("Unknown flag (%d)", int(f))
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
data uintptr
|
||||
}
|
||||
|
||||
type Foo struct {
|
||||
unexportedField Bar
|
||||
ExportedField map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// This example demonstrates how to use Dump to dump variables to stdout.
|
||||
func ExampleDump() {
|
||||
// The following package level declarations are assumed for this example:
|
||||
/*
|
||||
type Flag int
|
||||
|
||||
const (
|
||||
flagOne Flag = iota
|
||||
flagTwo
|
||||
)
|
||||
|
||||
var flagStrings = map[Flag]string{
|
||||
flagOne: "flagOne",
|
||||
flagTwo: "flagTwo",
|
||||
}
|
||||
|
||||
func (f Flag) String() string {
|
||||
if s, ok := flagStrings[f]; ok {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("Unknown flag (%d)", int(f))
|
||||
}
|
||||
|
||||
type Bar struct {
|
||||
data uintptr
|
||||
}
|
||||
|
||||
type Foo struct {
|
||||
unexportedField Bar
|
||||
ExportedField map[interface{}]interface{}
|
||||
}
|
||||
*/
|
||||
|
||||
// Setup some sample data structures for the example.
|
||||
bar := Bar{uintptr(0)}
|
||||
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
|
||||
f := Flag(5)
|
||||
b := []byte{
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
|
||||
0x31, 0x32,
|
||||
}
|
||||
|
||||
// Dump!
|
||||
spew.Dump(s1, f, b)
|
||||
|
||||
// Output:
|
||||
// (spew_test.Foo) {
|
||||
// unexportedField: (spew_test.Bar) {
|
||||
// data: (uintptr) <nil>
|
||||
// },
|
||||
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||
// (string) (len=3) "one": (bool) true
|
||||
// }
|
||||
// }
|
||||
// (spew_test.Flag) Unknown flag (5)
|
||||
// ([]uint8) (len=34 cap=34) {
|
||||
// 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||
// 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||
// 00000020 31 32 |12|
|
||||
// }
|
||||
//
|
||||
}
|
||||
|
||||
// This example demonstrates how to use Printf to display a variable with a
|
||||
// format string and inline formatting.
|
||||
func ExamplePrintf() {
|
||||
// Create a double pointer to a uint 8.
|
||||
ui8 := uint8(5)
|
||||
pui8 := &ui8
|
||||
ppui8 := &pui8
|
||||
|
||||
// Create a circular data type.
|
||||
type circular struct {
|
||||
ui8 uint8
|
||||
c *circular
|
||||
}
|
||||
c := circular{ui8: 1}
|
||||
c.c = &c
|
||||
|
||||
// Print!
|
||||
spew.Printf("ppui8: %v\n", ppui8)
|
||||
spew.Printf("circular: %v\n", c)
|
||||
|
||||
// Output:
|
||||
// ppui8: <**>5
|
||||
// circular: {1 <*>{1 <*><shown>}}
|
||||
}
|
||||
|
||||
// This example demonstrates how to use a ConfigState.
|
||||
func ExampleConfigState() {
|
||||
// Modify the indent level of the ConfigState only. The global
|
||||
// configuration is not modified.
|
||||
scs := spew.ConfigState{Indent: "\t"}
|
||||
|
||||
// Output using the ConfigState instance.
|
||||
v := map[string]int{"one": 1}
|
||||
scs.Printf("v: %v\n", v)
|
||||
scs.Dump(v)
|
||||
|
||||
// Output:
|
||||
// v: map[one:1]
|
||||
// (map[string]int) (len=1) {
|
||||
// (string) (len=3) "one": (int) 1
|
||||
// }
|
||||
}
|
||||
|
||||
// This example demonstrates how to use ConfigState.Dump to dump variables to
|
||||
// stdout
|
||||
func ExampleConfigState_Dump() {
|
||||
// See the top-level Dump example for details on the types used in this
|
||||
// example.
|
||||
|
||||
// Create two ConfigState instances with different indentation.
|
||||
scs := spew.ConfigState{Indent: "\t"}
|
||||
scs2 := spew.ConfigState{Indent: " "}
|
||||
|
||||
// Setup some sample data structures for the example.
|
||||
bar := Bar{uintptr(0)}
|
||||
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
|
||||
|
||||
// Dump using the ConfigState instances.
|
||||
scs.Dump(s1)
|
||||
scs2.Dump(s1)
|
||||
|
||||
// Output:
|
||||
// (spew_test.Foo) {
|
||||
// unexportedField: (spew_test.Bar) {
|
||||
// data: (uintptr) <nil>
|
||||
// },
|
||||
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||
// (string) (len=3) "one": (bool) true
|
||||
// }
|
||||
// }
|
||||
// (spew_test.Foo) {
|
||||
// unexportedField: (spew_test.Bar) {
|
||||
// data: (uintptr) <nil>
|
||||
// },
|
||||
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||
// (string) (len=3) "one": (bool) true
|
||||
// }
|
||||
// }
|
||||
//
|
||||
}
|
||||
|
||||
// This example demonstrates how to use ConfigState.Printf to display a variable
|
||||
// with a format string and inline formatting.
|
||||
func ExampleConfigState_Printf() {
|
||||
// See the top-level Dump example for details on the types used in this
|
||||
// example.
|
||||
|
||||
// Create two ConfigState instances and modify the method handling of the
|
||||
// first ConfigState only.
|
||||
scs := spew.NewDefaultConfig()
|
||||
scs2 := spew.NewDefaultConfig()
|
||||
scs.DisableMethods = true
|
||||
|
||||
// Alternatively
|
||||
// scs := spew.ConfigState{Indent: " ", DisableMethods: true}
|
||||
// scs2 := spew.ConfigState{Indent: " "}
|
||||
|
||||
// This is of type Flag which implements a Stringer and has raw value 1.
|
||||
f := flagTwo
|
||||
|
||||
// Dump using the ConfigState instances.
|
||||
scs.Printf("f: %v\n", f)
|
||||
scs2.Printf("f: %v\n", f)
|
||||
|
||||
// Output:
|
||||
// f: 1
|
||||
// f: flagTwo
|
||||
}
|
||||
-1558
File diff suppressed because it is too large
Load Diff
-87
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
This test file is part of the spew package rather than than the spew_test
|
||||
package because it needs access to internals to properly test certain cases
|
||||
which are not possible via the public interface since they should never happen.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// dummyFmtState implements a fake fmt.State to use for testing invalid
|
||||
// reflect.Value handling. This is necessary because the fmt package catches
|
||||
// invalid values before invoking the formatter on them.
|
||||
type dummyFmtState struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func (dfs *dummyFmtState) Flag(f int) bool {
|
||||
if f == int('+') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (dfs *dummyFmtState) Precision() (int, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (dfs *dummyFmtState) Width() (int, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// TestInvalidReflectValue ensures the dump and formatter code handles an
|
||||
// invalid reflect value properly. This needs access to internal state since it
|
||||
// should never happen in real code and therefore can't be tested via the public
|
||||
// API.
|
||||
func TestInvalidReflectValue(t *testing.T) {
|
||||
i := 1
|
||||
|
||||
// Dump invalid reflect value.
|
||||
v := new(reflect.Value)
|
||||
buf := new(bytes.Buffer)
|
||||
d := dumpState{w: buf, cs: &Config}
|
||||
d.dump(*v)
|
||||
s := buf.String()
|
||||
want := "<invalid>"
|
||||
if s != want {
|
||||
t.Errorf("InvalidReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||
}
|
||||
i++
|
||||
|
||||
// Formatter invalid reflect value.
|
||||
buf2 := new(dummyFmtState)
|
||||
f := formatState{value: *v, cs: &Config, fs: buf2}
|
||||
f.format(*v)
|
||||
s = buf2.String()
|
||||
want = "<invalid>"
|
||||
if s != want {
|
||||
t.Errorf("InvalidReflectValue #%d got: %s want: %s", i, s, want)
|
||||
}
|
||||
}
|
||||
|
||||
// SortValues makes the internal sortValues function available to the test
|
||||
// package.
|
||||
func SortValues(values []reflect.Value, cs *ConfigState) {
|
||||
sortValues(values, cs)
|
||||
}
|
||||
-102
@@ -1,102 +0,0 @@
|
||||
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// +build !js,!appengine,!safe,!disableunsafe
|
||||
|
||||
/*
|
||||
This test file is part of the spew package rather than than the spew_test
|
||||
package because it needs access to internals to properly test certain cases
|
||||
which are not possible via the public interface since they should never happen.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// changeKind uses unsafe to intentionally change the kind of a reflect.Value to
|
||||
// the maximum kind value which does not exist. This is needed to test the
|
||||
// fallback code which punts to the standard fmt library for new types that
|
||||
// might get added to the language.
|
||||
func changeKind(v *reflect.Value, readOnly bool) {
|
||||
rvf := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + offsetFlag))
|
||||
*rvf = *rvf | ((1<<flagKindWidth - 1) << flagKindShift)
|
||||
if readOnly {
|
||||
*rvf |= flagRO
|
||||
} else {
|
||||
*rvf &= ^uintptr(flagRO)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAddedReflectValue tests functionaly of the dump and formatter code which
|
||||
// falls back to the standard fmt library for new types that might get added to
|
||||
// the language.
|
||||
func TestAddedReflectValue(t *testing.T) {
|
||||
i := 1
|
||||
|
||||
// Dump using a reflect.Value that is exported.
|
||||
v := reflect.ValueOf(int8(5))
|
||||
changeKind(&v, false)
|
||||
buf := new(bytes.Buffer)
|
||||
d := dumpState{w: buf, cs: &Config}
|
||||
d.dump(v)
|
||||
s := buf.String()
|
||||
want := "(int8) 5"
|
||||
if s != want {
|
||||
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||
}
|
||||
i++
|
||||
|
||||
// Dump using a reflect.Value that is not exported.
|
||||
changeKind(&v, true)
|
||||
buf.Reset()
|
||||
d.dump(v)
|
||||
s = buf.String()
|
||||
want = "(int8) <int8 Value>"
|
||||
if s != want {
|
||||
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||
}
|
||||
i++
|
||||
|
||||
// Formatter using a reflect.Value that is exported.
|
||||
changeKind(&v, false)
|
||||
buf2 := new(dummyFmtState)
|
||||
f := formatState{value: v, cs: &Config, fs: buf2}
|
||||
f.format(v)
|
||||
s = buf2.String()
|
||||
want = "5"
|
||||
if s != want {
|
||||
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
|
||||
}
|
||||
i++
|
||||
|
||||
// Formatter using a reflect.Value that is not exported.
|
||||
changeKind(&v, true)
|
||||
buf2.Reset()
|
||||
f = formatState{value: v, cs: &Config, fs: buf2}
|
||||
f.format(v)
|
||||
s = buf2.String()
|
||||
want = "<int8 Value>"
|
||||
if s != want {
|
||||
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
|
||||
}
|
||||
}
|
||||
-320
@@ -1,320 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// spewFunc is used to identify which public function of the spew package or
|
||||
// ConfigState a test applies to.
|
||||
type spewFunc int
|
||||
|
||||
const (
|
||||
fCSFdump spewFunc = iota
|
||||
fCSFprint
|
||||
fCSFprintf
|
||||
fCSFprintln
|
||||
fCSPrint
|
||||
fCSPrintln
|
||||
fCSSdump
|
||||
fCSSprint
|
||||
fCSSprintf
|
||||
fCSSprintln
|
||||
fCSErrorf
|
||||
fCSNewFormatter
|
||||
fErrorf
|
||||
fFprint
|
||||
fFprintln
|
||||
fPrint
|
||||
fPrintln
|
||||
fSdump
|
||||
fSprint
|
||||
fSprintf
|
||||
fSprintln
|
||||
)
|
||||
|
||||
// Map of spewFunc values to names for pretty printing.
|
||||
var spewFuncStrings = map[spewFunc]string{
|
||||
fCSFdump: "ConfigState.Fdump",
|
||||
fCSFprint: "ConfigState.Fprint",
|
||||
fCSFprintf: "ConfigState.Fprintf",
|
||||
fCSFprintln: "ConfigState.Fprintln",
|
||||
fCSSdump: "ConfigState.Sdump",
|
||||
fCSPrint: "ConfigState.Print",
|
||||
fCSPrintln: "ConfigState.Println",
|
||||
fCSSprint: "ConfigState.Sprint",
|
||||
fCSSprintf: "ConfigState.Sprintf",
|
||||
fCSSprintln: "ConfigState.Sprintln",
|
||||
fCSErrorf: "ConfigState.Errorf",
|
||||
fCSNewFormatter: "ConfigState.NewFormatter",
|
||||
fErrorf: "spew.Errorf",
|
||||
fFprint: "spew.Fprint",
|
||||
fFprintln: "spew.Fprintln",
|
||||
fPrint: "spew.Print",
|
||||
fPrintln: "spew.Println",
|
||||
fSdump: "spew.Sdump",
|
||||
fSprint: "spew.Sprint",
|
||||
fSprintf: "spew.Sprintf",
|
||||
fSprintln: "spew.Sprintln",
|
||||
}
|
||||
|
||||
func (f spewFunc) String() string {
|
||||
if s, ok := spewFuncStrings[f]; ok {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("Unknown spewFunc (%d)", int(f))
|
||||
}
|
||||
|
||||
// spewTest is used to describe a test to be performed against the public
|
||||
// functions of the spew package or ConfigState.
|
||||
type spewTest struct {
|
||||
cs *spew.ConfigState
|
||||
f spewFunc
|
||||
format string
|
||||
in interface{}
|
||||
want string
|
||||
}
|
||||
|
||||
// spewTests houses the tests to be performed against the public functions of
|
||||
// the spew package and ConfigState.
|
||||
//
|
||||
// These tests are only intended to ensure the public functions are exercised
|
||||
// and are intentionally not exhaustive of types. The exhaustive type
|
||||
// tests are handled in the dump and format tests.
|
||||
var spewTests []spewTest
|
||||
|
||||
// redirStdout is a helper function to return the standard output from f as a
|
||||
// byte slice.
|
||||
func redirStdout(f func()) ([]byte, error) {
|
||||
tempFile, err := ioutil.TempFile("", "ss-test")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fileName := tempFile.Name()
|
||||
defer os.Remove(fileName) // Ignore error
|
||||
|
||||
origStdout := os.Stdout
|
||||
os.Stdout = tempFile
|
||||
f()
|
||||
os.Stdout = origStdout
|
||||
tempFile.Close()
|
||||
|
||||
return ioutil.ReadFile(fileName)
|
||||
}
|
||||
|
||||
func initSpewTests() {
|
||||
// Config states with various settings.
|
||||
scsDefault := spew.NewDefaultConfig()
|
||||
scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true}
|
||||
scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true}
|
||||
scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1}
|
||||
scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true}
|
||||
scsNoPtrAddr := &spew.ConfigState{DisablePointerAddresses: true}
|
||||
scsNoCap := &spew.ConfigState{DisableCapacities: true}
|
||||
|
||||
// Variables for tests on types which implement Stringer interface with and
|
||||
// without a pointer receiver.
|
||||
ts := stringer("test")
|
||||
tps := pstringer("test")
|
||||
|
||||
type ptrTester struct {
|
||||
s *struct{}
|
||||
}
|
||||
tptr := &ptrTester{s: &struct{}{}}
|
||||
|
||||
// depthTester is used to test max depth handling for structs, array, slices
|
||||
// and maps.
|
||||
type depthTester struct {
|
||||
ic indirCir1
|
||||
arr [1]string
|
||||
slice []string
|
||||
m map[string]int
|
||||
}
|
||||
dt := depthTester{indirCir1{nil}, [1]string{"arr"}, []string{"slice"},
|
||||
map[string]int{"one": 1}}
|
||||
|
||||
// Variable for tests on types which implement error interface.
|
||||
te := customError(10)
|
||||
|
||||
spewTests = []spewTest{
|
||||
{scsDefault, fCSFdump, "", int8(127), "(int8) 127\n"},
|
||||
{scsDefault, fCSFprint, "", int16(32767), "32767"},
|
||||
{scsDefault, fCSFprintf, "%v", int32(2147483647), "2147483647"},
|
||||
{scsDefault, fCSFprintln, "", int(2147483647), "2147483647\n"},
|
||||
{scsDefault, fCSPrint, "", int64(9223372036854775807), "9223372036854775807"},
|
||||
{scsDefault, fCSPrintln, "", uint8(255), "255\n"},
|
||||
{scsDefault, fCSSdump, "", uint8(64), "(uint8) 64\n"},
|
||||
{scsDefault, fCSSprint, "", complex(1, 2), "(1+2i)"},
|
||||
{scsDefault, fCSSprintf, "%v", complex(float32(3), 4), "(3+4i)"},
|
||||
{scsDefault, fCSSprintln, "", complex(float64(5), 6), "(5+6i)\n"},
|
||||
{scsDefault, fCSErrorf, "%#v", uint16(65535), "(uint16)65535"},
|
||||
{scsDefault, fCSNewFormatter, "%v", uint32(4294967295), "4294967295"},
|
||||
{scsDefault, fErrorf, "%v", uint64(18446744073709551615), "18446744073709551615"},
|
||||
{scsDefault, fFprint, "", float32(3.14), "3.14"},
|
||||
{scsDefault, fFprintln, "", float64(6.28), "6.28\n"},
|
||||
{scsDefault, fPrint, "", true, "true"},
|
||||
{scsDefault, fPrintln, "", false, "false\n"},
|
||||
{scsDefault, fSdump, "", complex(-10, -20), "(complex128) (-10-20i)\n"},
|
||||
{scsDefault, fSprint, "", complex(-1, -2), "(-1-2i)"},
|
||||
{scsDefault, fSprintf, "%v", complex(float32(-3), -4), "(-3-4i)"},
|
||||
{scsDefault, fSprintln, "", complex(float64(-5), -6), "(-5-6i)\n"},
|
||||
{scsNoMethods, fCSFprint, "", ts, "test"},
|
||||
{scsNoMethods, fCSFprint, "", &ts, "<*>test"},
|
||||
{scsNoMethods, fCSFprint, "", tps, "test"},
|
||||
{scsNoMethods, fCSFprint, "", &tps, "<*>test"},
|
||||
{scsNoPmethods, fCSFprint, "", ts, "stringer test"},
|
||||
{scsNoPmethods, fCSFprint, "", &ts, "<*>stringer test"},
|
||||
{scsNoPmethods, fCSFprint, "", tps, "test"},
|
||||
{scsNoPmethods, fCSFprint, "", &tps, "<*>stringer test"},
|
||||
{scsMaxDepth, fCSFprint, "", dt, "{{<max>} [<max>] [<max>] map[<max>]}"},
|
||||
{scsMaxDepth, fCSFdump, "", dt, "(spew_test.depthTester) {\n" +
|
||||
" ic: (spew_test.indirCir1) {\n <max depth reached>\n },\n" +
|
||||
" arr: ([1]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
|
||||
" slice: ([]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
|
||||
" m: (map[string]int) (len=1) {\n <max depth reached>\n }\n}\n"},
|
||||
{scsContinue, fCSFprint, "", ts, "(stringer test) test"},
|
||||
{scsContinue, fCSFdump, "", ts, "(spew_test.stringer) " +
|
||||
"(len=4) (stringer test) \"test\"\n"},
|
||||
{scsContinue, fCSFprint, "", te, "(error: 10) 10"},
|
||||
{scsContinue, fCSFdump, "", te, "(spew_test.customError) " +
|
||||
"(error: 10) 10\n"},
|
||||
{scsNoPtrAddr, fCSFprint, "", tptr, "<*>{<*>{}}"},
|
||||
{scsNoPtrAddr, fCSSdump, "", tptr, "(*spew_test.ptrTester)({\ns: (*struct {})({\n})\n})\n"},
|
||||
{scsNoCap, fCSSdump, "", make([]string, 0, 10), "([]string) {\n}\n"},
|
||||
{scsNoCap, fCSSdump, "", make([]string, 1, 10), "([]string) (len=1) {\n(string) \"\"\n}\n"},
|
||||
}
|
||||
}
|
||||
|
||||
// TestSpew executes all of the tests described by spewTests.
|
||||
func TestSpew(t *testing.T) {
|
||||
initSpewTests()
|
||||
|
||||
t.Logf("Running %d tests", len(spewTests))
|
||||
for i, test := range spewTests {
|
||||
buf := new(bytes.Buffer)
|
||||
switch test.f {
|
||||
case fCSFdump:
|
||||
test.cs.Fdump(buf, test.in)
|
||||
|
||||
case fCSFprint:
|
||||
test.cs.Fprint(buf, test.in)
|
||||
|
||||
case fCSFprintf:
|
||||
test.cs.Fprintf(buf, test.format, test.in)
|
||||
|
||||
case fCSFprintln:
|
||||
test.cs.Fprintln(buf, test.in)
|
||||
|
||||
case fCSPrint:
|
||||
b, err := redirStdout(func() { test.cs.Print(test.in) })
|
||||
if err != nil {
|
||||
t.Errorf("%v #%d %v", test.f, i, err)
|
||||
continue
|
||||
}
|
||||
buf.Write(b)
|
||||
|
||||
case fCSPrintln:
|
||||
b, err := redirStdout(func() { test.cs.Println(test.in) })
|
||||
if err != nil {
|
||||
t.Errorf("%v #%d %v", test.f, i, err)
|
||||
continue
|
||||
}
|
||||
buf.Write(b)
|
||||
|
||||
case fCSSdump:
|
||||
str := test.cs.Sdump(test.in)
|
||||
buf.WriteString(str)
|
||||
|
||||
case fCSSprint:
|
||||
str := test.cs.Sprint(test.in)
|
||||
buf.WriteString(str)
|
||||
|
||||
case fCSSprintf:
|
||||
str := test.cs.Sprintf(test.format, test.in)
|
||||
buf.WriteString(str)
|
||||
|
||||
case fCSSprintln:
|
||||
str := test.cs.Sprintln(test.in)
|
||||
buf.WriteString(str)
|
||||
|
||||
case fCSErrorf:
|
||||
err := test.cs.Errorf(test.format, test.in)
|
||||
buf.WriteString(err.Error())
|
||||
|
||||
case fCSNewFormatter:
|
||||
fmt.Fprintf(buf, test.format, test.cs.NewFormatter(test.in))
|
||||
|
||||
case fErrorf:
|
||||
err := spew.Errorf(test.format, test.in)
|
||||
buf.WriteString(err.Error())
|
||||
|
||||
case fFprint:
|
||||
spew.Fprint(buf, test.in)
|
||||
|
||||
case fFprintln:
|
||||
spew.Fprintln(buf, test.in)
|
||||
|
||||
case fPrint:
|
||||
b, err := redirStdout(func() { spew.Print(test.in) })
|
||||
if err != nil {
|
||||
t.Errorf("%v #%d %v", test.f, i, err)
|
||||
continue
|
||||
}
|
||||
buf.Write(b)
|
||||
|
||||
case fPrintln:
|
||||
b, err := redirStdout(func() { spew.Println(test.in) })
|
||||
if err != nil {
|
||||
t.Errorf("%v #%d %v", test.f, i, err)
|
||||
continue
|
||||
}
|
||||
buf.Write(b)
|
||||
|
||||
case fSdump:
|
||||
str := spew.Sdump(test.in)
|
||||
buf.WriteString(str)
|
||||
|
||||
case fSprint:
|
||||
str := spew.Sprint(test.in)
|
||||
buf.WriteString(str)
|
||||
|
||||
case fSprintf:
|
||||
str := spew.Sprintf(test.format, test.in)
|
||||
buf.WriteString(str)
|
||||
|
||||
case fSprintln:
|
||||
str := spew.Sprintln(test.in)
|
||||
buf.WriteString(str)
|
||||
|
||||
default:
|
||||
t.Errorf("%v #%d unrecognized function", test.f, i)
|
||||
continue
|
||||
}
|
||||
s := buf.String()
|
||||
if test.want != s {
|
||||
t.Errorf("ConfigState #%d\n got: %s want: %s", i, s, test.want)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
-61
@@ -1,61 +0,0 @@
|
||||
|
||||
github.com/davecgh/go-spew/spew/dump.go dumpState.dump 100.00% (88/88)
|
||||
github.com/davecgh/go-spew/spew/format.go formatState.format 100.00% (82/82)
|
||||
github.com/davecgh/go-spew/spew/format.go formatState.formatPtr 100.00% (52/52)
|
||||
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpPtr 100.00% (44/44)
|
||||
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpSlice 100.00% (39/39)
|
||||
github.com/davecgh/go-spew/spew/common.go handleMethods 100.00% (30/30)
|
||||
github.com/davecgh/go-spew/spew/common.go printHexPtr 100.00% (18/18)
|
||||
github.com/davecgh/go-spew/spew/common.go unsafeReflectValue 100.00% (13/13)
|
||||
github.com/davecgh/go-spew/spew/format.go formatState.constructOrigFormat 100.00% (12/12)
|
||||
github.com/davecgh/go-spew/spew/dump.go fdump 100.00% (11/11)
|
||||
github.com/davecgh/go-spew/spew/format.go formatState.Format 100.00% (11/11)
|
||||
github.com/davecgh/go-spew/spew/common.go init 100.00% (10/10)
|
||||
github.com/davecgh/go-spew/spew/common.go printComplex 100.00% (9/9)
|
||||
github.com/davecgh/go-spew/spew/common.go valuesSorter.Less 100.00% (8/8)
|
||||
github.com/davecgh/go-spew/spew/format.go formatState.buildDefaultFormat 100.00% (7/7)
|
||||
github.com/davecgh/go-spew/spew/format.go formatState.unpackValue 100.00% (5/5)
|
||||
github.com/davecgh/go-spew/spew/dump.go dumpState.indent 100.00% (4/4)
|
||||
github.com/davecgh/go-spew/spew/common.go catchPanic 100.00% (4/4)
|
||||
github.com/davecgh/go-spew/spew/config.go ConfigState.convertArgs 100.00% (4/4)
|
||||
github.com/davecgh/go-spew/spew/spew.go convertArgs 100.00% (4/4)
|
||||
github.com/davecgh/go-spew/spew/format.go newFormatter 100.00% (3/3)
|
||||
github.com/davecgh/go-spew/spew/dump.go Sdump 100.00% (3/3)
|
||||
github.com/davecgh/go-spew/spew/common.go printBool 100.00% (3/3)
|
||||
github.com/davecgh/go-spew/spew/common.go sortValues 100.00% (3/3)
|
||||
github.com/davecgh/go-spew/spew/config.go ConfigState.Sdump 100.00% (3/3)
|
||||
github.com/davecgh/go-spew/spew/dump.go dumpState.unpackValue 100.00% (3/3)
|
||||
github.com/davecgh/go-spew/spew/spew.go Printf 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/spew.go Println 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/spew.go Sprint 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/spew.go Sprintf 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/spew.go Sprintln 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/common.go printFloat 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/config.go NewDefaultConfig 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/common.go printInt 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/common.go printUint 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/common.go valuesSorter.Len 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/common.go valuesSorter.Swap 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/config.go ConfigState.Errorf 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprint 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintf 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintln 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/config.go ConfigState.Print 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/config.go ConfigState.Printf 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/config.go ConfigState.Println 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprint 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintf 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintln 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/config.go ConfigState.NewFormatter 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/config.go ConfigState.Fdump 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/config.go ConfigState.Dump 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/dump.go Fdump 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/dump.go Dump 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/spew.go Fprintln 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/format.go NewFormatter 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/spew.go Errorf 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/spew.go Fprint 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/spew.go Fprintf 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew/spew.go Print 100.00% (1/1)
|
||||
github.com/davecgh/go-spew/spew ------------------------------- 100.00% (505/505)
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
/debug
|
||||
/.vscode
|
||||
+118
-19
@@ -5,11 +5,24 @@
|
||||
|
||||
This is a generic middleware to rate-limit HTTP requests.
|
||||
|
||||
**NOTE:** This library is considered finished, any new activities are probably centered around `thirdparty` modules.
|
||||
**NOTE 1:** This library is considered finished.
|
||||
|
||||
**NOTE 2:** In the coming weeks, I will be removing thirdparty modules and moving them to their own dedicated repos.
|
||||
|
||||
**NOTE 3:** Major version changes are backward-incompatible. `v2.0.0` streamlines the ugliness of the old API.
|
||||
|
||||
|
||||
## Versions
|
||||
|
||||
**v1.0.0:** This version maintains the old API but all of the thirdparty modules are moved to their own repo.
|
||||
|
||||
**v2.x.x:** Brand new API for the sake of code cleanup, thread safety, & auto-expiring data structures.
|
||||
|
||||
**v3.x.x:** Apparently we have been using golang.org/x/time/rate incorrectly. See issue #48. It always limit X number per 1 second. The time duration is not changeable, so it does not make sense to pass TTL to tollbooth.
|
||||
|
||||
|
||||
## Five Minutes Tutorial
|
||||
```
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -24,7 +37,7 @@ func HelloHandler(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
func main() {
|
||||
// Create a request limiter per handler.
|
||||
http.Handle("/", tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, time.Second), HelloHandler))
|
||||
http.Handle("/", tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, nil), HelloHandler))
|
||||
http.ListenAndServe(":12345", nil)
|
||||
}
|
||||
```
|
||||
@@ -32,34 +45,120 @@ func main() {
|
||||
## Features
|
||||
|
||||
1. Rate-limit by request's remote IP, path, methods, custom headers, & basic auth usernames.
|
||||
```
|
||||
limiter := tollbooth.NewLimiter(1, time.Second)
|
||||
```go
|
||||
import (
|
||||
"time"
|
||||
"github.com/didip/tollbooth/limiter"
|
||||
)
|
||||
|
||||
lmt := tollbooth.NewLimiter(1, nil)
|
||||
|
||||
// or create a limiter with expirable token buckets
|
||||
// This setting means:
|
||||
// create a 1 request/second limiter and
|
||||
// every token bucket in it will expire 1 hour after it was initially set.
|
||||
lmt = tollbooth.NewLimiter(1, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Hour})
|
||||
|
||||
// Configure list of places to look for IP address.
|
||||
// By default it's: "RemoteAddr", "X-Forwarded-For", "X-Real-IP"
|
||||
// If your application is behind a proxy, set "X-Forwarded-For" first.
|
||||
limiter.IPLookups = []string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}
|
||||
lmt.SetIPLookups([]string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"})
|
||||
|
||||
// Limit only GET and POST requests.
|
||||
limiter.Methods = []string{"GET", "POST"}
|
||||
|
||||
// Limit request headers containing certain values.
|
||||
// Typically, you prefetched these values from the database.
|
||||
limiter.Headers = make(map[string][]string)
|
||||
limiter.Headers["X-Access-Token"] = []string{"abc123", "xyz098"}
|
||||
lmt.SetMethods([]string{"GET", "POST"})
|
||||
|
||||
// Limit based on basic auth usernames.
|
||||
// Typically, you prefetched these values from the database.
|
||||
limiter.BasicAuthUsers = []string{"bob", "joe", "didip"}
|
||||
// You add them on-load, or later as you handle requests.
|
||||
lmt.SetBasicAuthUsers([]string{"bob", "jane", "didip", "vip"})
|
||||
// You can remove them later as well.
|
||||
lmt.RemoveBasicAuthUsers([]string{"vip"})
|
||||
|
||||
// Limit request headers containing certain values.
|
||||
// You add them on-load, or later as you handle requests.
|
||||
lmt.SetHeader("X-Access-Token", []string{"abc123", "xyz098"})
|
||||
// You can remove all entries at once.
|
||||
lmt.RemoveHeader("X-Access-Token")
|
||||
// Or remove specific ones.
|
||||
lmt.RemoveHeaderEntries("X-Access-Token", []string{"limitless-token"})
|
||||
|
||||
// By the way, the setters are chainable. Example:
|
||||
lmt.SetIPLookups([]string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}).
|
||||
SetMethods([]string{"GET", "POST"}).
|
||||
SetBasicAuthUsers([]string{"sansa"}).
|
||||
SetBasicAuthUsers([]string{"tyrion"})
|
||||
```
|
||||
|
||||
2. Each request handler can be rate-limited individually.
|
||||
2. Compose your own middleware by using `LimitByKeys()`.
|
||||
|
||||
3. Compose your own middleware by using `LimitByKeys()`.
|
||||
3. Header entries and basic auth users can expire over time (to conserve memory).
|
||||
|
||||
4. Tollbooth does not require external storage since it uses an algorithm called [Token Bucket](http://en.wikipedia.org/wiki/Token_bucket) [(Go library: ratelimit)](https://github.com/juju/ratelimit).
|
||||
```go
|
||||
import "time"
|
||||
|
||||
lmt := tollbooth.NewLimiter(1, nil)
|
||||
|
||||
// Set a custom expiration TTL for token bucket.
|
||||
lmt.SetTokenBucketExpirationTTL(time.Hour)
|
||||
|
||||
// Set a custom expiration TTL for basic auth users.
|
||||
lmt.SetBasicAuthExpirationTTL(time.Hour)
|
||||
|
||||
// Set a custom expiration TTL for header entries.
|
||||
lmt.SetHeaderEntryExpirationTTL(time.Hour)
|
||||
```
|
||||
|
||||
4. Upon rejection, the following HTTP response headers are available to users:
|
||||
|
||||
* `X-Rate-Limit-Limit` The maximum request limit.
|
||||
|
||||
* `X-Rate-Limit-Duration` The rate-limiter duration.
|
||||
|
||||
* `X-Rate-Limit-Request-Forwarded-For` The rejected request `X-Forwarded-For`.
|
||||
|
||||
* `X-Rate-Limit-Request-Remote-Addr` The rejected request `RemoteAddr`.
|
||||
|
||||
|
||||
# Other Web Frameworks
|
||||
5. Customize your own message or function when limit is reached.
|
||||
|
||||
Support for other web frameworks are defined under `/thirdparty` directory.
|
||||
```go
|
||||
lmt := tollbooth.NewLimiter(1, nil)
|
||||
|
||||
// Set a custom message.
|
||||
lmt.SetMessage("You have reached maximum request limit.")
|
||||
|
||||
// Set a custom content-type.
|
||||
lmt.SetMessageContentType("text/plain; charset=utf-8")
|
||||
|
||||
// Set a custom function for rejection.
|
||||
lmt.SetOnLimitReached(func(w http.ResponseWriter, r *http.Request) { fmt.Println("A request was rejected") })
|
||||
```
|
||||
|
||||
6. Tollbooth does not require external storage since it uses an algorithm called [Token Bucket](http://en.wikipedia.org/wiki/Token_bucket) [(Go library: golang.org/x/time/rate)](//godoc.org/golang.org/x/time/rate).
|
||||
|
||||
|
||||
## Other Web Frameworks
|
||||
|
||||
Sometimes, other frameworks require a little bit of shim to use Tollbooth. These shims below are contributed by the community, so I make no promises on how well they work. The one I am familiar with are: Chi, Gin, and Negroni.
|
||||
|
||||
* [Chi](https://github.com/didip/tollbooth_chi)
|
||||
|
||||
* [Echo](https://github.com/didip/tollbooth_echo)
|
||||
|
||||
* [FastHTTP](https://github.com/didip/tollbooth_fasthttp)
|
||||
|
||||
* [Gin](https://github.com/didip/tollbooth_gin)
|
||||
|
||||
* [GoRestful](https://github.com/didip/tollbooth_gorestful)
|
||||
|
||||
* [HTTPRouter](https://github.com/didip/tollbooth_httprouter)
|
||||
|
||||
* [Iris](https://github.com/didip/tollbooth_iris)
|
||||
|
||||
* [Negroni](https://github.com/didip/tollbooth_negroni)
|
||||
|
||||
|
||||
## My other Go libraries
|
||||
|
||||
* [Stopwatch](https://github.com/didip/stopwatch): A small library to measure latency of things. Useful if you want to report latency data to Graphite.
|
||||
|
||||
* [Gomet](https://github.com/didip/gomet): Simple HTTP client & server long poll library for Go. Useful for receiving live updates without needing Websocket.
|
||||
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
// Package config provides data structure to configure rate-limiter.
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/juju/ratelimit"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewLimiter is a constructor for Limiter.
|
||||
func NewLimiter(max int64, ttl time.Duration) *Limiter {
|
||||
limiter := &Limiter{Max: max, TTL: ttl}
|
||||
limiter.MessageContentType = "text/plain; charset=utf-8"
|
||||
limiter.Message = "You have reached maximum request limit."
|
||||
limiter.StatusCode = 429
|
||||
limiter.tokenBuckets = make(map[string]*ratelimit.Bucket)
|
||||
limiter.IPLookups = []string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}
|
||||
|
||||
return limiter
|
||||
}
|
||||
|
||||
// Limiter is a config struct to limit a particular request handler.
|
||||
type Limiter struct {
|
||||
// HTTP message when limit is reached.
|
||||
Message string
|
||||
|
||||
// Content-Type for Message
|
||||
MessageContentType string
|
||||
|
||||
// HTTP status code when limit is reached.
|
||||
StatusCode int
|
||||
|
||||
// Maximum number of requests to limit per duration.
|
||||
Max int64
|
||||
|
||||
// Duration of rate-limiter.
|
||||
TTL time.Duration
|
||||
|
||||
// List of places to look up IP address.
|
||||
// Default is "RemoteAddr", "X-Forwarded-For", "X-Real-IP".
|
||||
// You can rearrange the order as you like.
|
||||
IPLookups []string
|
||||
|
||||
// List of HTTP Methods to limit (GET, POST, PUT, etc.).
|
||||
// Empty means limit all methods.
|
||||
Methods []string
|
||||
|
||||
// List of HTTP headers to limit.
|
||||
// Empty means skip headers checking.
|
||||
Headers map[string][]string
|
||||
|
||||
// List of basic auth usernames to limit.
|
||||
BasicAuthUsers []string
|
||||
|
||||
// Throttler struct
|
||||
tokenBuckets map[string]*ratelimit.Bucket
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// LimitReached returns a bool indicating if the Bucket identified by key ran out of tokens.
|
||||
func (l *Limiter) LimitReached(key string) bool {
|
||||
l.Lock()
|
||||
if _, found := l.tokenBuckets[key]; !found {
|
||||
l.tokenBuckets[key] = ratelimit.NewBucket(l.TTL, l.Max)
|
||||
}
|
||||
|
||||
_, isSoonerThanMaxWait := l.tokenBuckets[key].TakeMaxDuration(1, 0)
|
||||
l.Unlock()
|
||||
|
||||
if isSoonerThanMaxWait {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BenchmarkLimitReached(b *testing.B) {
|
||||
limiter := NewLimiter(1, time.Second)
|
||||
key := "127.0.0.1|/"
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
limiter.LimitReached(key)
|
||||
}
|
||||
}
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConstructor(t *testing.T) {
|
||||
limiter := NewLimiter(1, time.Second)
|
||||
if limiter.Max != 1 {
|
||||
t.Errorf("Max field is incorrect. Value: %v", limiter.Max)
|
||||
}
|
||||
if limiter.TTL != time.Second {
|
||||
t.Errorf("TTL field is incorrect. Value: %v", limiter.TTL)
|
||||
}
|
||||
if limiter.Message != "You have reached maximum request limit." {
|
||||
t.Errorf("Message field is incorrect. Value: %v", limiter.Message)
|
||||
}
|
||||
if limiter.StatusCode != 429 {
|
||||
t.Errorf("StatusCode field is incorrect. Value: %v", limiter.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLimitReached(t *testing.T) {
|
||||
limiter := NewLimiter(1, time.Second)
|
||||
key := "127.0.0.1|/"
|
||||
|
||||
if limiter.LimitReached(key) == true {
|
||||
t.Error("First time count should not reached the limit.")
|
||||
}
|
||||
|
||||
if limiter.LimitReached(key) == false {
|
||||
t.Error("Second time count should return true because it exceeds 1 request per second.")
|
||||
}
|
||||
|
||||
<-time.After(1 * time.Second)
|
||||
if limiter.LimitReached(key) == true {
|
||||
t.Error("Third time count should not reached the limit because the 1 second window has passed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMuchHigherMaxRequests(t *testing.T) {
|
||||
numRequests := 1000
|
||||
limiter := NewLimiter(int64(numRequests), time.Second)
|
||||
key := "127.0.0.1|/"
|
||||
|
||||
for i := 0; i < numRequests; i++ {
|
||||
if limiter.LimitReached(key) == true {
|
||||
t.Errorf("N(%v) limit should not be reached.", i)
|
||||
}
|
||||
}
|
||||
|
||||
if limiter.LimitReached(key) == false {
|
||||
t.Errorf("N(%v) limit should be reached because it exceeds %v request per second.", numRequests+2, numRequests)
|
||||
}
|
||||
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
package errors
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
errs := HTTPError{"blah", 429}
|
||||
if errs.Error() == "" {
|
||||
t.Errorf("Unable to print Error(). Value: %v", errs.Error())
|
||||
}
|
||||
}
|
||||
+16
-11
@@ -2,6 +2,7 @@
|
||||
package libstring
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
@@ -16,22 +17,20 @@ func StringInSlice(sliceString []string, needle string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func ipAddrFromRemoteAddr(s string) string {
|
||||
idx := strings.LastIndex(s, ":")
|
||||
if idx == -1 {
|
||||
return s
|
||||
}
|
||||
return s[:idx]
|
||||
}
|
||||
|
||||
// RemoteIP finds IP Address given http.Request struct.
|
||||
func RemoteIP(ipLookups []string, r *http.Request) string {
|
||||
func RemoteIP(ipLookups []string, forwardedForIndexFromBehind int, r *http.Request) string {
|
||||
realIP := r.Header.Get("X-Real-IP")
|
||||
forwardedFor := r.Header.Get("X-Forwarded-For")
|
||||
|
||||
for _, lookup := range ipLookups {
|
||||
if lookup == "RemoteAddr" {
|
||||
return ipAddrFromRemoteAddr(r.RemoteAddr)
|
||||
// 1. Cover the basic use cases for both ipv4 and ipv6
|
||||
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
// 2. Upon error, just return the remote addr.
|
||||
return r.RemoteAddr
|
||||
}
|
||||
return ip
|
||||
}
|
||||
if lookup == "X-Forwarded-For" && forwardedFor != "" {
|
||||
// X-Forwarded-For is potentially a list of addresses separated with ","
|
||||
@@ -39,7 +38,13 @@ func RemoteIP(ipLookups []string, r *http.Request) string {
|
||||
for i, p := range parts {
|
||||
parts[i] = strings.TrimSpace(p)
|
||||
}
|
||||
return parts[0]
|
||||
|
||||
partIndex := len(parts) - 1 - forwardedForIndexFromBehind
|
||||
if partIndex < 0 {
|
||||
partIndex = 0
|
||||
}
|
||||
|
||||
return parts[partIndex]
|
||||
}
|
||||
if lookup == "X-Real-IP" && realIP != "" {
|
||||
return realIP
|
||||
|
||||
-81
@@ -1,81 +0,0 @@
|
||||
package libstring
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringInSlice(t *testing.T) {
|
||||
if StringInSlice([]string{"alice", "dan", "didip", "jason", "karl"}, "brotato") {
|
||||
t.Error("brotato should not be in slice.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPAddrFromRemoteAddr(t *testing.T) {
|
||||
if ipAddrFromRemoteAddr("127.0.0.1:8989") != "127.0.0.1" {
|
||||
t.Errorf("ipAddrFromRemoteAddr did not chop the port number correctly.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteIPDefault(t *testing.T) {
|
||||
ipLookups := []string{"RemoteAddr", "X-Real-IP"}
|
||||
ipv6 := "2601:7:1c82:4097:59a0:a80b:2841:b8c8"
|
||||
|
||||
request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
|
||||
if err != nil {
|
||||
t.Errorf("Unable to create new HTTP request. Error: %v", err)
|
||||
}
|
||||
|
||||
request.Header.Set("X-Real-IP", ipv6)
|
||||
|
||||
ip := RemoteIP(ipLookups, request)
|
||||
if ip != request.RemoteAddr {
|
||||
t.Errorf("Did not get the right IP. IP: %v", ip)
|
||||
}
|
||||
if ip == ipv6 {
|
||||
t.Errorf("X-Real-IP should have been skipped. IP: %v", ip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteIPForwardedFor(t *testing.T) {
|
||||
ipLookups := []string{"X-Forwarded-For", "X-Real-IP", "RemoteAddr"}
|
||||
ipv6 := "2601:7:1c82:4097:59a0:a80b:2841:b8c8"
|
||||
|
||||
request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
|
||||
if err != nil {
|
||||
t.Errorf("Unable to create new HTTP request. Error: %v", err)
|
||||
}
|
||||
|
||||
request.Header.Set("X-Forwarded-For", "54.223.11.104")
|
||||
request.Header.Set("X-Real-IP", ipv6)
|
||||
|
||||
ip := RemoteIP(ipLookups, request)
|
||||
if ip != "54.223.11.104" {
|
||||
t.Errorf("Did not get the right IP. IP: %v", ip)
|
||||
}
|
||||
if ip == ipv6 {
|
||||
t.Errorf("X-Real-IP should have been skipped. IP: %v", ip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteIPRealIP(t *testing.T) {
|
||||
ipLookups := []string{"X-Real-IP", "X-Forwarded-For", "RemoteAddr"}
|
||||
ipv6 := "2601:7:1c82:4097:59a0:a80b:2841:b8c8"
|
||||
|
||||
request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
|
||||
if err != nil {
|
||||
t.Errorf("Unable to create new HTTP request. Error: %v", err)
|
||||
}
|
||||
|
||||
request.Header.Set("X-Forwarded-For", "54.223.11.104")
|
||||
request.Header.Set("X-Real-IP", ipv6)
|
||||
|
||||
ip := RemoteIP(ipLookups, request)
|
||||
if ip != ipv6 {
|
||||
t.Errorf("Did not get the right IP. IP: %v", ip)
|
||||
}
|
||||
if ip == "54.223.11.104" {
|
||||
t.Errorf("X-Forwarded-For should have been skipped. IP: %v", ip)
|
||||
}
|
||||
}
|
||||
+474
@@ -0,0 +1,474 @@
|
||||
// Package limiter provides data structure to configure rate-limiter.
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
gocache "github.com/patrickmn/go-cache"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// New is a constructor for Limiter.
|
||||
func New(generalExpirableOptions *ExpirableOptions) *Limiter {
|
||||
lmt := &Limiter{}
|
||||
|
||||
lmt.SetMessageContentType("text/plain; charset=utf-8").
|
||||
SetMessage("You have reached maximum request limit.").
|
||||
SetStatusCode(429).
|
||||
SetOnLimitReached(nil).
|
||||
SetIPLookups([]string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}).
|
||||
SetForwardedForIndexFromBehind(0).
|
||||
SetHeaders(make(map[string][]string))
|
||||
|
||||
if generalExpirableOptions != nil {
|
||||
lmt.generalExpirableOptions = generalExpirableOptions
|
||||
} else {
|
||||
lmt.generalExpirableOptions = &ExpirableOptions{}
|
||||
}
|
||||
|
||||
// Default for ExpireJobInterval is every minute.
|
||||
if lmt.generalExpirableOptions.ExpireJobInterval <= 0 {
|
||||
lmt.generalExpirableOptions.ExpireJobInterval = time.Minute
|
||||
}
|
||||
|
||||
// Default for DefaultExpirationTTL is 10 years.
|
||||
if lmt.generalExpirableOptions.DefaultExpirationTTL <= 0 {
|
||||
lmt.generalExpirableOptions.DefaultExpirationTTL = 87600 * time.Hour
|
||||
}
|
||||
|
||||
lmt.tokenBuckets = gocache.New(
|
||||
lmt.generalExpirableOptions.DefaultExpirationTTL,
|
||||
lmt.generalExpirableOptions.ExpireJobInterval,
|
||||
)
|
||||
|
||||
lmt.basicAuthUsers = gocache.New(
|
||||
lmt.generalExpirableOptions.DefaultExpirationTTL,
|
||||
lmt.generalExpirableOptions.ExpireJobInterval,
|
||||
)
|
||||
|
||||
return lmt
|
||||
}
|
||||
|
||||
// Limiter is a config struct to limit a particular request handler.
|
||||
type Limiter struct {
|
||||
// Maximum number of requests to limit per second.
|
||||
max float64
|
||||
|
||||
// Limiter burst size
|
||||
burst int
|
||||
|
||||
// HTTP message when limit is reached.
|
||||
message string
|
||||
|
||||
// Content-Type for Message
|
||||
messageContentType string
|
||||
|
||||
// HTTP status code when limit is reached.
|
||||
statusCode int
|
||||
|
||||
// A function to call when a request is rejected.
|
||||
onLimitReached func(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// List of places to look up IP address.
|
||||
// Default is "RemoteAddr", "X-Forwarded-For", "X-Real-IP".
|
||||
// You can rearrange the order as you like.
|
||||
ipLookups []string
|
||||
|
||||
forwardedForIndex int
|
||||
|
||||
// List of HTTP Methods to limit (GET, POST, PUT, etc.).
|
||||
// Empty means limit all methods.
|
||||
methods []string
|
||||
|
||||
// Able to configure token bucket expirations.
|
||||
generalExpirableOptions *ExpirableOptions
|
||||
|
||||
// List of basic auth usernames to limit.
|
||||
basicAuthUsers *gocache.Cache
|
||||
|
||||
// Map of HTTP headers to limit.
|
||||
// Empty means skip headers checking.
|
||||
headers map[string]*gocache.Cache
|
||||
|
||||
// Map of limiters with TTL
|
||||
tokenBuckets *gocache.Cache
|
||||
|
||||
tokenBucketExpirationTTL time.Duration
|
||||
basicAuthExpirationTTL time.Duration
|
||||
headerEntryExpirationTTL time.Duration
|
||||
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// SetTokenBucketExpirationTTL is thread-safe way of setting custom token bucket expiration TTL.
|
||||
func (l *Limiter) SetTokenBucketExpirationTTL(ttl time.Duration) *Limiter {
|
||||
l.Lock()
|
||||
l.tokenBucketExpirationTTL = ttl
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GettokenBucketExpirationTTL is thread-safe way of getting custom token bucket expiration TTL.
|
||||
func (l *Limiter) GetTokenBucketExpirationTTL() time.Duration {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.tokenBucketExpirationTTL
|
||||
}
|
||||
|
||||
// SetBasicAuthExpirationTTL is thread-safe way of setting custom basic auth expiration TTL.
|
||||
func (l *Limiter) SetBasicAuthExpirationTTL(ttl time.Duration) *Limiter {
|
||||
l.Lock()
|
||||
l.basicAuthExpirationTTL = ttl
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetBasicAuthExpirationTTL is thread-safe way of getting custom basic auth expiration TTL.
|
||||
func (l *Limiter) GetBasicAuthExpirationTTL() time.Duration {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.basicAuthExpirationTTL
|
||||
}
|
||||
|
||||
// SetHeaderEntryExpirationTTL is thread-safe way of setting custom basic auth expiration TTL.
|
||||
func (l *Limiter) SetHeaderEntryExpirationTTL(ttl time.Duration) *Limiter {
|
||||
l.Lock()
|
||||
l.headerEntryExpirationTTL = ttl
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetHeaderEntryExpirationTTL is thread-safe way of getting custom basic auth expiration TTL.
|
||||
func (l *Limiter) GetHeaderEntryExpirationTTL() time.Duration {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.headerEntryExpirationTTL
|
||||
}
|
||||
|
||||
// SetMax is thread-safe way of setting maximum number of requests to limit per duration.
|
||||
func (l *Limiter) SetMax(max float64) *Limiter {
|
||||
l.Lock()
|
||||
l.max = max
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetMax is thread-safe way of getting maximum number of requests to limit per duration.
|
||||
func (l *Limiter) GetMax() float64 {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.max
|
||||
}
|
||||
|
||||
// SetBurst is thread-safe way of setting maximum burst size.
|
||||
func (l *Limiter) SetBurst(burst int) *Limiter {
|
||||
l.Lock()
|
||||
l.burst = burst
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetBurst is thread-safe way of setting maximum burst size.
|
||||
func (l *Limiter) GetBurst() int {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
return l.burst
|
||||
}
|
||||
|
||||
// SetMessage is thread-safe way of setting HTTP message when limit is reached.
|
||||
func (l *Limiter) SetMessage(msg string) *Limiter {
|
||||
l.Lock()
|
||||
l.message = msg
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetMessage is thread-safe way of getting HTTP message when limit is reached.
|
||||
func (l *Limiter) GetMessage() string {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.message
|
||||
}
|
||||
|
||||
// SetMessageContentType is thread-safe way of setting HTTP message Content-Type when limit is reached.
|
||||
func (l *Limiter) SetMessageContentType(contentType string) *Limiter {
|
||||
l.Lock()
|
||||
l.messageContentType = contentType
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetMessageContentType is thread-safe way of getting HTTP message Content-Type when limit is reached.
|
||||
func (l *Limiter) GetMessageContentType() string {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.messageContentType
|
||||
}
|
||||
|
||||
// SetStatusCode is thread-safe way of setting HTTP status code when limit is reached.
|
||||
func (l *Limiter) SetStatusCode(statusCode int) *Limiter {
|
||||
l.Lock()
|
||||
l.statusCode = statusCode
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetStatusCode is thread-safe way of getting HTTP status code when limit is reached.
|
||||
func (l *Limiter) GetStatusCode() int {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.statusCode
|
||||
}
|
||||
|
||||
// SetOnLimitReached is thread-safe way of setting after-rejection function when limit is reached.
|
||||
func (l *Limiter) SetOnLimitReached(fn func(w http.ResponseWriter, r *http.Request)) *Limiter {
|
||||
l.Lock()
|
||||
l.onLimitReached = fn
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// ExecOnLimitReached is thread-safe way of executing after-rejection function when limit is reached.
|
||||
func (l *Limiter) ExecOnLimitReached(w http.ResponseWriter, r *http.Request) {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
fn := l.onLimitReached
|
||||
if fn != nil {
|
||||
fn(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// SetIPLookups is thread-safe way of setting list of places to look up IP address.
|
||||
func (l *Limiter) SetIPLookups(ipLookups []string) *Limiter {
|
||||
l.Lock()
|
||||
l.ipLookups = ipLookups
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetIPLookups is thread-safe way of getting list of places to look up IP address.
|
||||
func (l *Limiter) GetIPLookups() []string {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.ipLookups
|
||||
}
|
||||
|
||||
// SetForwardedForIndexFromBehind is thread-safe way of setting which X-Forwarded-For index to choose.
|
||||
func (l *Limiter) SetForwardedForIndexFromBehind(forwardedForIndex int) *Limiter {
|
||||
l.Lock()
|
||||
l.forwardedForIndex = forwardedForIndex
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetForwardedForIndexFromBehind is thread-safe way of getting which X-Forwarded-For index to choose.
|
||||
func (l *Limiter) GetForwardedForIndexFromBehind() int {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.forwardedForIndex
|
||||
}
|
||||
|
||||
// SetMethods is thread-safe way of setting list of HTTP Methods to limit (GET, POST, PUT, etc.).
|
||||
func (l *Limiter) SetMethods(methods []string) *Limiter {
|
||||
l.Lock()
|
||||
l.methods = methods
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetMethods is thread-safe way of getting list of HTTP Methods to limit (GET, POST, PUT, etc.).
|
||||
func (l *Limiter) GetMethods() []string {
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
return l.methods
|
||||
}
|
||||
|
||||
// SetBasicAuthUsers is thread-safe way of setting list of basic auth usernames to limit.
|
||||
func (l *Limiter) SetBasicAuthUsers(basicAuthUsers []string) *Limiter {
|
||||
ttl := l.GetBasicAuthExpirationTTL()
|
||||
if ttl <= 0 {
|
||||
ttl = l.generalExpirableOptions.DefaultExpirationTTL
|
||||
}
|
||||
|
||||
for _, basicAuthUser := range basicAuthUsers {
|
||||
l.basicAuthUsers.Set(basicAuthUser, true, ttl)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetBasicAuthUsers is thread-safe way of getting list of basic auth usernames to limit.
|
||||
func (l *Limiter) GetBasicAuthUsers() []string {
|
||||
asMap := l.basicAuthUsers.Items()
|
||||
|
||||
var basicAuthUsers []string
|
||||
for basicAuthUser, _ := range asMap {
|
||||
basicAuthUsers = append(basicAuthUsers, basicAuthUser)
|
||||
}
|
||||
|
||||
return basicAuthUsers
|
||||
}
|
||||
|
||||
// RemoveBasicAuthUsers is thread-safe way of removing basic auth usernames from existing list.
|
||||
func (l *Limiter) RemoveBasicAuthUsers(basicAuthUsers []string) *Limiter {
|
||||
for _, toBeRemoved := range basicAuthUsers {
|
||||
l.basicAuthUsers.Delete(toBeRemoved)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// SetHeaders is thread-safe way of setting map of HTTP headers to limit.
|
||||
func (l *Limiter) SetHeaders(headers map[string][]string) *Limiter {
|
||||
if l.headers == nil {
|
||||
l.headers = make(map[string]*gocache.Cache)
|
||||
}
|
||||
|
||||
for header, entries := range headers {
|
||||
l.SetHeader(header, entries)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetHeaders is thread-safe way of getting map of HTTP headers to limit.
|
||||
func (l *Limiter) GetHeaders() map[string][]string {
|
||||
results := make(map[string][]string)
|
||||
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
for header, entriesAsGoCache := range l.headers {
|
||||
entries := make([]string, 0)
|
||||
|
||||
for entry, _ := range entriesAsGoCache.Items() {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
results[header] = entries
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// SetHeader is thread-safe way of setting entries of 1 HTTP header.
|
||||
func (l *Limiter) SetHeader(header string, entries []string) *Limiter {
|
||||
l.RLock()
|
||||
existing, found := l.headers[header]
|
||||
l.RUnlock()
|
||||
|
||||
ttl := l.GetHeaderEntryExpirationTTL()
|
||||
if ttl <= 0 {
|
||||
ttl = l.generalExpirableOptions.DefaultExpirationTTL
|
||||
}
|
||||
|
||||
if !found {
|
||||
existing = gocache.New(ttl, l.generalExpirableOptions.ExpireJobInterval)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
existing.Set(entry, true, ttl)
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
l.headers[header] = existing
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// GetHeader is thread-safe way of getting entries of 1 HTTP header.
|
||||
func (l *Limiter) GetHeader(header string) []string {
|
||||
l.RLock()
|
||||
entriesAsGoCache := l.headers[header]
|
||||
l.RUnlock()
|
||||
|
||||
entriesAsMap := entriesAsGoCache.Items()
|
||||
entries := make([]string, 0)
|
||||
|
||||
for entry, _ := range entriesAsMap {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
// RemoveHeader is thread-safe way of removing entries of 1 HTTP header.
|
||||
func (l *Limiter) RemoveHeader(header string) *Limiter {
|
||||
ttl := l.GetHeaderEntryExpirationTTL()
|
||||
if ttl <= 0 {
|
||||
ttl = l.generalExpirableOptions.DefaultExpirationTTL
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
l.headers[header] = gocache.New(ttl, l.generalExpirableOptions.ExpireJobInterval)
|
||||
l.Unlock()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// RemoveHeaderEntries is thread-safe way of adding new entries to 1 HTTP header rule.
|
||||
func (l *Limiter) RemoveHeaderEntries(header string, entriesForRemoval []string) *Limiter {
|
||||
l.RLock()
|
||||
entries, found := l.headers[header]
|
||||
l.RUnlock()
|
||||
|
||||
if !found {
|
||||
return l
|
||||
}
|
||||
|
||||
for _, toBeRemoved := range entriesForRemoval {
|
||||
entries.Delete(toBeRemoved)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Limiter) limitReachedWithTokenBucketTTL(key string, tokenBucketTTL time.Duration) bool {
|
||||
lmtMax := l.GetMax()
|
||||
lmtBurst := l.GetBurst()
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
if _, found := l.tokenBuckets.Get(key); !found {
|
||||
l.tokenBuckets.Set(
|
||||
key,
|
||||
rate.NewLimiter(rate.Limit(lmtMax), lmtBurst),
|
||||
tokenBucketTTL,
|
||||
)
|
||||
}
|
||||
|
||||
expiringMap, found := l.tokenBuckets.Get(key)
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
|
||||
return !expiringMap.(*rate.Limiter).Allow()
|
||||
}
|
||||
|
||||
// LimitReached returns a bool indicating if the Bucket identified by key ran out of tokens.
|
||||
func (l *Limiter) LimitReached(key string) bool {
|
||||
ttl := l.GetTokenBucketExpirationTTL()
|
||||
|
||||
if ttl <= 0 {
|
||||
ttl = l.generalExpirableOptions.DefaultExpirationTTL
|
||||
}
|
||||
|
||||
return l.limitReachedWithTokenBucketTTL(key, ttl)
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package limiter
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ExpirableOptions struct {
|
||||
DefaultExpirationTTL time.Duration
|
||||
|
||||
// How frequently expire job triggers
|
||||
ExpireJobInterval time.Duration
|
||||
}
|
||||
+73
-53
@@ -2,48 +2,42 @@
|
||||
package tollbooth
|
||||
|
||||
import (
|
||||
"github.com/didip/tollbooth/config"
|
||||
"github.com/didip/tollbooth/errors"
|
||||
"github.com/didip/tollbooth/libstring"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
"github.com/didip/tollbooth/errors"
|
||||
"github.com/didip/tollbooth/libstring"
|
||||
"github.com/didip/tollbooth/limiter"
|
||||
"math"
|
||||
)
|
||||
|
||||
// NewLimiter is a convenience function to config.NewLimiter.
|
||||
func NewLimiter(max int64, ttl time.Duration) *config.Limiter {
|
||||
return config.NewLimiter(max, ttl)
|
||||
// setResponseHeaders configures X-Rate-Limit-Limit and X-Rate-Limit-Duration
|
||||
func setResponseHeaders(lmt *limiter.Limiter, w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("X-Rate-Limit-Limit", fmt.Sprintf("%.2f", lmt.GetMax()))
|
||||
w.Header().Add("X-Rate-Limit-Duration", "1")
|
||||
w.Header().Add("X-Rate-Limit-Request-Forwarded-For", r.Header.Get("X-Forwarded-For"))
|
||||
w.Header().Add("X-Rate-Limit-Request-Remote-Addr", r.RemoteAddr)
|
||||
}
|
||||
|
||||
// NewLimiter is a convenience function to limiter.New.
|
||||
func NewLimiter(max float64, tbOptions *limiter.ExpirableOptions) *limiter.Limiter {
|
||||
return limiter.New(tbOptions).SetMax(max).SetBurst(int(math.Max(1, max)))
|
||||
}
|
||||
|
||||
// LimitByKeys keeps track number of request made by keys separated by pipe.
|
||||
// It returns HTTPError when limit is exceeded.
|
||||
func LimitByKeys(limiter *config.Limiter, keys []string) *errors.HTTPError {
|
||||
if limiter.LimitReached(strings.Join(keys, "|")) {
|
||||
return &errors.HTTPError{Message: limiter.Message, StatusCode: limiter.StatusCode}
|
||||
func LimitByKeys(lmt *limiter.Limiter, keys []string) *errors.HTTPError {
|
||||
if lmt.LimitReached(strings.Join(keys, "|")) {
|
||||
return &errors.HTTPError{Message: lmt.GetMessage(), StatusCode: lmt.GetStatusCode()}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LimitByRequest builds keys based on http.Request struct,
|
||||
// loops through all the keys, and check if any one of them returns HTTPError.
|
||||
func LimitByRequest(limiter *config.Limiter, r *http.Request) *errors.HTTPError {
|
||||
sliceKeys := BuildKeys(limiter, r)
|
||||
|
||||
// Loop sliceKeys and check if one of them has error.
|
||||
for _, keys := range sliceKeys {
|
||||
httpError := LimitByKeys(limiter, keys)
|
||||
if httpError != nil {
|
||||
return httpError
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildKeys generates a slice of keys to rate-limit by given config and request structs.
|
||||
func BuildKeys(limiter *config.Limiter, r *http.Request) [][]string {
|
||||
remoteIP := libstring.RemoteIP(limiter.IPLookups, r)
|
||||
// BuildKeys generates a slice of keys to rate-limit by given limiter and request structs.
|
||||
func BuildKeys(lmt *limiter.Limiter, r *http.Request) [][]string {
|
||||
remoteIP := libstring.RemoteIP(lmt.GetIPLookups(), lmt.GetForwardedForIndexFromBehind(), r)
|
||||
path := r.URL.Path
|
||||
sliceKeys := make([][]string, 0)
|
||||
|
||||
@@ -52,14 +46,21 @@ func BuildKeys(limiter *config.Limiter, r *http.Request) [][]string {
|
||||
return sliceKeys
|
||||
}
|
||||
|
||||
if limiter.Methods != nil && limiter.Headers != nil && limiter.BasicAuthUsers != nil {
|
||||
lmtMethods := lmt.GetMethods()
|
||||
lmtHeaders := lmt.GetHeaders()
|
||||
lmtBasicAuthUsers := lmt.GetBasicAuthUsers()
|
||||
|
||||
lmtHeadersIsSet := len(lmtHeaders) > 0
|
||||
lmtBasicAuthUsersIsSet := len(lmtBasicAuthUsers) > 0
|
||||
|
||||
if lmtMethods != nil && lmtHeadersIsSet && lmtBasicAuthUsersIsSet {
|
||||
// Limit by HTTP methods and HTTP headers+values and Basic Auth credentials.
|
||||
if libstring.StringInSlice(limiter.Methods, r.Method) {
|
||||
for headerKey, headerValues := range limiter.Headers {
|
||||
if libstring.StringInSlice(lmtMethods, r.Method) {
|
||||
for headerKey, headerValues := range lmtHeaders {
|
||||
if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
|
||||
// If header values are empty, rate-limit all request with headerKey.
|
||||
username, _, ok := r.BasicAuth()
|
||||
if ok && libstring.StringInSlice(limiter.BasicAuthUsers, username) {
|
||||
if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
|
||||
sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, username})
|
||||
}
|
||||
|
||||
@@ -67,7 +68,7 @@ func BuildKeys(limiter *config.Limiter, r *http.Request) [][]string {
|
||||
// If header values are not empty, rate-limit all request with headerKey and headerValues.
|
||||
for _, headerValue := range headerValues {
|
||||
username, _, ok := r.BasicAuth()
|
||||
if ok && libstring.StringInSlice(limiter.BasicAuthUsers, username) {
|
||||
if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
|
||||
sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue, username})
|
||||
}
|
||||
}
|
||||
@@ -75,10 +76,10 @@ func BuildKeys(limiter *config.Limiter, r *http.Request) [][]string {
|
||||
}
|
||||
}
|
||||
|
||||
} else if limiter.Methods != nil && limiter.Headers != nil {
|
||||
} else if lmtMethods != nil && lmtHeadersIsSet {
|
||||
// Limit by HTTP methods and HTTP headers+values.
|
||||
if libstring.StringInSlice(limiter.Methods, r.Method) {
|
||||
for headerKey, headerValues := range limiter.Headers {
|
||||
if libstring.StringInSlice(lmtMethods, r.Method) {
|
||||
for headerKey, headerValues := range lmtHeaders {
|
||||
if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
|
||||
// If header values are empty, rate-limit all request with headerKey.
|
||||
sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey})
|
||||
@@ -92,24 +93,24 @@ func BuildKeys(limiter *config.Limiter, r *http.Request) [][]string {
|
||||
}
|
||||
}
|
||||
|
||||
} else if limiter.Methods != nil && limiter.BasicAuthUsers != nil {
|
||||
} else if lmtMethods != nil && lmtBasicAuthUsersIsSet {
|
||||
// Limit by HTTP methods and Basic Auth credentials.
|
||||
if libstring.StringInSlice(limiter.Methods, r.Method) {
|
||||
if libstring.StringInSlice(lmtMethods, r.Method) {
|
||||
username, _, ok := r.BasicAuth()
|
||||
if ok && libstring.StringInSlice(limiter.BasicAuthUsers, username) {
|
||||
if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
|
||||
sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, username})
|
||||
}
|
||||
}
|
||||
|
||||
} else if limiter.Methods != nil {
|
||||
} else if lmtMethods != nil {
|
||||
// Limit by HTTP methods.
|
||||
if libstring.StringInSlice(limiter.Methods, r.Method) {
|
||||
if libstring.StringInSlice(lmtMethods, r.Method) {
|
||||
sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method})
|
||||
}
|
||||
|
||||
} else if limiter.Headers != nil {
|
||||
} else if lmtHeadersIsSet {
|
||||
// Limit by HTTP headers+values.
|
||||
for headerKey, headerValues := range limiter.Headers {
|
||||
for headerKey, headerValues := range lmtHeaders {
|
||||
if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
|
||||
// If header values are empty, rate-limit all request with headerKey.
|
||||
sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey})
|
||||
@@ -122,10 +123,10 @@ func BuildKeys(limiter *config.Limiter, r *http.Request) [][]string {
|
||||
}
|
||||
}
|
||||
|
||||
} else if limiter.BasicAuthUsers != nil {
|
||||
} else if lmtBasicAuthUsersIsSet {
|
||||
// Limit by Basic Auth credentials.
|
||||
username, _, ok := r.BasicAuth()
|
||||
if ok && libstring.StringInSlice(limiter.BasicAuthUsers, username) {
|
||||
if ok && libstring.StringInSlice(lmtBasicAuthUsers, username) {
|
||||
sliceKeys = append(sliceKeys, []string{remoteIP, path, username})
|
||||
}
|
||||
} else {
|
||||
@@ -136,12 +137,31 @@ func BuildKeys(limiter *config.Limiter, r *http.Request) [][]string {
|
||||
return sliceKeys
|
||||
}
|
||||
|
||||
// LimitHandler is a middleware that performs rate-limiting given http.Handler struct.
|
||||
func LimitHandler(limiter *config.Limiter, next http.Handler) http.Handler {
|
||||
middle := func(w http.ResponseWriter, r *http.Request) {
|
||||
httpError := LimitByRequest(limiter, r)
|
||||
// LimitByRequest builds keys based on http.Request struct,
|
||||
// loops through all the keys, and check if any one of them returns HTTPError.
|
||||
func LimitByRequest(lmt *limiter.Limiter, w http.ResponseWriter, r *http.Request) *errors.HTTPError {
|
||||
setResponseHeaders(lmt, w, r)
|
||||
|
||||
sliceKeys := BuildKeys(lmt, r)
|
||||
|
||||
// Loop sliceKeys and check if one of them has error.
|
||||
for _, keys := range sliceKeys {
|
||||
httpError := LimitByKeys(lmt, keys)
|
||||
if httpError != nil {
|
||||
w.Header().Add("Content-Type", limiter.MessageContentType)
|
||||
return httpError
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LimitHandler is a middleware that performs rate-limiting given http.Handler struct.
|
||||
func LimitHandler(lmt *limiter.Limiter, next http.Handler) http.Handler {
|
||||
middle := func(w http.ResponseWriter, r *http.Request) {
|
||||
httpError := LimitByRequest(lmt, w, r)
|
||||
if httpError != nil {
|
||||
lmt.ExecOnLimitReached(w, r)
|
||||
w.Header().Add("Content-Type", lmt.GetMessageContentType())
|
||||
w.WriteHeader(httpError.StatusCode)
|
||||
w.Write([]byte(httpError.Message))
|
||||
return
|
||||
@@ -155,6 +175,6 @@ func LimitHandler(limiter *config.Limiter, next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
// LimitFuncHandler is a middleware that performs rate-limiting given request handler function.
|
||||
func LimitFuncHandler(limiter *config.Limiter, nextFunc func(http.ResponseWriter, *http.Request)) http.Handler {
|
||||
return LimitHandler(limiter, http.HandlerFunc(nextFunc))
|
||||
func LimitFuncHandler(lmt *limiter.Limiter, nextFunc func(http.ResponseWriter, *http.Request)) http.Handler {
|
||||
return LimitHandler(lmt, http.HandlerFunc(nextFunc))
|
||||
}
|
||||
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
package tollbooth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BenchmarkLimitByKeys(b *testing.B) {
|
||||
limiter := NewLimiter(1, time.Second) // Only 1 request per second is allowed.
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
LimitByKeys(limiter, []string{"127.0.0.1", "/"})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBuildKeys(b *testing.B) {
|
||||
limiter := NewLimiter(1, time.Second)
|
||||
|
||||
request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to create new HTTP request. Error: %v", err)
|
||||
}
|
||||
|
||||
request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
|
||||
for i := 0; i < b.N; i++ {
|
||||
sliceKeys := BuildKeys(limiter, request)
|
||||
if len(sliceKeys) == 0 {
|
||||
fmt.Print("Length of sliceKeys should never be empty.")
|
||||
}
|
||||
}
|
||||
}
|
||||
-266
@@ -1,266 +0,0 @@
|
||||
package tollbooth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLimitByKeys(t *testing.T) {
|
||||
limiter := NewLimiter(1, time.Second) // Only 1 request per second is allowed.
|
||||
|
||||
httperror := LimitByKeys(limiter, []string{"127.0.0.1", "/"})
|
||||
if httperror != nil {
|
||||
t.Errorf("First time count should not return error. Error: %v", httperror.Error())
|
||||
}
|
||||
|
||||
httperror = LimitByKeys(limiter, []string{"127.0.0.1", "/"})
|
||||
if httperror == nil {
|
||||
t.Errorf("Second time count should return error because it exceeds 1 request per second.")
|
||||
}
|
||||
|
||||
<-time.After(1 * time.Second)
|
||||
httperror = LimitByKeys(limiter, []string{"127.0.0.1", "/"})
|
||||
if httperror != nil {
|
||||
t.Errorf("Third time count should not return error because the 1 second window has passed.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultBuildKeys(t *testing.T) {
|
||||
limiter := NewLimiter(1, time.Second)
|
||||
limiter.IPLookups = []string{"X-Forwarded-For", "X-Real-IP", "RemoteAddr"}
|
||||
|
||||
request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
|
||||
if err != nil {
|
||||
t.Errorf("Unable to create new HTTP request. Error: %v", err)
|
||||
}
|
||||
|
||||
request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
|
||||
|
||||
sliceKeys := BuildKeys(limiter, request)
|
||||
if len(sliceKeys) == 0 {
|
||||
t.Error("Length of sliceKeys should never be empty.")
|
||||
}
|
||||
|
||||
for _, keys := range sliceKeys {
|
||||
for i, keyChunk := range keys {
|
||||
if i == 0 && keyChunk != request.Header.Get("X-Real-IP") {
|
||||
t.Errorf("The first chunk should be remote IP. KeyChunk: %v", keyChunk)
|
||||
}
|
||||
if i == 1 && keyChunk != request.URL.Path {
|
||||
t.Errorf("The second chunk should be request path. KeyChunk: %v", keyChunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAuthBuildKeys(t *testing.T) {
|
||||
limiter := NewLimiter(1, time.Second)
|
||||
limiter.BasicAuthUsers = []string{"bro"}
|
||||
|
||||
request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
|
||||
if err != nil {
|
||||
t.Errorf("Unable to create new HTTP request. Error: %v", err)
|
||||
}
|
||||
|
||||
request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
|
||||
|
||||
request.SetBasicAuth("bro", "tato")
|
||||
|
||||
for _, keys := range BuildKeys(limiter, request) {
|
||||
if len(keys) != 3 {
|
||||
t.Error("Keys should be made of 3 parts.")
|
||||
}
|
||||
for i, keyChunk := range keys {
|
||||
if i == 0 && keyChunk != request.Header.Get("X-Real-IP") {
|
||||
t.Errorf("The (%v) chunk should be remote IP. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 1 && keyChunk != request.URL.Path {
|
||||
t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 2 && keyChunk != "bro" {
|
||||
t.Errorf("The (%v) chunk should be request username. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomHeadersBuildKeys(t *testing.T) {
|
||||
limiter := NewLimiter(1, time.Second)
|
||||
limiter.Headers = make(map[string][]string)
|
||||
limiter.Headers["X-Auth-Token"] = []string{"totally-top-secret", "another-secret"}
|
||||
|
||||
request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
|
||||
if err != nil {
|
||||
t.Errorf("Unable to create new HTTP request. Error: %v", err)
|
||||
}
|
||||
|
||||
request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
|
||||
request.Header.Set("X-Auth-Token", "totally-top-secret")
|
||||
|
||||
for _, keys := range BuildKeys(limiter, request) {
|
||||
if len(keys) != 4 {
|
||||
t.Errorf("Keys should be made of 4 parts. Keys: %v", keys)
|
||||
}
|
||||
for i, keyChunk := range keys {
|
||||
if i == 0 && keyChunk != request.Header.Get("X-Real-IP") {
|
||||
t.Errorf("The (%v) chunk should be remote IP. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 1 && keyChunk != request.URL.Path {
|
||||
t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 2 && keyChunk != "X-Auth-Token" {
|
||||
t.Errorf("The (%v) chunk should be request header. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 3 && (keyChunk != "totally-top-secret" && keyChunk != "another-secret") {
|
||||
t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestMethodBuildKeys(t *testing.T) {
|
||||
limiter := NewLimiter(1, time.Second)
|
||||
limiter.Methods = []string{"GET"}
|
||||
|
||||
request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
|
||||
if err != nil {
|
||||
t.Errorf("Unable to create new HTTP request. Error: %v", err)
|
||||
}
|
||||
|
||||
request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
|
||||
|
||||
for _, keys := range BuildKeys(limiter, request) {
|
||||
if len(keys) != 3 {
|
||||
t.Errorf("Keys should be made of 3 parts. Keys: %v", keys)
|
||||
}
|
||||
for i, keyChunk := range keys {
|
||||
if i == 0 && keyChunk != request.Header.Get("X-Real-IP") {
|
||||
t.Errorf("The (%v) chunk should be remote IP. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 1 && keyChunk != request.URL.Path {
|
||||
t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 2 && keyChunk != "GET" {
|
||||
t.Errorf("The (%v) chunk should be request method. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestMethodAndCustomHeadersBuildKeys(t *testing.T) {
|
||||
limiter := NewLimiter(1, time.Second)
|
||||
limiter.Methods = []string{"GET"}
|
||||
limiter.Headers = make(map[string][]string)
|
||||
limiter.Headers["X-Auth-Token"] = []string{"totally-top-secret", "another-secret"}
|
||||
|
||||
request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
|
||||
if err != nil {
|
||||
t.Errorf("Unable to create new HTTP request. Error: %v", err)
|
||||
}
|
||||
|
||||
request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
|
||||
request.Header.Set("X-Auth-Token", "totally-top-secret")
|
||||
|
||||
for _, keys := range BuildKeys(limiter, request) {
|
||||
if len(keys) != 5 {
|
||||
t.Errorf("Keys should be made of 4 parts. Keys: %v", keys)
|
||||
}
|
||||
for i, keyChunk := range keys {
|
||||
if i == 0 && keyChunk != request.Header.Get("X-Real-IP") {
|
||||
t.Errorf("The (%v) chunk should be remote IP. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 1 && keyChunk != request.URL.Path {
|
||||
t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 2 && keyChunk != "GET" {
|
||||
t.Errorf("The (%v) chunk should be request method. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 3 && keyChunk != "X-Auth-Token" {
|
||||
t.Errorf("The (%v) chunk should be request header. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 4 && (keyChunk != "totally-top-secret" && keyChunk != "another-secret") {
|
||||
t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestMethodAndBasicAuthUsersBuildKeys(t *testing.T) {
|
||||
limiter := NewLimiter(1, time.Second)
|
||||
limiter.Methods = []string{"GET"}
|
||||
limiter.BasicAuthUsers = []string{"bro"}
|
||||
|
||||
request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
|
||||
if err != nil {
|
||||
t.Errorf("Unable to create new HTTP request. Error: %v", err)
|
||||
}
|
||||
|
||||
request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
|
||||
request.SetBasicAuth("bro", "tato")
|
||||
|
||||
for _, keys := range BuildKeys(limiter, request) {
|
||||
if len(keys) != 4 {
|
||||
t.Errorf("Keys should be made of 4 parts. Keys: %v", keys)
|
||||
}
|
||||
for i, keyChunk := range keys {
|
||||
if i == 0 && keyChunk != request.Header.Get("X-Real-IP") {
|
||||
t.Errorf("The (%v) chunk should be remote IP. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 1 && keyChunk != request.URL.Path {
|
||||
t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 2 && keyChunk != "GET" {
|
||||
t.Errorf("The (%v) chunk should be request method. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 3 && keyChunk != "bro" {
|
||||
t.Errorf("The (%v) chunk should be basic auth user. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestMethodCustomHeadersAndBasicAuthUsersBuildKeys(t *testing.T) {
|
||||
limiter := NewLimiter(1, time.Second)
|
||||
limiter.Methods = []string{"GET"}
|
||||
limiter.Headers = make(map[string][]string)
|
||||
limiter.Headers["X-Auth-Token"] = []string{"totally-top-secret", "another-secret"}
|
||||
limiter.BasicAuthUsers = []string{"bro"}
|
||||
|
||||
request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
|
||||
if err != nil {
|
||||
t.Errorf("Unable to create new HTTP request. Error: %v", err)
|
||||
}
|
||||
|
||||
request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
|
||||
request.Header.Set("X-Auth-Token", "totally-top-secret")
|
||||
request.SetBasicAuth("bro", "tato")
|
||||
|
||||
for _, keys := range BuildKeys(limiter, request) {
|
||||
if len(keys) != 6 {
|
||||
t.Errorf("Keys should be made of 4 parts. Keys: %v", keys)
|
||||
}
|
||||
for i, keyChunk := range keys {
|
||||
if i == 0 && keyChunk != request.Header.Get("X-Real-IP") {
|
||||
t.Errorf("The (%v) chunk should be remote IP. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 1 && keyChunk != request.URL.Path {
|
||||
t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 2 && keyChunk != "GET" {
|
||||
t.Errorf("The (%v) chunk should be request method. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 3 && keyChunk != "X-Auth-Token" {
|
||||
t.Errorf("The (%v) chunk should be request header. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 4 && (keyChunk != "totally-top-secret" && keyChunk != "another-secret") {
|
||||
t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
if i == 5 && keyChunk != "bro" {
|
||||
t.Errorf("The (%v) chunk should be basic auth user. KeyChunk: %v", i+1, keyChunk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
## tollbooth_chi
|
||||
|
||||
[Chi](https://github.com/pressly/chi) middleware for rate limiting HTTP requests.
|
||||
|
||||
|
||||
## Five Minutes Tutorial
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/didip/tollbooth"
|
||||
"github.com/didip/tollbooth_chi"
|
||||
"github.com/pressly/chi"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a limiter struct.
|
||||
limiter := tollbooth.NewLimiter(1, time.Second, nil)
|
||||
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(tollbooth_chi.LimitHandler(limiter))
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("Hello, world!"))
|
||||
})
|
||||
|
||||
http.ListenAndServe(":12345", r)
|
||||
}
|
||||
```
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package tollbooth_chi
|
||||
|
||||
import (
|
||||
"github.com/didip/tollbooth"
|
||||
"github.com/didip/tollbooth/limiter"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func LimitHandler(lmt *limiter.Limiter) func(http.Handler) http.Handler {
|
||||
return func(handler http.Handler) http.Handler {
|
||||
wrapper := &limiterWrapper{
|
||||
lmt: lmt,
|
||||
}
|
||||
|
||||
wrapper.handler = handler
|
||||
return wrapper
|
||||
}
|
||||
}
|
||||
|
||||
type limiterWrapper struct {
|
||||
lmt *limiter.Limiter
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
func (l *limiterWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
http.Error(w, "Context was canceled", http.StatusServiceUnavailable)
|
||||
return
|
||||
|
||||
default:
|
||||
httpError := tollbooth.LimitByRequest(l.lmt, w, r)
|
||||
if httpError != nil {
|
||||
w.Header().Add("Content-Type", l.lmt.GetMessageContentType())
|
||||
w.WriteHeader(httpError.StatusCode)
|
||||
w.Write([]byte(httpError.Message))
|
||||
return
|
||||
}
|
||||
|
||||
l.handler.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
-124
@@ -1,124 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
func TestContentCharset(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var tests = []struct {
|
||||
name string
|
||||
inputValue string
|
||||
inputContentCharset []string
|
||||
want int
|
||||
}{
|
||||
{
|
||||
"should accept requests with a matching charset",
|
||||
"application/json; charset=UTF-8",
|
||||
[]string{"UTF-8"},
|
||||
http.StatusOK,
|
||||
},
|
||||
{
|
||||
"should be case-insensitive",
|
||||
"application/json; charset=utf-8",
|
||||
[]string{"UTF-8"},
|
||||
http.StatusOK,
|
||||
},
|
||||
{
|
||||
"should accept requests with a matching charset with extra values",
|
||||
"application/json; foo=bar; charset=UTF-8; spam=eggs",
|
||||
[]string{"UTF-8"},
|
||||
http.StatusOK,
|
||||
},
|
||||
{
|
||||
"should accept requests with a matching charset when multiple charsets are supported",
|
||||
"text/xml; charset=UTF-8",
|
||||
[]string{"UTF-8", "Latin-1"},
|
||||
http.StatusOK,
|
||||
},
|
||||
{
|
||||
"should accept requests with no charset if empty charset headers are allowed",
|
||||
"text/xml",
|
||||
[]string{"UTF-8", ""},
|
||||
http.StatusOK,
|
||||
},
|
||||
{
|
||||
"should not accept requests with no charset if empty charset headers are not allowed",
|
||||
"text/xml",
|
||||
[]string{"UTF-8"},
|
||||
http.StatusUnsupportedMediaType,
|
||||
},
|
||||
{
|
||||
"should not accept requests with a mismatching charset",
|
||||
"text/plain; charset=Latin-1",
|
||||
[]string{"UTF-8"},
|
||||
http.StatusUnsupportedMediaType,
|
||||
},
|
||||
{
|
||||
"should not accept requests with a mismatching charset even if empty charsets are allowed",
|
||||
"text/plain; charset=Latin-1",
|
||||
[]string{"UTF-8", ""},
|
||||
http.StatusUnsupportedMediaType,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
var tt = tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var recorder = httptest.NewRecorder()
|
||||
|
||||
var r = chi.NewRouter()
|
||||
r.Use(ContentCharset(tt.inputContentCharset...))
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
var req, _ = http.NewRequest("GET", "/", nil)
|
||||
req.Header.Set("Content-Type", tt.inputValue)
|
||||
|
||||
r.ServeHTTP(recorder, req)
|
||||
var res = recorder.Result()
|
||||
|
||||
if res.StatusCode != tt.want {
|
||||
t.Errorf("response is incorrect, got %d, want %d", recorder.Code, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var s1, s2 = split(" type1;type2 ", ";")
|
||||
|
||||
if s1 != "type1" || s2 != "type2" {
|
||||
t.Errorf("Want type1, type2 got %s, %s", s1, s2)
|
||||
}
|
||||
|
||||
s1, s2 = split("type1 ", ";")
|
||||
|
||||
if s1 != "type1" {
|
||||
t.Errorf("Want \"type1\" got \"%s\"", s1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContentEncoding(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if !contentEncoding("application/json; foo=bar; charset=utf-8; spam=eggs", []string{"utf-8"}...) {
|
||||
t.Error("Want true, got false")
|
||||
}
|
||||
|
||||
if contentEncoding("text/plain; charset=latin-1", []string{"utf-8"}...) {
|
||||
t.Error("Want false, got true")
|
||||
}
|
||||
|
||||
if !contentEncoding("text/xml; charset=UTF-8", []string{"latin-1", "utf-8"}...) {
|
||||
t.Error("Want true, got false")
|
||||
}
|
||||
}
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
func TestGetHead(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
r.Use(GetHead)
|
||||
r.Get("/hi", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Test", "yes")
|
||||
w.Write([]byte("bye"))
|
||||
})
|
||||
r.Route("/articles", func(r chi.Router) {
|
||||
r.Get("/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
w.Header().Set("X-Article", id)
|
||||
w.Write([]byte("article:" + id))
|
||||
})
|
||||
})
|
||||
r.Route("/users", func(r chi.Router) {
|
||||
r.Head("/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-User", "-")
|
||||
w.Write([]byte("user"))
|
||||
})
|
||||
r.Get("/{id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
w.Header().Set("X-User", id)
|
||||
w.Write([]byte("user:" + id))
|
||||
})
|
||||
})
|
||||
|
||||
ts := httptest.NewServer(r)
|
||||
defer ts.Close()
|
||||
|
||||
if _, body := testRequest(t, ts, "GET", "/hi", nil); body != "bye" {
|
||||
t.Fatalf(body)
|
||||
}
|
||||
if req, body := testRequest(t, ts, "HEAD", "/hi", nil); body != "" || req.Header.Get("X-Test") != "yes" {
|
||||
t.Fatalf(body)
|
||||
}
|
||||
if _, body := testRequest(t, ts, "GET", "/", nil); body != "404 page not found\n" {
|
||||
t.Fatalf(body)
|
||||
}
|
||||
if req, body := testRequest(t, ts, "HEAD", "/", nil); body != "" || req.StatusCode != 404 {
|
||||
t.Fatalf(body)
|
||||
}
|
||||
|
||||
if _, body := testRequest(t, ts, "GET", "/articles/5", nil); body != "article:5" {
|
||||
t.Fatalf(body)
|
||||
}
|
||||
if req, body := testRequest(t, ts, "HEAD", "/articles/5", nil); body != "" || req.Header.Get("X-Article") != "5" {
|
||||
t.Fatalf("expecting X-Article header '5' but got '%s'", req.Header.Get("X-Article"))
|
||||
}
|
||||
|
||||
if _, body := testRequest(t, ts, "GET", "/users/1", nil); body != "user:1" {
|
||||
t.Fatalf(body)
|
||||
}
|
||||
if req, body := testRequest(t, ts, "HEAD", "/users/1", nil); body != "" || req.Header.Get("X-User") != "-" {
|
||||
t.Fatalf("expecting X-User header '-' but got '%s'", req.Header.Get("X-User"))
|
||||
}
|
||||
}
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
// +build go1.8
|
||||
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
// NOTE: we must import `golang.org/x/net/http2` in order to explicitly enable
|
||||
// http2 transports for certain tests. The runtime pkg does not have this dependency
|
||||
// though as the transport configuration happens under the hood on go 1.7+.
|
||||
|
||||
func TestWrapWriterHTTP2(t *testing.T) {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, cn := w.(http.CloseNotifier)
|
||||
if !cn {
|
||||
t.Fatal("request should have been a http.CloseNotifier")
|
||||
}
|
||||
_, fl := w.(http.Flusher)
|
||||
if !fl {
|
||||
t.Fatal("request should have been a http.Flusher")
|
||||
}
|
||||
_, hj := w.(http.Hijacker)
|
||||
if hj {
|
||||
t.Fatal("request should not have been a http.Hijacker")
|
||||
}
|
||||
_, rf := w.(io.ReaderFrom)
|
||||
if rf {
|
||||
t.Fatal("request should not have been a io.ReaderFrom")
|
||||
}
|
||||
_, ps := w.(http.Pusher)
|
||||
if !ps {
|
||||
t.Fatal("request should have been a http.Pusher")
|
||||
}
|
||||
|
||||
w.Write([]byte("OK"))
|
||||
})
|
||||
|
||||
wmw := func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
next.ServeHTTP(NewWrapResponseWriter(w, r.ProtoMajor), r)
|
||||
})
|
||||
}
|
||||
|
||||
server := http.Server{
|
||||
Addr: ":7072",
|
||||
Handler: wmw(handler),
|
||||
}
|
||||
// By serving over TLS, we get HTTP2 requests
|
||||
go server.ListenAndServeTLS(testdataDir+"/cert.pem", testdataDir+"/key.pem")
|
||||
defer server.Close()
|
||||
// We need the server to start before making the request
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http2.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
// The certificates we are using are self signed
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := client.Get("https://localhost:7072")
|
||||
if err != nil {
|
||||
t.Fatalf("could not get server: %v", err)
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
t.Fatalf("non 200 response: %v", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// NOTE: we must import `golang.org/x/net/http2` in order to explicitly enable
|
||||
// http2 transports for certain tests. The runtime pkg does not have this dependency
|
||||
// though as the transport configuration happens under the hood on go 1.7+.
|
||||
|
||||
var testdataDir string
|
||||
|
||||
func init() {
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
testdataDir = path.Join(path.Dir(filename), "/../testdata")
|
||||
}
|
||||
|
||||
func testRequest(t *testing.T, ts *httptest.Server, method, path string, body io.Reader) (*http.Response, string) {
|
||||
req, err := http.NewRequest(method, ts.URL+path, body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
respBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return nil, ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return resp, string(respBody)
|
||||
}
|
||||
|
||||
func assertNoError(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
t.Fatalf("expecting no error")
|
||||
}
|
||||
}
|
||||
|
||||
func assertError(t *testing.T, err error) {
|
||||
if err == nil {
|
||||
t.Fatalf("expecting error")
|
||||
}
|
||||
}
|
||||
|
||||
func assertEqual(t *testing.T, a, b interface{}) {
|
||||
if !reflect.DeepEqual(a, b) {
|
||||
t.Fatalf("expecting values to be equal but got: '%v' and '%v'", a, b)
|
||||
}
|
||||
}
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
func TestXRealIP(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
req.Header.Add("X-Real-IP", "100.100.100.100")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Use(RealIP)
|
||||
|
||||
realIP := ""
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
realIP = r.RemoteAddr
|
||||
w.Write([]byte("Hello World"))
|
||||
})
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != 200 {
|
||||
t.Fatal("Response Code should be 200")
|
||||
}
|
||||
|
||||
if realIP != "100.100.100.100" {
|
||||
t.Fatal("Test get real IP error.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestXForwardForIP(t *testing.T) {
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
req.Header.Add("X-Forwarded-For", "100.100.100.100")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Use(RealIP)
|
||||
|
||||
realIP := ""
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
realIP = r.RemoteAddr
|
||||
w.Write([]byte("Hello World"))
|
||||
})
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != 200 {
|
||||
t.Fatal("Response Code should be 200")
|
||||
}
|
||||
|
||||
if realIP != "100.100.100.100" {
|
||||
t.Fatal("Test get real IP error.")
|
||||
}
|
||||
}
|
||||
-147
@@ -1,147 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
func TestStripSlashes(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
// This middleware must be mounted at the top level of the router, not at the end-handler
|
||||
// because then it'll be too late and will end up in a 404
|
||||
r.Use(StripSlashes)
|
||||
|
||||
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte("nothing here"))
|
||||
})
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("root"))
|
||||
})
|
||||
|
||||
r.Route("/accounts/{accountID}", func(r chi.Router) {
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
accountID := chi.URLParam(r, "accountID")
|
||||
w.Write([]byte(accountID))
|
||||
})
|
||||
})
|
||||
|
||||
ts := httptest.NewServer(r)
|
||||
defer ts.Close()
|
||||
|
||||
if _, resp := testRequest(t, ts, "GET", "/", nil); resp != "root" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "//", nil); resp != "root" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "/accounts/admin", nil); resp != "admin" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "/accounts/admin/", nil); resp != "admin" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "/nothing-here", nil); resp != "nothing here" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripSlashesInRoute(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte("nothing here"))
|
||||
})
|
||||
|
||||
r.Get("/hi", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("hi"))
|
||||
})
|
||||
|
||||
r.Route("/accounts/{accountID}", func(r chi.Router) {
|
||||
r.Use(StripSlashes)
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("accounts index"))
|
||||
})
|
||||
r.Get("/query", func(w http.ResponseWriter, r *http.Request) {
|
||||
accountID := chi.URLParam(r, "accountID")
|
||||
w.Write([]byte(accountID))
|
||||
})
|
||||
})
|
||||
|
||||
ts := httptest.NewServer(r)
|
||||
defer ts.Close()
|
||||
|
||||
if _, resp := testRequest(t, ts, "GET", "/hi", nil); resp != "hi" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "/hi/", nil); resp != "nothing here" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "/accounts/admin", nil); resp != "accounts index" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "/accounts/admin/", nil); resp != "accounts index" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "/accounts/admin/query", nil); resp != "admin" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
if _, resp := testRequest(t, ts, "GET", "/accounts/admin/query/", nil); resp != "admin" {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedirectSlashes(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
// This middleware must be mounted at the top level of the router, not at the end-handler
|
||||
// because then it'll be too late and will end up in a 404
|
||||
r.Use(RedirectSlashes)
|
||||
|
||||
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte("nothing here"))
|
||||
})
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("root"))
|
||||
})
|
||||
|
||||
r.Route("/accounts/{accountID}", func(r chi.Router) {
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
accountID := chi.URLParam(r, "accountID")
|
||||
w.Write([]byte(accountID))
|
||||
})
|
||||
})
|
||||
|
||||
ts := httptest.NewServer(r)
|
||||
defer ts.Close()
|
||||
|
||||
if req, resp := testRequest(t, ts, "GET", "/", nil); resp != "root" && req.StatusCode != 200 {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
|
||||
// NOTE: the testRequest client will follow the redirection..
|
||||
if req, resp := testRequest(t, ts, "GET", "//", nil); resp != "root" && req.StatusCode != 200 {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
|
||||
if req, resp := testRequest(t, ts, "GET", "/accounts/admin", nil); resp != "admin" && req.StatusCode != 200 {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
|
||||
// NOTE: the testRequest client will follow the redirection..
|
||||
if req, resp := testRequest(t, ts, "GET", "/accounts/admin/", nil); resp != "admin" && req.StatusCode != 200 {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
|
||||
if req, resp := testRequest(t, ts, "GET", "/nothing-here", nil); resp != "nothing here" && req.StatusCode != 200 {
|
||||
t.Fatalf(resp)
|
||||
}
|
||||
}
|
||||
-204
@@ -1,204 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
var testContent = []byte("Hello world!")
|
||||
|
||||
func TestThrottleBacklog(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(ThrottleBacklog(10, 50, time.Second*10))
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
time.Sleep(time.Second * 1) // Expensive operation.
|
||||
w.Write(testContent)
|
||||
})
|
||||
|
||||
server := httptest.NewServer(r)
|
||||
defer server.Close()
|
||||
|
||||
client := http.Client{
|
||||
Timeout: time.Second * 5, // Maximum waiting time.
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// The throttler proccesses 10 consecutive requests, each one of those
|
||||
// requests lasts 1s. The maximum number of requests this can possible serve
|
||||
// before the clients time out (5s) is 40.
|
||||
for i := 0; i < 40; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
res, err := client.Get(server.URL)
|
||||
assertNoError(t, err)
|
||||
|
||||
assertEqual(t, http.StatusOK, res.StatusCode)
|
||||
buf, err := ioutil.ReadAll(res.Body)
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, testContent, buf)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestThrottleClientTimeout(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(ThrottleBacklog(10, 50, time.Second*10))
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
time.Sleep(time.Second * 5) // Expensive operation.
|
||||
w.Write(testContent)
|
||||
})
|
||||
|
||||
server := httptest.NewServer(r)
|
||||
defer server.Close()
|
||||
|
||||
client := http.Client{
|
||||
Timeout: time.Second * 3, // Maximum waiting time.
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
_, err := client.Get(server.URL)
|
||||
assertError(t, err)
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestThrottleTriggerGatewayTimeout(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(ThrottleBacklog(50, 100, time.Second*5))
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
time.Sleep(time.Second * 10) // Expensive operation.
|
||||
w.Write(testContent)
|
||||
})
|
||||
|
||||
server := httptest.NewServer(r)
|
||||
defer server.Close()
|
||||
|
||||
client := http.Client{
|
||||
Timeout: time.Second * 60, // Maximum waiting time.
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
// These requests will be processed normally until they finish.
|
||||
for i := 0; i < 50; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
res, err := client.Get(server.URL)
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
}(i)
|
||||
}
|
||||
|
||||
time.Sleep(time.Second * 1)
|
||||
|
||||
// These requests will wait for the first batch to complete but it will take
|
||||
// too much time, so they will eventually receive a timeout error.
|
||||
for i := 0; i < 50; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
res, err := client.Get(server.URL)
|
||||
assertNoError(t, err)
|
||||
|
||||
buf, err := ioutil.ReadAll(res.Body)
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, http.StatusServiceUnavailable, res.StatusCode)
|
||||
assertEqual(t, errTimedOut, strings.TrimSpace(string(buf)))
|
||||
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestThrottleMaximum(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(ThrottleBacklog(50, 50, time.Second*5))
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
time.Sleep(time.Second * 2) // Expensive operation.
|
||||
w.Write(testContent)
|
||||
})
|
||||
|
||||
server := httptest.NewServer(r)
|
||||
defer server.Close()
|
||||
|
||||
client := http.Client{
|
||||
Timeout: time.Second * 60, // Maximum waiting time.
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
res, err := client.Get(server.URL)
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
buf, err := ioutil.ReadAll(res.Body)
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, testContent, buf)
|
||||
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Wait less time than what the server takes to reply.
|
||||
time.Sleep(time.Second * 1)
|
||||
|
||||
// At this point the server is still processing, all the following request
|
||||
// will be beyond the server capacity.
|
||||
for i := 0; i < 100; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
res, err := client.Get(server.URL)
|
||||
assertNoError(t, err)
|
||||
|
||||
buf, err := ioutil.ReadAll(res.Body)
|
||||
assertNoError(t, err)
|
||||
assertEqual(t, http.StatusServiceUnavailable, res.StatusCode)
|
||||
assertEqual(t, errCapacityExceeded, strings.TrimSpace(string(buf)))
|
||||
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFlushWriterRemembersWroteHeaderWhenFlushed(t *testing.T) {
|
||||
f := &flushWriter{basicWriter{ResponseWriter: httptest.NewRecorder()}}
|
||||
f.Flush()
|
||||
|
||||
if !f.wroteHeader {
|
||||
t.Fatal("want Flush to have set wroteHeader=true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpFancyWriterRemembersWroteHeaderWhenFlushed(t *testing.T) {
|
||||
f := &httpFancyWriter{basicWriter{ResponseWriter: httptest.NewRecorder()}}
|
||||
f.Flush()
|
||||
|
||||
if !f.wroteHeader {
|
||||
t.Fatal("want Flush to have set wroteHeader=true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttp2FancyWriterRemembersWroteHeaderWhenFlushed(t *testing.T) {
|
||||
f := &http2FancyWriter{basicWriter{ResponseWriter: httptest.NewRecorder()}}
|
||||
f.Flush()
|
||||
|
||||
if !f.wroteHeader {
|
||||
t.Fatal("want Flush to have set wroteHeader=true")
|
||||
}
|
||||
}
|
||||
-1662
File diff suppressed because it is too large
Load Diff
-467
@@ -1,467 +0,0 @@
|
||||
package chi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTree(t *testing.T) {
|
||||
hStub := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hIndex := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hFavicon := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hArticleList := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hArticleNear := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hArticleShow := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hArticleShowRelated := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hArticleShowOpts := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hArticleSlug := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hArticleByUser := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hUserList := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hUserShow := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hAdminCatchall := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hAdminAppShow := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hAdminAppShowCatchall := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hUserProfile := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hUserSuper := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hUserAll := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hHubView1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hHubView2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hHubView3 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
tr := &node{}
|
||||
|
||||
tr.InsertRoute(mGET, "/", hIndex)
|
||||
tr.InsertRoute(mGET, "/favicon.ico", hFavicon)
|
||||
|
||||
tr.InsertRoute(mGET, "/pages/*", hStub)
|
||||
|
||||
tr.InsertRoute(mGET, "/article", hArticleList)
|
||||
tr.InsertRoute(mGET, "/article/", hArticleList)
|
||||
|
||||
tr.InsertRoute(mGET, "/article/near", hArticleNear)
|
||||
tr.InsertRoute(mGET, "/article/{id}", hStub)
|
||||
tr.InsertRoute(mGET, "/article/{id}", hArticleShow)
|
||||
tr.InsertRoute(mGET, "/article/{id}", hArticleShow) // duplicate will have no effect
|
||||
tr.InsertRoute(mGET, "/article/@{user}", hArticleByUser)
|
||||
|
||||
tr.InsertRoute(mGET, "/article/{sup}/{opts}", hArticleShowOpts)
|
||||
tr.InsertRoute(mGET, "/article/{id}/{opts}", hArticleShowOpts) // overwrite above route, latest wins
|
||||
|
||||
tr.InsertRoute(mGET, "/article/{iffd}/edit", hStub)
|
||||
tr.InsertRoute(mGET, "/article/{id}//related", hArticleShowRelated)
|
||||
tr.InsertRoute(mGET, "/article/slug/{month}/-/{day}/{year}", hArticleSlug)
|
||||
|
||||
tr.InsertRoute(mGET, "/admin/user", hUserList)
|
||||
tr.InsertRoute(mGET, "/admin/user/", hStub) // will get replaced by next route
|
||||
tr.InsertRoute(mGET, "/admin/user/", hUserList)
|
||||
|
||||
tr.InsertRoute(mGET, "/admin/user//{id}", hUserShow)
|
||||
tr.InsertRoute(mGET, "/admin/user/{id}", hUserShow)
|
||||
|
||||
tr.InsertRoute(mGET, "/admin/apps/{id}", hAdminAppShow)
|
||||
tr.InsertRoute(mGET, "/admin/apps/{id}/*ff", hAdminAppShowCatchall) // TODO: ALLOWED...? prob not.. panic..?
|
||||
|
||||
tr.InsertRoute(mGET, "/admin/*ff", hStub) // catchall segment will get replaced by next route
|
||||
tr.InsertRoute(mGET, "/admin/*", hAdminCatchall)
|
||||
|
||||
tr.InsertRoute(mGET, "/users/{userID}/profile", hUserProfile)
|
||||
tr.InsertRoute(mGET, "/users/super/*", hUserSuper)
|
||||
tr.InsertRoute(mGET, "/users/*", hUserAll)
|
||||
|
||||
tr.InsertRoute(mGET, "/hubs/{hubID}/view", hHubView1)
|
||||
tr.InsertRoute(mGET, "/hubs/{hubID}/view/*", hHubView2)
|
||||
sr := NewRouter()
|
||||
sr.Get("/users", hHubView3)
|
||||
tr.InsertRoute(mGET, "/hubs/{hubID}/*", sr)
|
||||
tr.InsertRoute(mGET, "/hubs/{hubID}/users", hHubView3)
|
||||
|
||||
tests := []struct {
|
||||
r string // input request path
|
||||
h http.Handler // output matched handler
|
||||
k []string // output param keys
|
||||
v []string // output param values
|
||||
}{
|
||||
{r: "/", h: hIndex, k: []string{}, v: []string{}},
|
||||
{r: "/favicon.ico", h: hFavicon, k: []string{}, v: []string{}},
|
||||
|
||||
{r: "/pages", h: nil, k: []string{}, v: []string{}},
|
||||
{r: "/pages/", h: hStub, k: []string{"*"}, v: []string{""}},
|
||||
{r: "/pages/yes", h: hStub, k: []string{"*"}, v: []string{"yes"}},
|
||||
|
||||
{r: "/article", h: hArticleList, k: []string{}, v: []string{}},
|
||||
{r: "/article/", h: hArticleList, k: []string{}, v: []string{}},
|
||||
{r: "/article/near", h: hArticleNear, k: []string{}, v: []string{}},
|
||||
{r: "/article/neard", h: hArticleShow, k: []string{"id"}, v: []string{"neard"}},
|
||||
{r: "/article/123", h: hArticleShow, k: []string{"id"}, v: []string{"123"}},
|
||||
{r: "/article/123/456", h: hArticleShowOpts, k: []string{"id", "opts"}, v: []string{"123", "456"}},
|
||||
{r: "/article/@peter", h: hArticleByUser, k: []string{"user"}, v: []string{"peter"}},
|
||||
{r: "/article/22//related", h: hArticleShowRelated, k: []string{"id"}, v: []string{"22"}},
|
||||
{r: "/article/111/edit", h: hStub, k: []string{"iffd"}, v: []string{"111"}},
|
||||
{r: "/article/slug/sept/-/4/2015", h: hArticleSlug, k: []string{"month", "day", "year"}, v: []string{"sept", "4", "2015"}},
|
||||
{r: "/article/:id", h: hArticleShow, k: []string{"id"}, v: []string{":id"}},
|
||||
|
||||
{r: "/admin/user", h: hUserList, k: []string{}, v: []string{}},
|
||||
{r: "/admin/user/", h: hUserList, k: []string{}, v: []string{}},
|
||||
{r: "/admin/user/1", h: hUserShow, k: []string{"id"}, v: []string{"1"}},
|
||||
{r: "/admin/user//1", h: hUserShow, k: []string{"id"}, v: []string{"1"}},
|
||||
{r: "/admin/hi", h: hAdminCatchall, k: []string{"*"}, v: []string{"hi"}},
|
||||
{r: "/admin/lots/of/:fun", h: hAdminCatchall, k: []string{"*"}, v: []string{"lots/of/:fun"}},
|
||||
{r: "/admin/apps/333", h: hAdminAppShow, k: []string{"id"}, v: []string{"333"}},
|
||||
{r: "/admin/apps/333/woot", h: hAdminAppShowCatchall, k: []string{"id", "*"}, v: []string{"333", "woot"}},
|
||||
|
||||
{r: "/hubs/123/view", h: hHubView1, k: []string{"hubID"}, v: []string{"123"}},
|
||||
{r: "/hubs/123/view/index.html", h: hHubView2, k: []string{"hubID", "*"}, v: []string{"123", "index.html"}},
|
||||
{r: "/hubs/123/users", h: hHubView3, k: []string{"hubID"}, v: []string{"123"}},
|
||||
|
||||
{r: "/users/123/profile", h: hUserProfile, k: []string{"userID"}, v: []string{"123"}},
|
||||
{r: "/users/super/123/okay/yes", h: hUserSuper, k: []string{"*"}, v: []string{"123/okay/yes"}},
|
||||
{r: "/users/123/okay/yes", h: hUserAll, k: []string{"*"}, v: []string{"123/okay/yes"}},
|
||||
}
|
||||
|
||||
// log.Println("~~~~~~~~~")
|
||||
// log.Println("~~~~~~~~~")
|
||||
// debugPrintTree(0, 0, tr, 0)
|
||||
// log.Println("~~~~~~~~~")
|
||||
// log.Println("~~~~~~~~~")
|
||||
|
||||
for i, tt := range tests {
|
||||
rctx := NewRouteContext()
|
||||
|
||||
_, handlers, _ := tr.FindRoute(rctx, mGET, tt.r)
|
||||
|
||||
var handler http.Handler
|
||||
if methodHandler, ok := handlers[mGET]; ok {
|
||||
handler = methodHandler.handler
|
||||
}
|
||||
|
||||
paramKeys := rctx.routeParams.Keys
|
||||
paramValues := rctx.routeParams.Values
|
||||
|
||||
if fmt.Sprintf("%v", tt.h) != fmt.Sprintf("%v", handler) {
|
||||
t.Errorf("input [%d]: find '%s' expecting handler:%v , got:%v", i, tt.r, tt.h, handler)
|
||||
}
|
||||
if !stringSliceEqual(tt.k, paramKeys) {
|
||||
t.Errorf("input [%d]: find '%s' expecting paramKeys:(%d)%v , got:(%d)%v", i, tt.r, len(tt.k), tt.k, len(paramKeys), paramKeys)
|
||||
}
|
||||
if !stringSliceEqual(tt.v, paramValues) {
|
||||
t.Errorf("input [%d]: find '%s' expecting paramValues:(%d)%v , got:(%d)%v", i, tt.r, len(tt.v), tt.v, len(paramValues), paramValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeMoar(t *testing.T) {
|
||||
hStub := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub3 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub4 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub5 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub6 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub7 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub8 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub9 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub10 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub11 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub12 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub13 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub14 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub15 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub16 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
// TODO: panic if we see {id}{x} because we're missing a delimiter, its not possible.
|
||||
// also {:id}* is not possible.
|
||||
|
||||
tr := &node{}
|
||||
|
||||
tr.InsertRoute(mGET, "/articlefun", hStub5)
|
||||
tr.InsertRoute(mGET, "/articles/{id}", hStub)
|
||||
tr.InsertRoute(mDELETE, "/articles/{slug}", hStub8)
|
||||
tr.InsertRoute(mGET, "/articles/search", hStub1)
|
||||
tr.InsertRoute(mGET, "/articles/{id}:delete", hStub8)
|
||||
tr.InsertRoute(mGET, "/articles/{iidd}!sup", hStub4)
|
||||
tr.InsertRoute(mGET, "/articles/{id}:{op}", hStub3)
|
||||
tr.InsertRoute(mGET, "/articles/{id}:{op}", hStub2) // this route sets a new handler for the above route
|
||||
tr.InsertRoute(mGET, "/articles/{slug:^[a-z]+}/posts", hStub) // up to tail '/' will only match if contents match the rex
|
||||
tr.InsertRoute(mGET, "/articles/{id}/posts/{pid}", hStub6) // /articles/123/posts/1
|
||||
tr.InsertRoute(mGET, "/articles/{id}/posts/{month}/{day}/{year}/{slug}", hStub7) // /articles/123/posts/09/04/1984/juice
|
||||
tr.InsertRoute(mGET, "/articles/{id}.json", hStub10)
|
||||
tr.InsertRoute(mGET, "/articles/{id}/data.json", hStub11)
|
||||
tr.InsertRoute(mGET, "/articles/files/{file}.{ext}", hStub12)
|
||||
tr.InsertRoute(mPUT, "/articles/me", hStub13)
|
||||
|
||||
// TODO: make a separate test case for this one..
|
||||
// tr.InsertRoute(mGET, "/articles/{id}/{id}", hStub1) // panic expected, we're duplicating param keys
|
||||
|
||||
tr.InsertRoute(mGET, "/pages/*ff", hStub) // TODO: panic, allow it..?
|
||||
tr.InsertRoute(mGET, "/pages/*", hStub9)
|
||||
|
||||
tr.InsertRoute(mGET, "/users/{id}", hStub14)
|
||||
tr.InsertRoute(mGET, "/users/{id}/settings/{key}", hStub15)
|
||||
tr.InsertRoute(mGET, "/users/{id}/settings/*", hStub16)
|
||||
|
||||
tests := []struct {
|
||||
m methodTyp // input request http method
|
||||
r string // input request path
|
||||
h http.Handler // output matched handler
|
||||
k []string // output param keys
|
||||
v []string // output param values
|
||||
}{
|
||||
{m: mGET, r: "/articles/search", h: hStub1, k: []string{}, v: []string{}},
|
||||
{m: mGET, r: "/articlefun", h: hStub5, k: []string{}, v: []string{}},
|
||||
{m: mGET, r: "/articles/123", h: hStub, k: []string{"id"}, v: []string{"123"}},
|
||||
{m: mDELETE, r: "/articles/123mm", h: hStub8, k: []string{"slug"}, v: []string{"123mm"}},
|
||||
{m: mGET, r: "/articles/789:delete", h: hStub8, k: []string{"id"}, v: []string{"789"}},
|
||||
{m: mGET, r: "/articles/789!sup", h: hStub4, k: []string{"iidd"}, v: []string{"789"}},
|
||||
{m: mGET, r: "/articles/123:sync", h: hStub2, k: []string{"id", "op"}, v: []string{"123", "sync"}},
|
||||
{m: mGET, r: "/articles/456/posts/1", h: hStub6, k: []string{"id", "pid"}, v: []string{"456", "1"}},
|
||||
{m: mGET, r: "/articles/456/posts/09/04/1984/juice", h: hStub7, k: []string{"id", "month", "day", "year", "slug"}, v: []string{"456", "09", "04", "1984", "juice"}},
|
||||
{m: mGET, r: "/articles/456.json", h: hStub10, k: []string{"id"}, v: []string{"456"}},
|
||||
{m: mGET, r: "/articles/456/data.json", h: hStub11, k: []string{"id"}, v: []string{"456"}},
|
||||
|
||||
{m: mGET, r: "/articles/files/file.zip", h: hStub12, k: []string{"file", "ext"}, v: []string{"file", "zip"}},
|
||||
{m: mGET, r: "/articles/files/photos.tar.gz", h: hStub12, k: []string{"file", "ext"}, v: []string{"photos", "tar.gz"}},
|
||||
{m: mGET, r: "/articles/files/photos.tar.gz", h: hStub12, k: []string{"file", "ext"}, v: []string{"photos", "tar.gz"}},
|
||||
|
||||
{m: mPUT, r: "/articles/me", h: hStub13, k: []string{}, v: []string{}},
|
||||
{m: mGET, r: "/articles/me", h: hStub, k: []string{"id"}, v: []string{"me"}},
|
||||
{m: mGET, r: "/pages", h: nil, k: []string{}, v: []string{}},
|
||||
{m: mGET, r: "/pages/", h: hStub9, k: []string{"*"}, v: []string{""}},
|
||||
{m: mGET, r: "/pages/yes", h: hStub9, k: []string{"*"}, v: []string{"yes"}},
|
||||
|
||||
{m: mGET, r: "/users/1", h: hStub14, k: []string{"id"}, v: []string{"1"}},
|
||||
{m: mGET, r: "/users/", h: nil, k: []string{}, v: []string{}},
|
||||
{m: mGET, r: "/users/2/settings/password", h: hStub15, k: []string{"id", "key"}, v: []string{"2", "password"}},
|
||||
{m: mGET, r: "/users/2/settings/", h: hStub16, k: []string{"id", "*"}, v: []string{"2", ""}},
|
||||
}
|
||||
|
||||
// log.Println("~~~~~~~~~")
|
||||
// log.Println("~~~~~~~~~")
|
||||
// debugPrintTree(0, 0, tr, 0)
|
||||
// log.Println("~~~~~~~~~")
|
||||
// log.Println("~~~~~~~~~")
|
||||
|
||||
for i, tt := range tests {
|
||||
rctx := NewRouteContext()
|
||||
|
||||
_, handlers, _ := tr.FindRoute(rctx, tt.m, tt.r)
|
||||
|
||||
var handler http.Handler
|
||||
if methodHandler, ok := handlers[tt.m]; ok {
|
||||
handler = methodHandler.handler
|
||||
}
|
||||
|
||||
paramKeys := rctx.routeParams.Keys
|
||||
paramValues := rctx.routeParams.Values
|
||||
|
||||
if fmt.Sprintf("%v", tt.h) != fmt.Sprintf("%v", handler) {
|
||||
t.Errorf("input [%d]: find '%s' expecting handler:%v , got:%v", i, tt.r, tt.h, handler)
|
||||
}
|
||||
if !stringSliceEqual(tt.k, paramKeys) {
|
||||
t.Errorf("input [%d]: find '%s' expecting paramKeys:(%d)%v , got:(%d)%v", i, tt.r, len(tt.k), tt.k, len(paramKeys), paramKeys)
|
||||
}
|
||||
if !stringSliceEqual(tt.v, paramValues) {
|
||||
t.Errorf("input [%d]: find '%s' expecting paramValues:(%d)%v , got:(%d)%v", i, tt.r, len(tt.v), tt.v, len(paramValues), paramValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeRegexp(t *testing.T) {
|
||||
hStub1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub3 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub4 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub5 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub6 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub7 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
tr := &node{}
|
||||
tr.InsertRoute(mGET, "/articles/{rid:^[0-9]{5,6}}", hStub7)
|
||||
tr.InsertRoute(mGET, "/articles/{zid:^0[0-9]+}", hStub3)
|
||||
tr.InsertRoute(mGET, "/articles/{name:^@[a-z]+}/posts", hStub4)
|
||||
tr.InsertRoute(mGET, "/articles/{op:^[0-9]+}/run", hStub5)
|
||||
tr.InsertRoute(mGET, "/articles/{id:^[0-9]+}", hStub1)
|
||||
tr.InsertRoute(mGET, "/articles/{id:^[1-9]+}-{aux}", hStub6)
|
||||
tr.InsertRoute(mGET, "/articles/{slug}", hStub2)
|
||||
|
||||
// log.Println("~~~~~~~~~")
|
||||
// log.Println("~~~~~~~~~")
|
||||
// debugPrintTree(0, 0, tr, 0)
|
||||
// log.Println("~~~~~~~~~")
|
||||
// log.Println("~~~~~~~~~")
|
||||
|
||||
tests := []struct {
|
||||
r string // input request path
|
||||
h http.Handler // output matched handler
|
||||
k []string // output param keys
|
||||
v []string // output param values
|
||||
}{
|
||||
{r: "/articles", h: nil, k: []string{}, v: []string{}},
|
||||
{r: "/articles/12345", h: hStub7, k: []string{"rid"}, v: []string{"12345"}},
|
||||
{r: "/articles/123", h: hStub1, k: []string{"id"}, v: []string{"123"}},
|
||||
{r: "/articles/how-to-build-a-router", h: hStub2, k: []string{"slug"}, v: []string{"how-to-build-a-router"}},
|
||||
{r: "/articles/0456", h: hStub3, k: []string{"zid"}, v: []string{"0456"}},
|
||||
{r: "/articles/@pk/posts", h: hStub4, k: []string{"name"}, v: []string{"@pk"}},
|
||||
{r: "/articles/1/run", h: hStub5, k: []string{"op"}, v: []string{"1"}},
|
||||
{r: "/articles/1122", h: hStub1, k: []string{"id"}, v: []string{"1122"}},
|
||||
{r: "/articles/1122-yes", h: hStub6, k: []string{"id", "aux"}, v: []string{"1122", "yes"}},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
rctx := NewRouteContext()
|
||||
|
||||
_, handlers, _ := tr.FindRoute(rctx, mGET, tt.r)
|
||||
|
||||
var handler http.Handler
|
||||
if methodHandler, ok := handlers[mGET]; ok {
|
||||
handler = methodHandler.handler
|
||||
}
|
||||
|
||||
paramKeys := rctx.routeParams.Keys
|
||||
paramValues := rctx.routeParams.Values
|
||||
|
||||
if fmt.Sprintf("%v", tt.h) != fmt.Sprintf("%v", handler) {
|
||||
t.Errorf("input [%d]: find '%s' expecting handler:%v , got:%v", i, tt.r, tt.h, handler)
|
||||
}
|
||||
if !stringSliceEqual(tt.k, paramKeys) {
|
||||
t.Errorf("input [%d]: find '%s' expecting paramKeys:(%d)%v , got:(%d)%v", i, tt.r, len(tt.k), tt.k, len(paramKeys), paramKeys)
|
||||
}
|
||||
if !stringSliceEqual(tt.v, paramValues) {
|
||||
t.Errorf("input [%d]: find '%s' expecting paramValues:(%d)%v , got:(%d)%v", i, tt.r, len(tt.v), tt.v, len(paramValues), paramValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeRegexMatchWholeParam(t *testing.T) {
|
||||
hStub1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
rctx := NewRouteContext()
|
||||
tr := &node{}
|
||||
tr.InsertRoute(mGET, "/{id:[0-9]+}", hStub1)
|
||||
|
||||
tests := []struct {
|
||||
url string
|
||||
expectedHandler http.Handler
|
||||
}{
|
||||
{url: "/13", expectedHandler: hStub1},
|
||||
{url: "/a13", expectedHandler: nil},
|
||||
{url: "/13.jpg", expectedHandler: nil},
|
||||
{url: "/a13.jpg", expectedHandler: nil},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
_, _, handler := tr.FindRoute(rctx, mGET, tc.url)
|
||||
if fmt.Sprintf("%v", tc.expectedHandler) != fmt.Sprintf("%v", handler) {
|
||||
t.Errorf("expecting handler:%v , got:%v", tc.expectedHandler, handler)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeFindPattern(t *testing.T) {
|
||||
hStub1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
hStub3 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
tr := &node{}
|
||||
tr.InsertRoute(mGET, "/pages/*", hStub1)
|
||||
tr.InsertRoute(mGET, "/articles/{id}/*", hStub2)
|
||||
tr.InsertRoute(mGET, "/articles/{slug}/{uid}/*", hStub3)
|
||||
|
||||
if tr.findPattern("/pages") != false {
|
||||
t.Errorf("find /pages failed")
|
||||
}
|
||||
if tr.findPattern("/pages*") != false {
|
||||
t.Errorf("find /pages* failed - should be nil")
|
||||
}
|
||||
if tr.findPattern("/pages/*") == false {
|
||||
t.Errorf("find /pages/* failed")
|
||||
}
|
||||
if tr.findPattern("/articles/{id}/*") == false {
|
||||
t.Errorf("find /articles/{id}/* failed")
|
||||
}
|
||||
if tr.findPattern("/articles/{something}/*") == false {
|
||||
t.Errorf("find /articles/{something}/* failed")
|
||||
}
|
||||
if tr.findPattern("/articles/{slug}/{uid}/*") == false {
|
||||
t.Errorf("find /articles/{slug}/{uid}/* failed")
|
||||
}
|
||||
}
|
||||
|
||||
func debugPrintTree(parent int, i int, n *node, label byte) bool {
|
||||
numEdges := 0
|
||||
for _, nds := range n.children {
|
||||
numEdges += len(nds)
|
||||
}
|
||||
|
||||
// if n.handlers != nil {
|
||||
// log.Printf("[node %d parent:%d] typ:%d prefix:%s label:%s tail:%s numEdges:%d isLeaf:%v handler:%v pat:%s keys:%v\n", i, parent, n.typ, n.prefix, string(label), string(n.tail), numEdges, n.isLeaf(), n.handlers, n.pattern, n.paramKeys)
|
||||
// } else {
|
||||
// log.Printf("[node %d parent:%d] typ:%d prefix:%s label:%s tail:%s numEdges:%d isLeaf:%v pat:%s keys:%v\n", i, parent, n.typ, n.prefix, string(label), string(n.tail), numEdges, n.isLeaf(), n.pattern, n.paramKeys)
|
||||
// }
|
||||
if n.endpoints != nil {
|
||||
log.Printf("[node %d parent:%d] typ:%d prefix:%s label:%s tail:%s numEdges:%d isLeaf:%v handler:%v\n", i, parent, n.typ, n.prefix, string(label), string(n.tail), numEdges, n.isLeaf(), n.endpoints)
|
||||
} else {
|
||||
log.Printf("[node %d parent:%d] typ:%d prefix:%s label:%s tail:%s numEdges:%d isLeaf:%v\n", i, parent, n.typ, n.prefix, string(label), string(n.tail), numEdges, n.isLeaf())
|
||||
}
|
||||
parent = i
|
||||
for _, nds := range n.children {
|
||||
for _, e := range nds {
|
||||
i++
|
||||
if debugPrintTree(parent, i, e, e.label) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func stringSliceEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if b[i] != a[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func BenchmarkTreeGet(b *testing.B) {
|
||||
h1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
h2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
tr := &node{}
|
||||
tr.InsertRoute(mGET, "/", h1)
|
||||
tr.InsertRoute(mGET, "/ping", h2)
|
||||
tr.InsertRoute(mGET, "/pingall", h2)
|
||||
tr.InsertRoute(mGET, "/ping/{id}", h2)
|
||||
tr.InsertRoute(mGET, "/ping/{id}/woop", h2)
|
||||
tr.InsertRoute(mGET, "/ping/{id}/{opt}", h2)
|
||||
tr.InsertRoute(mGET, "/pinggggg", h2)
|
||||
tr.InsertRoute(mGET, "/hello", h1)
|
||||
|
||||
mctx := NewRouteContext()
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
mctx.Reset()
|
||||
tr.FindRoute(mctx, mGET, "/ping/123/456")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalker(t *testing.T) {
|
||||
r := bigMux()
|
||||
|
||||
// Walk the muxBig router tree.
|
||||
if err := Walk(r, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error {
|
||||
t.Logf("%v %v", method, route)
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
.DS_Store
|
||||
*.[568ao]
|
||||
*.ao
|
||||
*.so
|
||||
*.pyc
|
||||
._*
|
||||
.nfs.*
|
||||
[568a].out
|
||||
*~
|
||||
*.orig
|
||||
core
|
||||
_obj
|
||||
_test
|
||||
_testmain.go
|
||||
protoc-gen-go/testdata/multi/*.pb.go
|
||||
_conformance/_conformance
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
|
||||
install:
|
||||
- go get -v -d -t github.com/golang/protobuf/...
|
||||
- curl -L https://github.com/google/protobuf/releases/download/v3.3.0/protoc-3.3.0-linux-x86_64.zip -o /tmp/protoc.zip
|
||||
- unzip /tmp/protoc.zip -d $HOME/protoc
|
||||
|
||||
env:
|
||||
- PATH=$HOME/protoc/bin:$PATH
|
||||
|
||||
script:
|
||||
- make all test
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
# Go support for Protocol Buffers - Google's data interchange format
|
||||
#
|
||||
# Copyright 2010 The Go Authors. All rights reserved.
|
||||
# https://github.com/golang/protobuf
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
# Includable Makefile to add a rule for generating .pb.go files from .proto files
|
||||
# (Google protocol buffer descriptions).
|
||||
# Typical use if myproto.proto is a file in package mypackage in this directory:
|
||||
#
|
||||
# include $(GOROOT)/src/pkg/github.com/golang/protobuf/Make.protobuf
|
||||
|
||||
%.pb.go: %.proto
|
||||
protoc --go_out=. $<
|
||||
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
# Go support for Protocol Buffers - Google's data interchange format
|
||||
#
|
||||
# Copyright 2010 The Go Authors. All rights reserved.
|
||||
# https://github.com/golang/protobuf
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
all: install
|
||||
|
||||
install:
|
||||
go install ./proto ./jsonpb ./ptypes
|
||||
go install ./protoc-gen-go
|
||||
|
||||
test:
|
||||
go test ./proto ./jsonpb ./ptypes
|
||||
make -C protoc-gen-go/testdata test
|
||||
|
||||
clean:
|
||||
go clean ./...
|
||||
|
||||
nuke:
|
||||
go clean -i ./...
|
||||
|
||||
regenerate:
|
||||
make -C protoc-gen-go/descriptor regenerate
|
||||
make -C protoc-gen-go/plugin regenerate
|
||||
make -C protoc-gen-go/testdata regenerate
|
||||
make -C proto/testdata regenerate
|
||||
make -C jsonpb/jsonpb_test_proto regenerate
|
||||
make -C _conformance regenerate
|
||||
-244
@@ -1,244 +0,0 @@
|
||||
# Go support for Protocol Buffers
|
||||
|
||||
[](https://travis-ci.org/golang/protobuf)
|
||||
[](https://godoc.org/github.com/golang/protobuf)
|
||||
|
||||
Google's data interchange format.
|
||||
Copyright 2010 The Go Authors.
|
||||
https://github.com/golang/protobuf
|
||||
|
||||
This package and the code it generates requires at least Go 1.4.
|
||||
|
||||
This software implements Go bindings for protocol buffers. For
|
||||
information about protocol buffers themselves, see
|
||||
https://developers.google.com/protocol-buffers/
|
||||
|
||||
## Installation ##
|
||||
|
||||
To use this software, you must:
|
||||
- Install the standard C++ implementation of protocol buffers from
|
||||
https://developers.google.com/protocol-buffers/
|
||||
- Of course, install the Go compiler and tools from
|
||||
https://golang.org/
|
||||
See
|
||||
https://golang.org/doc/install
|
||||
for details or, if you are using gccgo, follow the instructions at
|
||||
https://golang.org/doc/install/gccgo
|
||||
- Grab the code from the repository and install the proto package.
|
||||
The simplest way is to run `go get -u github.com/golang/protobuf/protoc-gen-go`.
|
||||
The compiler plugin, protoc-gen-go, will be installed in $GOBIN,
|
||||
defaulting to $GOPATH/bin. It must be in your $PATH for the protocol
|
||||
compiler, protoc, to find it.
|
||||
|
||||
This software has two parts: a 'protocol compiler plugin' that
|
||||
generates Go source files that, once compiled, can access and manage
|
||||
protocol buffers; and a library that implements run-time support for
|
||||
encoding (marshaling), decoding (unmarshaling), and accessing protocol
|
||||
buffers.
|
||||
|
||||
There is support for gRPC in Go using protocol buffers.
|
||||
See the note at the bottom of this file for details.
|
||||
|
||||
There are no insertion points in the plugin.
|
||||
|
||||
|
||||
## Using protocol buffers with Go ##
|
||||
|
||||
Once the software is installed, there are two steps to using it.
|
||||
First you must compile the protocol buffer definitions and then import
|
||||
them, with the support library, into your program.
|
||||
|
||||
To compile the protocol buffer definition, run protoc with the --go_out
|
||||
parameter set to the directory you want to output the Go code to.
|
||||
|
||||
protoc --go_out=. *.proto
|
||||
|
||||
The generated files will be suffixed .pb.go. See the Test code below
|
||||
for an example using such a file.
|
||||
|
||||
|
||||
The package comment for the proto library contains text describing
|
||||
the interface provided in Go for protocol buffers. Here is an edited
|
||||
version.
|
||||
|
||||
==========
|
||||
|
||||
The proto package converts data structures to and from the
|
||||
wire format of protocol buffers. It works in concert with the
|
||||
Go source code generated for .proto files by the protocol compiler.
|
||||
|
||||
A summary of the properties of the protocol buffer interface
|
||||
for a protocol buffer variable v:
|
||||
|
||||
- Names are turned from camel_case to CamelCase for export.
|
||||
- There are no methods on v to set fields; just treat
|
||||
them as structure fields.
|
||||
- There are getters that return a field's value if set,
|
||||
and return the field's default value if unset.
|
||||
The getters work even if the receiver is a nil message.
|
||||
- The zero value for a struct is its correct initialization state.
|
||||
All desired fields must be set before marshaling.
|
||||
- A Reset() method will restore a protobuf struct to its zero state.
|
||||
- Non-repeated fields are pointers to the values; nil means unset.
|
||||
That is, optional or required field int32 f becomes F *int32.
|
||||
- Repeated fields are slices.
|
||||
- Helper functions are available to aid the setting of fields.
|
||||
Helpers for getting values are superseded by the
|
||||
GetFoo methods and their use is deprecated.
|
||||
msg.Foo = proto.String("hello") // set field
|
||||
- Constants are defined to hold the default values of all fields that
|
||||
have them. They have the form Default_StructName_FieldName.
|
||||
Because the getter methods handle defaulted values,
|
||||
direct use of these constants should be rare.
|
||||
- Enums are given type names and maps from names to values.
|
||||
Enum values are prefixed with the enum's type name. Enum types have
|
||||
a String method, and a Enum method to assist in message construction.
|
||||
- Nested groups and enums have type names prefixed with the name of
|
||||
the surrounding message type.
|
||||
- Extensions are given descriptor names that start with E_,
|
||||
followed by an underscore-delimited list of the nested messages
|
||||
that contain it (if any) followed by the CamelCased name of the
|
||||
extension field itself. HasExtension, ClearExtension, GetExtension
|
||||
and SetExtension are functions for manipulating extensions.
|
||||
- Oneof field sets are given a single field in their message,
|
||||
with distinguished wrapper types for each possible field value.
|
||||
- Marshal and Unmarshal are functions to encode and decode the wire format.
|
||||
|
||||
When the .proto file specifies `syntax="proto3"`, there are some differences:
|
||||
|
||||
- Non-repeated fields of non-message type are values instead of pointers.
|
||||
- Enum types do not get an Enum method.
|
||||
|
||||
Consider file test.proto, containing
|
||||
|
||||
```proto
|
||||
syntax = "proto2";
|
||||
package example;
|
||||
|
||||
enum FOO { X = 17; };
|
||||
|
||||
message Test {
|
||||
required string label = 1;
|
||||
optional int32 type = 2 [default=77];
|
||||
repeated int64 reps = 3;
|
||||
optional group OptionalGroup = 4 {
|
||||
required string RequiredField = 5;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To create and play with a Test object from the example package,
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"path/to/example"
|
||||
)
|
||||
|
||||
func main() {
|
||||
test := &example.Test {
|
||||
Label: proto.String("hello"),
|
||||
Type: proto.Int32(17),
|
||||
Reps: []int64{1, 2, 3},
|
||||
Optionalgroup: &example.Test_OptionalGroup {
|
||||
RequiredField: proto.String("good bye"),
|
||||
},
|
||||
}
|
||||
data, err := proto.Marshal(test)
|
||||
if err != nil {
|
||||
log.Fatal("marshaling error: ", err)
|
||||
}
|
||||
newTest := &example.Test{}
|
||||
err = proto.Unmarshal(data, newTest)
|
||||
if err != nil {
|
||||
log.Fatal("unmarshaling error: ", err)
|
||||
}
|
||||
// Now test and newTest contain the same data.
|
||||
if test.GetLabel() != newTest.GetLabel() {
|
||||
log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel())
|
||||
}
|
||||
// etc.
|
||||
}
|
||||
```
|
||||
|
||||
## Parameters ##
|
||||
|
||||
To pass extra parameters to the plugin, use a comma-separated
|
||||
parameter list separated from the output directory by a colon:
|
||||
|
||||
|
||||
protoc --go_out=plugins=grpc,import_path=mypackage:. *.proto
|
||||
|
||||
|
||||
- `import_prefix=xxx` - a prefix that is added onto the beginning of
|
||||
all imports. Useful for things like generating protos in a
|
||||
subdirectory, or regenerating vendored protobufs in-place.
|
||||
- `import_path=foo/bar` - used as the package if no input files
|
||||
declare `go_package`. If it contains slashes, everything up to the
|
||||
rightmost slash is ignored.
|
||||
- `plugins=plugin1+plugin2` - specifies the list of sub-plugins to
|
||||
load. The only plugin in this repo is `grpc`.
|
||||
- `Mfoo/bar.proto=quux/shme` - declares that foo/bar.proto is
|
||||
associated with Go package quux/shme. This is subject to the
|
||||
import_prefix parameter.
|
||||
|
||||
## gRPC Support ##
|
||||
|
||||
If a proto file specifies RPC services, protoc-gen-go can be instructed to
|
||||
generate code compatible with gRPC (http://www.grpc.io/). To do this, pass
|
||||
the `plugins` parameter to protoc-gen-go; the usual way is to insert it into
|
||||
the --go_out argument to protoc:
|
||||
|
||||
protoc --go_out=plugins=grpc:. *.proto
|
||||
|
||||
## Compatibility ##
|
||||
|
||||
The library and the generated code are expected to be stable over time.
|
||||
However, we reserve the right to make breaking changes without notice for the
|
||||
following reasons:
|
||||
|
||||
- Security. A security issue in the specification or implementation may come to
|
||||
light whose resolution requires breaking compatibility. We reserve the right
|
||||
to address such security issues.
|
||||
- Unspecified behavior. There are some aspects of the Protocol Buffers
|
||||
specification that are undefined. Programs that depend on such unspecified
|
||||
behavior may break in future releases.
|
||||
- Specification errors or changes. If it becomes necessary to address an
|
||||
inconsistency, incompleteness, or change in the Protocol Buffers
|
||||
specification, resolving the issue could affect the meaning or legality of
|
||||
existing programs. We reserve the right to address such issues, including
|
||||
updating the implementations.
|
||||
- Bugs. If the library has a bug that violates the specification, a program
|
||||
that depends on the buggy behavior may break if the bug is fixed. We reserve
|
||||
the right to fix such bugs.
|
||||
- Adding methods or fields to generated structs. These may conflict with field
|
||||
names that already exist in a schema, causing applications to break. When the
|
||||
code generator encounters a field in the schema that would collide with a
|
||||
generated field or method name, the code generator will append an underscore
|
||||
to the generated field or method name.
|
||||
- Adding, removing, or changing methods or fields in generated structs that
|
||||
start with `XXX`. These parts of the generated code are exported out of
|
||||
necessity, but should not be considered part of the public API.
|
||||
- Adding, removing, or changing unexported symbols in generated code.
|
||||
|
||||
Any breaking changes outside of these will be announced 6 months in advance to
|
||||
protobuf@googlegroups.com.
|
||||
|
||||
You should, whenever possible, use generated code created by the `protoc-gen-go`
|
||||
tool built at the same commit as the `proto` package. The `proto` package
|
||||
declares package-level constants in the form `ProtoPackageIsVersionX`.
|
||||
Application code and generated code may depend on one of these constants to
|
||||
ensure that compilation will fail if the available version of the proto library
|
||||
is too old. Whenever we make a change to the generated code that requires newer
|
||||
library support, in the same commit we will increment the version number of the
|
||||
generated code and declare a new package-level constant whose name incorporates
|
||||
the latest version number. Removing a compatibility constant is considered a
|
||||
breaking change and would be subject to the announcement policy stated above.
|
||||
|
||||
The `protoc-gen-go/generator` package exposes a plugin interface,
|
||||
which is used by the gRPC code generation. This interface is not
|
||||
supported and is subject to incompatible changes without notice.
|
||||
-2278
File diff suppressed because it is too large
Load Diff
-300
@@ -1,300 +0,0 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
pb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
testpb "github.com/golang/protobuf/proto/testdata"
|
||||
anypb "github.com/golang/protobuf/ptypes/any"
|
||||
)
|
||||
|
||||
var (
|
||||
expandedMarshaler = proto.TextMarshaler{ExpandAny: true}
|
||||
expandedCompactMarshaler = proto.TextMarshaler{Compact: true, ExpandAny: true}
|
||||
)
|
||||
|
||||
// anyEqual reports whether two messages which may be google.protobuf.Any or may
|
||||
// contain google.protobuf.Any fields are equal. We can't use proto.Equal for
|
||||
// comparison, because semantically equivalent messages may be marshaled to
|
||||
// binary in different tag order. Instead, trust that TextMarshaler with
|
||||
// ExpandAny option works and compare the text marshaling results.
|
||||
func anyEqual(got, want proto.Message) bool {
|
||||
// if messages are proto.Equal, no need to marshal.
|
||||
if proto.Equal(got, want) {
|
||||
return true
|
||||
}
|
||||
g := expandedMarshaler.Text(got)
|
||||
w := expandedMarshaler.Text(want)
|
||||
return g == w
|
||||
}
|
||||
|
||||
type golden struct {
|
||||
m proto.Message
|
||||
t, c string
|
||||
}
|
||||
|
||||
var goldenMessages = makeGolden()
|
||||
|
||||
func makeGolden() []golden {
|
||||
nested := &pb.Nested{Bunny: "Monty"}
|
||||
nb, err := proto.Marshal(nested)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
m1 := &pb.Message{
|
||||
Name: "David",
|
||||
ResultCount: 47,
|
||||
Anything: &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb},
|
||||
}
|
||||
m2 := &pb.Message{
|
||||
Name: "David",
|
||||
ResultCount: 47,
|
||||
Anything: &anypb.Any{TypeUrl: "http://[::1]/type.googleapis.com/" + proto.MessageName(nested), Value: nb},
|
||||
}
|
||||
m3 := &pb.Message{
|
||||
Name: "David",
|
||||
ResultCount: 47,
|
||||
Anything: &anypb.Any{TypeUrl: `type.googleapis.com/"/` + proto.MessageName(nested), Value: nb},
|
||||
}
|
||||
m4 := &pb.Message{
|
||||
Name: "David",
|
||||
ResultCount: 47,
|
||||
Anything: &anypb.Any{TypeUrl: "type.googleapis.com/a/path/" + proto.MessageName(nested), Value: nb},
|
||||
}
|
||||
m5 := &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(nested), Value: nb}
|
||||
|
||||
any1 := &testpb.MyMessage{Count: proto.Int32(47), Name: proto.String("David")}
|
||||
proto.SetExtension(any1, testpb.E_Ext_More, &testpb.Ext{Data: proto.String("foo")})
|
||||
proto.SetExtension(any1, testpb.E_Ext_Text, proto.String("bar"))
|
||||
any1b, err := proto.Marshal(any1)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
any2 := &testpb.MyMessage{Count: proto.Int32(42), Bikeshed: testpb.MyMessage_GREEN.Enum(), RepBytes: [][]byte{[]byte("roboto")}}
|
||||
proto.SetExtension(any2, testpb.E_Ext_More, &testpb.Ext{Data: proto.String("baz")})
|
||||
any2b, err := proto.Marshal(any2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
m6 := &pb.Message{
|
||||
Name: "David",
|
||||
ResultCount: 47,
|
||||
Anything: &anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b},
|
||||
ManyThings: []*anypb.Any{
|
||||
&anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any2), Value: any2b},
|
||||
&anypb.Any{TypeUrl: "type.googleapis.com/" + proto.MessageName(any1), Value: any1b},
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
m1Golden = `
|
||||
name: "David"
|
||||
result_count: 47
|
||||
anything: <
|
||||
[type.googleapis.com/proto3_proto.Nested]: <
|
||||
bunny: "Monty"
|
||||
>
|
||||
>
|
||||
`
|
||||
m2Golden = `
|
||||
name: "David"
|
||||
result_count: 47
|
||||
anything: <
|
||||
["http://[::1]/type.googleapis.com/proto3_proto.Nested"]: <
|
||||
bunny: "Monty"
|
||||
>
|
||||
>
|
||||
`
|
||||
m3Golden = `
|
||||
name: "David"
|
||||
result_count: 47
|
||||
anything: <
|
||||
["type.googleapis.com/\"/proto3_proto.Nested"]: <
|
||||
bunny: "Monty"
|
||||
>
|
||||
>
|
||||
`
|
||||
m4Golden = `
|
||||
name: "David"
|
||||
result_count: 47
|
||||
anything: <
|
||||
[type.googleapis.com/a/path/proto3_proto.Nested]: <
|
||||
bunny: "Monty"
|
||||
>
|
||||
>
|
||||
`
|
||||
m5Golden = `
|
||||
[type.googleapis.com/proto3_proto.Nested]: <
|
||||
bunny: "Monty"
|
||||
>
|
||||
`
|
||||
m6Golden = `
|
||||
name: "David"
|
||||
result_count: 47
|
||||
anything: <
|
||||
[type.googleapis.com/testdata.MyMessage]: <
|
||||
count: 47
|
||||
name: "David"
|
||||
[testdata.Ext.more]: <
|
||||
data: "foo"
|
||||
>
|
||||
[testdata.Ext.text]: "bar"
|
||||
>
|
||||
>
|
||||
many_things: <
|
||||
[type.googleapis.com/testdata.MyMessage]: <
|
||||
count: 42
|
||||
bikeshed: GREEN
|
||||
rep_bytes: "roboto"
|
||||
[testdata.Ext.more]: <
|
||||
data: "baz"
|
||||
>
|
||||
>
|
||||
>
|
||||
many_things: <
|
||||
[type.googleapis.com/testdata.MyMessage]: <
|
||||
count: 47
|
||||
name: "David"
|
||||
[testdata.Ext.more]: <
|
||||
data: "foo"
|
||||
>
|
||||
[testdata.Ext.text]: "bar"
|
||||
>
|
||||
>
|
||||
`
|
||||
)
|
||||
return []golden{
|
||||
{m1, strings.TrimSpace(m1Golden) + "\n", strings.TrimSpace(compact(m1Golden)) + " "},
|
||||
{m2, strings.TrimSpace(m2Golden) + "\n", strings.TrimSpace(compact(m2Golden)) + " "},
|
||||
{m3, strings.TrimSpace(m3Golden) + "\n", strings.TrimSpace(compact(m3Golden)) + " "},
|
||||
{m4, strings.TrimSpace(m4Golden) + "\n", strings.TrimSpace(compact(m4Golden)) + " "},
|
||||
{m5, strings.TrimSpace(m5Golden) + "\n", strings.TrimSpace(compact(m5Golden)) + " "},
|
||||
{m6, strings.TrimSpace(m6Golden) + "\n", strings.TrimSpace(compact(m6Golden)) + " "},
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalGolden(t *testing.T) {
|
||||
for _, tt := range goldenMessages {
|
||||
if got, want := expandedMarshaler.Text(tt.m), tt.t; got != want {
|
||||
t.Errorf("message %v: got:\n%s\nwant:\n%s", tt.m, got, want)
|
||||
}
|
||||
if got, want := expandedCompactMarshaler.Text(tt.m), tt.c; got != want {
|
||||
t.Errorf("message %v: got:\n`%s`\nwant:\n`%s`", tt.m, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalGolden(t *testing.T) {
|
||||
for _, tt := range goldenMessages {
|
||||
want := tt.m
|
||||
got := proto.Clone(tt.m)
|
||||
got.Reset()
|
||||
if err := proto.UnmarshalText(tt.t, got); err != nil {
|
||||
t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.t, err)
|
||||
}
|
||||
if !anyEqual(got, want) {
|
||||
t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.t, got, want)
|
||||
}
|
||||
got.Reset()
|
||||
if err := proto.UnmarshalText(tt.c, got); err != nil {
|
||||
t.Errorf("failed to unmarshal\n%s\nerror: %v", tt.c, err)
|
||||
}
|
||||
if !anyEqual(got, want) {
|
||||
t.Errorf("message:\n%s\ngot:\n%s\nwant:\n%s", tt.c, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalUnknownAny(t *testing.T) {
|
||||
m := &pb.Message{
|
||||
Anything: &anypb.Any{
|
||||
TypeUrl: "foo",
|
||||
Value: []byte("bar"),
|
||||
},
|
||||
}
|
||||
want := `anything: <
|
||||
type_url: "foo"
|
||||
value: "bar"
|
||||
>
|
||||
`
|
||||
got := expandedMarshaler.Text(m)
|
||||
if got != want {
|
||||
t.Errorf("got\n`%s`\nwant\n`%s`", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAmbiguousAny(t *testing.T) {
|
||||
pb := &anypb.Any{}
|
||||
err := proto.UnmarshalText(`
|
||||
type_url: "ttt/proto3_proto.Nested"
|
||||
value: "\n\x05Monty"
|
||||
`, pb)
|
||||
t.Logf("result: %v (error: %v)", expandedMarshaler.Text(pb), err)
|
||||
if err != nil {
|
||||
t.Errorf("failed to parse ambiguous Any message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalOverwriteAny(t *testing.T) {
|
||||
pb := &anypb.Any{}
|
||||
err := proto.UnmarshalText(`
|
||||
[type.googleapis.com/a/path/proto3_proto.Nested]: <
|
||||
bunny: "Monty"
|
||||
>
|
||||
[type.googleapis.com/a/path/proto3_proto.Nested]: <
|
||||
bunny: "Rabbit of Caerbannog"
|
||||
>
|
||||
`, pb)
|
||||
want := `line 7: Any message unpacked multiple times, or "type_url" already set`
|
||||
if err.Error() != want {
|
||||
t.Errorf("incorrect error.\nHave: %v\nWant: %v", err.Error(), want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalAnyMixAndMatch(t *testing.T) {
|
||||
pb := &anypb.Any{}
|
||||
err := proto.UnmarshalText(`
|
||||
value: "\n\x05Monty"
|
||||
[type.googleapis.com/a/path/proto3_proto.Nested]: <
|
||||
bunny: "Rabbit of Caerbannog"
|
||||
>
|
||||
`, pb)
|
||||
want := `line 5: Any message unpacked multiple times, or "value" already set`
|
||||
if err.Error() != want {
|
||||
t.Errorf("incorrect error.\nHave: %v\nWant: %v", err.Error(), want)
|
||||
}
|
||||
}
|
||||
-300
@@ -1,300 +0,0 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
proto3pb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
pb "github.com/golang/protobuf/proto/testdata"
|
||||
)
|
||||
|
||||
var cloneTestMessage = &pb.MyMessage{
|
||||
Count: proto.Int32(42),
|
||||
Name: proto.String("Dave"),
|
||||
Pet: []string{"bunny", "kitty", "horsey"},
|
||||
Inner: &pb.InnerMessage{
|
||||
Host: proto.String("niles"),
|
||||
Port: proto.Int32(9099),
|
||||
Connected: proto.Bool(true),
|
||||
},
|
||||
Others: []*pb.OtherMessage{
|
||||
{
|
||||
Value: []byte("some bytes"),
|
||||
},
|
||||
},
|
||||
Somegroup: &pb.MyMessage_SomeGroup{
|
||||
GroupField: proto.Int32(6),
|
||||
},
|
||||
RepBytes: [][]byte{[]byte("sham"), []byte("wow")},
|
||||
}
|
||||
|
||||
func init() {
|
||||
ext := &pb.Ext{
|
||||
Data: proto.String("extension"),
|
||||
}
|
||||
if err := proto.SetExtension(cloneTestMessage, pb.E_Ext_More, ext); err != nil {
|
||||
panic("SetExtension: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClone(t *testing.T) {
|
||||
m := proto.Clone(cloneTestMessage).(*pb.MyMessage)
|
||||
if !proto.Equal(m, cloneTestMessage) {
|
||||
t.Errorf("Clone(%v) = %v", cloneTestMessage, m)
|
||||
}
|
||||
|
||||
// Verify it was a deep copy.
|
||||
*m.Inner.Port++
|
||||
if proto.Equal(m, cloneTestMessage) {
|
||||
t.Error("Mutating clone changed the original")
|
||||
}
|
||||
// Byte fields and repeated fields should be copied.
|
||||
if &m.Pet[0] == &cloneTestMessage.Pet[0] {
|
||||
t.Error("Pet: repeated field not copied")
|
||||
}
|
||||
if &m.Others[0] == &cloneTestMessage.Others[0] {
|
||||
t.Error("Others: repeated field not copied")
|
||||
}
|
||||
if &m.Others[0].Value[0] == &cloneTestMessage.Others[0].Value[0] {
|
||||
t.Error("Others[0].Value: bytes field not copied")
|
||||
}
|
||||
if &m.RepBytes[0] == &cloneTestMessage.RepBytes[0] {
|
||||
t.Error("RepBytes: repeated field not copied")
|
||||
}
|
||||
if &m.RepBytes[0][0] == &cloneTestMessage.RepBytes[0][0] {
|
||||
t.Error("RepBytes[0]: bytes field not copied")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloneNil(t *testing.T) {
|
||||
var m *pb.MyMessage
|
||||
if c := proto.Clone(m); !proto.Equal(m, c) {
|
||||
t.Errorf("Clone(%v) = %v", m, c)
|
||||
}
|
||||
}
|
||||
|
||||
var mergeTests = []struct {
|
||||
src, dst, want proto.Message
|
||||
}{
|
||||
{
|
||||
src: &pb.MyMessage{
|
||||
Count: proto.Int32(42),
|
||||
},
|
||||
dst: &pb.MyMessage{
|
||||
Name: proto.String("Dave"),
|
||||
},
|
||||
want: &pb.MyMessage{
|
||||
Count: proto.Int32(42),
|
||||
Name: proto.String("Dave"),
|
||||
},
|
||||
},
|
||||
{
|
||||
src: &pb.MyMessage{
|
||||
Inner: &pb.InnerMessage{
|
||||
Host: proto.String("hey"),
|
||||
Connected: proto.Bool(true),
|
||||
},
|
||||
Pet: []string{"horsey"},
|
||||
Others: []*pb.OtherMessage{
|
||||
{
|
||||
Value: []byte("some bytes"),
|
||||
},
|
||||
},
|
||||
},
|
||||
dst: &pb.MyMessage{
|
||||
Inner: &pb.InnerMessage{
|
||||
Host: proto.String("niles"),
|
||||
Port: proto.Int32(9099),
|
||||
},
|
||||
Pet: []string{"bunny", "kitty"},
|
||||
Others: []*pb.OtherMessage{
|
||||
{
|
||||
Key: proto.Int64(31415926535),
|
||||
},
|
||||
{
|
||||
// Explicitly test a src=nil field
|
||||
Inner: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &pb.MyMessage{
|
||||
Inner: &pb.InnerMessage{
|
||||
Host: proto.String("hey"),
|
||||
Connected: proto.Bool(true),
|
||||
Port: proto.Int32(9099),
|
||||
},
|
||||
Pet: []string{"bunny", "kitty", "horsey"},
|
||||
Others: []*pb.OtherMessage{
|
||||
{
|
||||
Key: proto.Int64(31415926535),
|
||||
},
|
||||
{},
|
||||
{
|
||||
Value: []byte("some bytes"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
src: &pb.MyMessage{
|
||||
RepBytes: [][]byte{[]byte("wow")},
|
||||
},
|
||||
dst: &pb.MyMessage{
|
||||
Somegroup: &pb.MyMessage_SomeGroup{
|
||||
GroupField: proto.Int32(6),
|
||||
},
|
||||
RepBytes: [][]byte{[]byte("sham")},
|
||||
},
|
||||
want: &pb.MyMessage{
|
||||
Somegroup: &pb.MyMessage_SomeGroup{
|
||||
GroupField: proto.Int32(6),
|
||||
},
|
||||
RepBytes: [][]byte{[]byte("sham"), []byte("wow")},
|
||||
},
|
||||
},
|
||||
// Check that a scalar bytes field replaces rather than appends.
|
||||
{
|
||||
src: &pb.OtherMessage{Value: []byte("foo")},
|
||||
dst: &pb.OtherMessage{Value: []byte("bar")},
|
||||
want: &pb.OtherMessage{Value: []byte("foo")},
|
||||
},
|
||||
{
|
||||
src: &pb.MessageWithMap{
|
||||
NameMapping: map[int32]string{6: "Nigel"},
|
||||
MsgMapping: map[int64]*pb.FloatingPoint{
|
||||
0x4001: &pb.FloatingPoint{F: proto.Float64(2.0)},
|
||||
0x4002: &pb.FloatingPoint{
|
||||
F: proto.Float64(2.0),
|
||||
},
|
||||
},
|
||||
ByteMapping: map[bool][]byte{true: []byte("wowsa")},
|
||||
},
|
||||
dst: &pb.MessageWithMap{
|
||||
NameMapping: map[int32]string{
|
||||
6: "Bruce", // should be overwritten
|
||||
7: "Andrew",
|
||||
},
|
||||
MsgMapping: map[int64]*pb.FloatingPoint{
|
||||
0x4002: &pb.FloatingPoint{
|
||||
F: proto.Float64(3.0),
|
||||
Exact: proto.Bool(true),
|
||||
}, // the entire message should be overwritten
|
||||
},
|
||||
},
|
||||
want: &pb.MessageWithMap{
|
||||
NameMapping: map[int32]string{
|
||||
6: "Nigel",
|
||||
7: "Andrew",
|
||||
},
|
||||
MsgMapping: map[int64]*pb.FloatingPoint{
|
||||
0x4001: &pb.FloatingPoint{F: proto.Float64(2.0)},
|
||||
0x4002: &pb.FloatingPoint{
|
||||
F: proto.Float64(2.0),
|
||||
},
|
||||
},
|
||||
ByteMapping: map[bool][]byte{true: []byte("wowsa")},
|
||||
},
|
||||
},
|
||||
// proto3 shouldn't merge zero values,
|
||||
// in the same way that proto2 shouldn't merge nils.
|
||||
{
|
||||
src: &proto3pb.Message{
|
||||
Name: "Aaron",
|
||||
Data: []byte(""), // zero value, but not nil
|
||||
},
|
||||
dst: &proto3pb.Message{
|
||||
HeightInCm: 176,
|
||||
Data: []byte("texas!"),
|
||||
},
|
||||
want: &proto3pb.Message{
|
||||
Name: "Aaron",
|
||||
HeightInCm: 176,
|
||||
Data: []byte("texas!"),
|
||||
},
|
||||
},
|
||||
// Oneof fields should merge by assignment.
|
||||
{
|
||||
src: &pb.Communique{
|
||||
Union: &pb.Communique_Number{41},
|
||||
},
|
||||
dst: &pb.Communique{
|
||||
Union: &pb.Communique_Name{"Bobby Tables"},
|
||||
},
|
||||
want: &pb.Communique{
|
||||
Union: &pb.Communique_Number{41},
|
||||
},
|
||||
},
|
||||
// Oneof nil is the same as not set.
|
||||
{
|
||||
src: &pb.Communique{},
|
||||
dst: &pb.Communique{
|
||||
Union: &pb.Communique_Name{"Bobby Tables"},
|
||||
},
|
||||
want: &pb.Communique{
|
||||
Union: &pb.Communique_Name{"Bobby Tables"},
|
||||
},
|
||||
},
|
||||
{
|
||||
src: &proto3pb.Message{
|
||||
Terrain: map[string]*proto3pb.Nested{
|
||||
"kay_a": &proto3pb.Nested{Cute: true}, // replace
|
||||
"kay_b": &proto3pb.Nested{Bunny: "rabbit"}, // insert
|
||||
},
|
||||
},
|
||||
dst: &proto3pb.Message{
|
||||
Terrain: map[string]*proto3pb.Nested{
|
||||
"kay_a": &proto3pb.Nested{Bunny: "lost"}, // replaced
|
||||
"kay_c": &proto3pb.Nested{Bunny: "bunny"}, // keep
|
||||
},
|
||||
},
|
||||
want: &proto3pb.Message{
|
||||
Terrain: map[string]*proto3pb.Nested{
|
||||
"kay_a": &proto3pb.Nested{Cute: true},
|
||||
"kay_b": &proto3pb.Nested{Bunny: "rabbit"},
|
||||
"kay_c": &proto3pb.Nested{Bunny: "bunny"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
for _, m := range mergeTests {
|
||||
got := proto.Clone(m.dst)
|
||||
proto.Merge(got, m.src)
|
||||
if !proto.Equal(got, m.want) {
|
||||
t.Errorf("Merge(%v, %v)\n got %v\nwant %v\n", m.dst, m.src, got, m.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
-258
@@ -1,258 +0,0 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
tpb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
)
|
||||
|
||||
var (
|
||||
bytesBlackhole []byte
|
||||
msgBlackhole = new(tpb.Message)
|
||||
)
|
||||
|
||||
// BenchmarkVarint32ArraySmall shows the performance on an array of small int32 fields (1 and
|
||||
// 2 bytes long).
|
||||
func BenchmarkVarint32ArraySmall(b *testing.B) {
|
||||
for i := uint(1); i <= 10; i++ {
|
||||
dist := genInt32Dist([7]int{0, 3, 1}, 1<<i)
|
||||
raw, err := proto.Marshal(&tpb.Message{
|
||||
ShortKey: dist,
|
||||
})
|
||||
if err != nil {
|
||||
b.Error("wrong encode", err)
|
||||
}
|
||||
b.Run(fmt.Sprintf("Len%v", len(dist)), func(b *testing.B) {
|
||||
scratchBuf := proto.NewBuffer(nil)
|
||||
b.ResetTimer()
|
||||
for k := 0; k < b.N; k++ {
|
||||
scratchBuf.SetBuf(raw)
|
||||
msgBlackhole.Reset()
|
||||
if err := scratchBuf.Unmarshal(msgBlackhole); err != nil {
|
||||
b.Error("wrong decode", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkVarint32ArrayLarge shows the performance on an array of large int32 fields (3 and
|
||||
// 4 bytes long, with a small number of 1, 2, 5 and 10 byte long versions).
|
||||
func BenchmarkVarint32ArrayLarge(b *testing.B) {
|
||||
for i := uint(1); i <= 10; i++ {
|
||||
dist := genInt32Dist([7]int{0, 1, 2, 4, 8, 1, 1}, 1<<i)
|
||||
raw, err := proto.Marshal(&tpb.Message{
|
||||
ShortKey: dist,
|
||||
})
|
||||
if err != nil {
|
||||
b.Error("wrong encode", err)
|
||||
}
|
||||
b.Run(fmt.Sprintf("Len%v", len(dist)), func(b *testing.B) {
|
||||
scratchBuf := proto.NewBuffer(nil)
|
||||
b.ResetTimer()
|
||||
for k := 0; k < b.N; k++ {
|
||||
scratchBuf.SetBuf(raw)
|
||||
msgBlackhole.Reset()
|
||||
if err := scratchBuf.Unmarshal(msgBlackhole); err != nil {
|
||||
b.Error("wrong decode", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkVarint64ArraySmall shows the performance on an array of small int64 fields (1 and
|
||||
// 2 bytes long).
|
||||
func BenchmarkVarint64ArraySmall(b *testing.B) {
|
||||
for i := uint(1); i <= 10; i++ {
|
||||
dist := genUint64Dist([11]int{0, 3, 1}, 1<<i)
|
||||
raw, err := proto.Marshal(&tpb.Message{
|
||||
Key: dist,
|
||||
})
|
||||
if err != nil {
|
||||
b.Error("wrong encode", err)
|
||||
}
|
||||
b.Run(fmt.Sprintf("Len%v", len(dist)), func(b *testing.B) {
|
||||
scratchBuf := proto.NewBuffer(nil)
|
||||
b.ResetTimer()
|
||||
for k := 0; k < b.N; k++ {
|
||||
scratchBuf.SetBuf(raw)
|
||||
msgBlackhole.Reset()
|
||||
if err := scratchBuf.Unmarshal(msgBlackhole); err != nil {
|
||||
b.Error("wrong decode", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkVarint64ArrayLarge shows the performance on an array of large int64 fields (6, 7,
|
||||
// and 8 bytes long with a small number of the other sizes).
|
||||
func BenchmarkVarint64ArrayLarge(b *testing.B) {
|
||||
for i := uint(1); i <= 10; i++ {
|
||||
dist := genUint64Dist([11]int{0, 1, 1, 2, 4, 8, 16, 32, 16, 1, 1}, 1<<i)
|
||||
raw, err := proto.Marshal(&tpb.Message{
|
||||
Key: dist,
|
||||
})
|
||||
if err != nil {
|
||||
b.Error("wrong encode", err)
|
||||
}
|
||||
b.Run(fmt.Sprintf("Len%v", len(dist)), func(b *testing.B) {
|
||||
scratchBuf := proto.NewBuffer(nil)
|
||||
b.ResetTimer()
|
||||
for k := 0; k < b.N; k++ {
|
||||
scratchBuf.SetBuf(raw)
|
||||
msgBlackhole.Reset()
|
||||
if err := scratchBuf.Unmarshal(msgBlackhole); err != nil {
|
||||
b.Error("wrong decode", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkVarint64ArrayMixed shows the performance of lots of small messages, each
|
||||
// containing a small number of large (3, 4, and 5 byte) repeated int64s.
|
||||
func BenchmarkVarint64ArrayMixed(b *testing.B) {
|
||||
for i := uint(1); i <= 1<<5; i <<= 1 {
|
||||
dist := genUint64Dist([11]int{0, 0, 0, 4, 6, 4, 0, 0, 0, 0, 0}, int(i))
|
||||
// number of sub fields
|
||||
for k := uint(1); k <= 1<<10; k <<= 2 {
|
||||
msg := &tpb.Message{}
|
||||
for m := uint(0); m < k; m++ {
|
||||
msg.Children = append(msg.Children, &tpb.Message{
|
||||
Key: dist,
|
||||
})
|
||||
}
|
||||
raw, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
b.Error("wrong encode", err)
|
||||
}
|
||||
b.Run(fmt.Sprintf("Fields%vLen%v", k, i), func(b *testing.B) {
|
||||
scratchBuf := proto.NewBuffer(nil)
|
||||
b.ResetTimer()
|
||||
for k := 0; k < b.N; k++ {
|
||||
scratchBuf.SetBuf(raw)
|
||||
msgBlackhole.Reset()
|
||||
if err := scratchBuf.Unmarshal(msgBlackhole); err != nil {
|
||||
b.Error("wrong decode", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// genInt32Dist generates a slice of ints that will match the size distribution of dist.
|
||||
// A size of 6 corresponds to a max length varint32, which is 10 bytes. The distribution
|
||||
// is 1-indexed. (i.e. the value at index 1 is how many 1 byte ints to create).
|
||||
func genInt32Dist(dist [7]int, count int) (dest []int32) {
|
||||
for i := 0; i < count; i++ {
|
||||
for k := 0; k < len(dist); k++ {
|
||||
var num int32
|
||||
switch k {
|
||||
case 1:
|
||||
num = 1<<7 - 1
|
||||
case 2:
|
||||
num = 1<<14 - 1
|
||||
case 3:
|
||||
num = 1<<21 - 1
|
||||
case 4:
|
||||
num = 1<<28 - 1
|
||||
case 5:
|
||||
num = 1<<29 - 1
|
||||
case 6:
|
||||
num = -1
|
||||
}
|
||||
for m := 0; m < dist[k]; m++ {
|
||||
dest = append(dest, num)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// genUint64Dist generates a slice of ints that will match the size distribution of dist.
|
||||
// The distribution is 1-indexed. (i.e. the value at index 1 is how many 1 byte ints to create).
|
||||
func genUint64Dist(dist [11]int, count int) (dest []uint64) {
|
||||
for i := 0; i < count; i++ {
|
||||
for k := 0; k < len(dist); k++ {
|
||||
var num uint64
|
||||
switch k {
|
||||
case 1:
|
||||
num = 1<<7 - 1
|
||||
case 2:
|
||||
num = 1<<14 - 1
|
||||
case 3:
|
||||
num = 1<<21 - 1
|
||||
case 4:
|
||||
num = 1<<28 - 1
|
||||
case 5:
|
||||
num = 1<<35 - 1
|
||||
case 6:
|
||||
num = 1<<42 - 1
|
||||
case 7:
|
||||
num = 1<<49 - 1
|
||||
case 8:
|
||||
num = 1<<56 - 1
|
||||
case 9:
|
||||
num = 1<<63 - 1
|
||||
case 10:
|
||||
num = 1<<64 - 1
|
||||
}
|
||||
for m := 0; m < dist[k]; m++ {
|
||||
dest = append(dest, num)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// BenchmarkDecodeEmpty measures the overhead of doing the minimal possible decode.
|
||||
func BenchmarkDecodeEmpty(b *testing.B) {
|
||||
raw, err := proto.Marshal(&tpb.Message{})
|
||||
if err != nil {
|
||||
b.Error("wrong encode", err)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := proto.Unmarshal(raw, msgBlackhole); err != nil {
|
||||
b.Error("wrong decode", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
-85
@@ -1,85 +0,0 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
tpb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
)
|
||||
|
||||
var (
|
||||
blackhole []byte
|
||||
)
|
||||
|
||||
// BenchmarkAny creates increasingly large arbitrary Any messages. The type is always the
|
||||
// same.
|
||||
func BenchmarkAny(b *testing.B) {
|
||||
data := make([]byte, 1<<20)
|
||||
quantum := 1 << 10
|
||||
for i := uint(0); i <= 10; i++ {
|
||||
b.Run(strconv.Itoa(quantum<<i), func(b *testing.B) {
|
||||
for k := 0; k < b.N; k++ {
|
||||
inner := &tpb.Message{
|
||||
Data: data[:quantum<<i],
|
||||
}
|
||||
outer, err := ptypes.MarshalAny(inner)
|
||||
if err != nil {
|
||||
b.Error("wrong encode", err)
|
||||
}
|
||||
raw, err := proto.Marshal(&tpb.Message{
|
||||
Anything: outer,
|
||||
})
|
||||
if err != nil {
|
||||
b.Error("wrong encode", err)
|
||||
}
|
||||
blackhole = raw
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkEmpy measures the overhead of doing the minimal possible encode.
|
||||
func BenchmarkEmpy(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
raw, err := proto.Marshal(&tpb.Message{})
|
||||
if err != nil {
|
||||
b.Error("wrong encode", err)
|
||||
}
|
||||
blackhole = raw
|
||||
}
|
||||
}
|
||||
-224
@@ -1,224 +0,0 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/golang/protobuf/proto"
|
||||
proto3pb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
pb "github.com/golang/protobuf/proto/testdata"
|
||||
)
|
||||
|
||||
// Four identical base messages.
|
||||
// The init function adds extensions to some of them.
|
||||
var messageWithoutExtension = &pb.MyMessage{Count: Int32(7)}
|
||||
var messageWithExtension1a = &pb.MyMessage{Count: Int32(7)}
|
||||
var messageWithExtension1b = &pb.MyMessage{Count: Int32(7)}
|
||||
var messageWithExtension2 = &pb.MyMessage{Count: Int32(7)}
|
||||
|
||||
// Two messages with non-message extensions.
|
||||
var messageWithInt32Extension1 = &pb.MyMessage{Count: Int32(8)}
|
||||
var messageWithInt32Extension2 = &pb.MyMessage{Count: Int32(8)}
|
||||
|
||||
func init() {
|
||||
ext1 := &pb.Ext{Data: String("Kirk")}
|
||||
ext2 := &pb.Ext{Data: String("Picard")}
|
||||
|
||||
// messageWithExtension1a has ext1, but never marshals it.
|
||||
if err := SetExtension(messageWithExtension1a, pb.E_Ext_More, ext1); err != nil {
|
||||
panic("SetExtension on 1a failed: " + err.Error())
|
||||
}
|
||||
|
||||
// messageWithExtension1b is the unmarshaled form of messageWithExtension1a.
|
||||
if err := SetExtension(messageWithExtension1b, pb.E_Ext_More, ext1); err != nil {
|
||||
panic("SetExtension on 1b failed: " + err.Error())
|
||||
}
|
||||
buf, err := Marshal(messageWithExtension1b)
|
||||
if err != nil {
|
||||
panic("Marshal of 1b failed: " + err.Error())
|
||||
}
|
||||
messageWithExtension1b.Reset()
|
||||
if err := Unmarshal(buf, messageWithExtension1b); err != nil {
|
||||
panic("Unmarshal of 1b failed: " + err.Error())
|
||||
}
|
||||
|
||||
// messageWithExtension2 has ext2.
|
||||
if err := SetExtension(messageWithExtension2, pb.E_Ext_More, ext2); err != nil {
|
||||
panic("SetExtension on 2 failed: " + err.Error())
|
||||
}
|
||||
|
||||
if err := SetExtension(messageWithInt32Extension1, pb.E_Ext_Number, Int32(23)); err != nil {
|
||||
panic("SetExtension on Int32-1 failed: " + err.Error())
|
||||
}
|
||||
if err := SetExtension(messageWithInt32Extension1, pb.E_Ext_Number, Int32(24)); err != nil {
|
||||
panic("SetExtension on Int32-2 failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
var EqualTests = []struct {
|
||||
desc string
|
||||
a, b Message
|
||||
exp bool
|
||||
}{
|
||||
{"different types", &pb.GoEnum{}, &pb.GoTestField{}, false},
|
||||
{"equal empty", &pb.GoEnum{}, &pb.GoEnum{}, true},
|
||||
{"nil vs nil", nil, nil, true},
|
||||
{"typed nil vs typed nil", (*pb.GoEnum)(nil), (*pb.GoEnum)(nil), true},
|
||||
{"typed nil vs empty", (*pb.GoEnum)(nil), &pb.GoEnum{}, false},
|
||||
{"different typed nil", (*pb.GoEnum)(nil), (*pb.GoTestField)(nil), false},
|
||||
|
||||
{"one set field, one unset field", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{}, false},
|
||||
{"one set field zero, one unset field", &pb.GoTest{Param: Int32(0)}, &pb.GoTest{}, false},
|
||||
{"different set fields", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{Label: String("bar")}, false},
|
||||
{"equal set", &pb.GoTestField{Label: String("foo")}, &pb.GoTestField{Label: String("foo")}, true},
|
||||
|
||||
{"repeated, one set", &pb.GoTest{F_Int32Repeated: []int32{2, 3}}, &pb.GoTest{}, false},
|
||||
{"repeated, different length", &pb.GoTest{F_Int32Repeated: []int32{2, 3}}, &pb.GoTest{F_Int32Repeated: []int32{2}}, false},
|
||||
{"repeated, different value", &pb.GoTest{F_Int32Repeated: []int32{2}}, &pb.GoTest{F_Int32Repeated: []int32{3}}, false},
|
||||
{"repeated, equal", &pb.GoTest{F_Int32Repeated: []int32{2, 4}}, &pb.GoTest{F_Int32Repeated: []int32{2, 4}}, true},
|
||||
{"repeated, nil equal nil", &pb.GoTest{F_Int32Repeated: nil}, &pb.GoTest{F_Int32Repeated: nil}, true},
|
||||
{"repeated, nil equal empty", &pb.GoTest{F_Int32Repeated: nil}, &pb.GoTest{F_Int32Repeated: []int32{}}, true},
|
||||
{"repeated, empty equal nil", &pb.GoTest{F_Int32Repeated: []int32{}}, &pb.GoTest{F_Int32Repeated: nil}, true},
|
||||
|
||||
{
|
||||
"nested, different",
|
||||
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("foo")}},
|
||||
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("bar")}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"nested, equal",
|
||||
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("wow")}},
|
||||
&pb.GoTest{RequiredField: &pb.GoTestField{Label: String("wow")}},
|
||||
true,
|
||||
},
|
||||
|
||||
{"bytes", &pb.OtherMessage{Value: []byte("foo")}, &pb.OtherMessage{Value: []byte("foo")}, true},
|
||||
{"bytes, empty", &pb.OtherMessage{Value: []byte{}}, &pb.OtherMessage{Value: []byte{}}, true},
|
||||
{"bytes, empty vs nil", &pb.OtherMessage{Value: []byte{}}, &pb.OtherMessage{Value: nil}, false},
|
||||
{
|
||||
"repeated bytes",
|
||||
&pb.MyMessage{RepBytes: [][]byte{[]byte("sham"), []byte("wow")}},
|
||||
&pb.MyMessage{RepBytes: [][]byte{[]byte("sham"), []byte("wow")}},
|
||||
true,
|
||||
},
|
||||
// In proto3, []byte{} and []byte(nil) are equal.
|
||||
{"proto3 bytes, empty vs nil", &proto3pb.Message{Data: []byte{}}, &proto3pb.Message{Data: nil}, true},
|
||||
|
||||
{"extension vs. no extension", messageWithoutExtension, messageWithExtension1a, false},
|
||||
{"extension vs. same extension", messageWithExtension1a, messageWithExtension1b, true},
|
||||
{"extension vs. different extension", messageWithExtension1a, messageWithExtension2, false},
|
||||
|
||||
{"int32 extension vs. itself", messageWithInt32Extension1, messageWithInt32Extension1, true},
|
||||
{"int32 extension vs. a different int32", messageWithInt32Extension1, messageWithInt32Extension2, false},
|
||||
|
||||
{
|
||||
"message with group",
|
||||
&pb.MyMessage{
|
||||
Count: Int32(1),
|
||||
Somegroup: &pb.MyMessage_SomeGroup{
|
||||
GroupField: Int32(5),
|
||||
},
|
||||
},
|
||||
&pb.MyMessage{
|
||||
Count: Int32(1),
|
||||
Somegroup: &pb.MyMessage_SomeGroup{
|
||||
GroupField: Int32(5),
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"map same",
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"map different entry",
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{2: "Rob"}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"map different key only",
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{2: "Ken"}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"map different value only",
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken"}},
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Rob"}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"zero-length maps same",
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{}},
|
||||
&pb.MessageWithMap{NameMapping: nil},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"orders in map don't matter",
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{1: "Ken", 2: "Rob"}},
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{2: "Rob", 1: "Ken"}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"oneof same",
|
||||
&pb.Communique{Union: &pb.Communique_Number{41}},
|
||||
&pb.Communique{Union: &pb.Communique_Number{41}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"oneof one nil",
|
||||
&pb.Communique{Union: &pb.Communique_Number{41}},
|
||||
&pb.Communique{},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"oneof different",
|
||||
&pb.Communique{Union: &pb.Communique_Number{41}},
|
||||
&pb.Communique{Union: &pb.Communique_Name{"Bobby Tables"}},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
func TestEqual(t *testing.T) {
|
||||
for _, tc := range EqualTests {
|
||||
if res := Equal(tc.a, tc.b); res != tc.exp {
|
||||
t.Errorf("%v: Equal(%v, %v) = %v, want %v", tc.desc, tc.a, tc.b, res, tc.exp)
|
||||
}
|
||||
}
|
||||
}
|
||||
-536
@@ -1,536 +0,0 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
pb "github.com/golang/protobuf/proto/testdata"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func TestGetExtensionsWithMissingExtensions(t *testing.T) {
|
||||
msg := &pb.MyMessage{}
|
||||
ext1 := &pb.Ext{}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_More, ext1); err != nil {
|
||||
t.Fatalf("Could not set ext1: %s", err)
|
||||
}
|
||||
exts, err := proto.GetExtensions(msg, []*proto.ExtensionDesc{
|
||||
pb.E_Ext_More,
|
||||
pb.E_Ext_Text,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("GetExtensions() failed: %s", err)
|
||||
}
|
||||
if exts[0] != ext1 {
|
||||
t.Errorf("ext1 not in returned extensions: %T %v", exts[0], exts[0])
|
||||
}
|
||||
if exts[1] != nil {
|
||||
t.Errorf("ext2 in returned extensions: %T %v", exts[1], exts[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtensionDescsWithMissingExtensions(t *testing.T) {
|
||||
msg := &pb.MyMessage{Count: proto.Int32(0)}
|
||||
extdesc1 := pb.E_Ext_More
|
||||
if descs, err := proto.ExtensionDescs(msg); len(descs) != 0 || err != nil {
|
||||
t.Errorf("proto.ExtensionDescs: got %d descs, error %v; want 0, nil", len(descs), err)
|
||||
}
|
||||
|
||||
ext1 := &pb.Ext{}
|
||||
if err := proto.SetExtension(msg, extdesc1, ext1); err != nil {
|
||||
t.Fatalf("Could not set ext1: %s", err)
|
||||
}
|
||||
extdesc2 := &proto.ExtensionDesc{
|
||||
ExtendedType: (*pb.MyMessage)(nil),
|
||||
ExtensionType: (*bool)(nil),
|
||||
Field: 123456789,
|
||||
Name: "a.b",
|
||||
Tag: "varint,123456789,opt",
|
||||
}
|
||||
ext2 := proto.Bool(false)
|
||||
if err := proto.SetExtension(msg, extdesc2, ext2); err != nil {
|
||||
t.Fatalf("Could not set ext2: %s", err)
|
||||
}
|
||||
|
||||
b, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not marshal msg: %v", err)
|
||||
}
|
||||
if err := proto.Unmarshal(b, msg); err != nil {
|
||||
t.Fatalf("Could not unmarshal into msg: %v", err)
|
||||
}
|
||||
|
||||
descs, err := proto.ExtensionDescs(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("proto.ExtensionDescs: got error %v", err)
|
||||
}
|
||||
sortExtDescs(descs)
|
||||
wantDescs := []*proto.ExtensionDesc{extdesc1, &proto.ExtensionDesc{Field: extdesc2.Field}}
|
||||
if !reflect.DeepEqual(descs, wantDescs) {
|
||||
t.Errorf("proto.ExtensionDescs(msg) sorted extension ids: got %+v, want %+v", descs, wantDescs)
|
||||
}
|
||||
}
|
||||
|
||||
type ExtensionDescSlice []*proto.ExtensionDesc
|
||||
|
||||
func (s ExtensionDescSlice) Len() int { return len(s) }
|
||||
func (s ExtensionDescSlice) Less(i, j int) bool { return s[i].Field < s[j].Field }
|
||||
func (s ExtensionDescSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
func sortExtDescs(s []*proto.ExtensionDesc) {
|
||||
sort.Sort(ExtensionDescSlice(s))
|
||||
}
|
||||
|
||||
func TestGetExtensionStability(t *testing.T) {
|
||||
check := func(m *pb.MyMessage) bool {
|
||||
ext1, err := proto.GetExtension(m, pb.E_Ext_More)
|
||||
if err != nil {
|
||||
t.Fatalf("GetExtension() failed: %s", err)
|
||||
}
|
||||
ext2, err := proto.GetExtension(m, pb.E_Ext_More)
|
||||
if err != nil {
|
||||
t.Fatalf("GetExtension() failed: %s", err)
|
||||
}
|
||||
return ext1 == ext2
|
||||
}
|
||||
msg := &pb.MyMessage{Count: proto.Int32(4)}
|
||||
ext0 := &pb.Ext{}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_More, ext0); err != nil {
|
||||
t.Fatalf("Could not set ext1: %s", ext0)
|
||||
}
|
||||
if !check(msg) {
|
||||
t.Errorf("GetExtension() not stable before marshaling")
|
||||
}
|
||||
bb, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal() failed: %s", err)
|
||||
}
|
||||
msg1 := &pb.MyMessage{}
|
||||
err = proto.Unmarshal(bb, msg1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unmarshal() failed: %s", err)
|
||||
}
|
||||
if !check(msg1) {
|
||||
t.Errorf("GetExtension() not stable after unmarshaling")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExtensionDefaults(t *testing.T) {
|
||||
var setFloat64 float64 = 1
|
||||
var setFloat32 float32 = 2
|
||||
var setInt32 int32 = 3
|
||||
var setInt64 int64 = 4
|
||||
var setUint32 uint32 = 5
|
||||
var setUint64 uint64 = 6
|
||||
var setBool = true
|
||||
var setBool2 = false
|
||||
var setString = "Goodnight string"
|
||||
var setBytes = []byte("Goodnight bytes")
|
||||
var setEnum = pb.DefaultsMessage_TWO
|
||||
|
||||
type testcase struct {
|
||||
ext *proto.ExtensionDesc // Extension we are testing.
|
||||
want interface{} // Expected value of extension, or nil (meaning that GetExtension will fail).
|
||||
def interface{} // Expected value of extension after ClearExtension().
|
||||
}
|
||||
tests := []testcase{
|
||||
{pb.E_NoDefaultDouble, setFloat64, nil},
|
||||
{pb.E_NoDefaultFloat, setFloat32, nil},
|
||||
{pb.E_NoDefaultInt32, setInt32, nil},
|
||||
{pb.E_NoDefaultInt64, setInt64, nil},
|
||||
{pb.E_NoDefaultUint32, setUint32, nil},
|
||||
{pb.E_NoDefaultUint64, setUint64, nil},
|
||||
{pb.E_NoDefaultSint32, setInt32, nil},
|
||||
{pb.E_NoDefaultSint64, setInt64, nil},
|
||||
{pb.E_NoDefaultFixed32, setUint32, nil},
|
||||
{pb.E_NoDefaultFixed64, setUint64, nil},
|
||||
{pb.E_NoDefaultSfixed32, setInt32, nil},
|
||||
{pb.E_NoDefaultSfixed64, setInt64, nil},
|
||||
{pb.E_NoDefaultBool, setBool, nil},
|
||||
{pb.E_NoDefaultBool, setBool2, nil},
|
||||
{pb.E_NoDefaultString, setString, nil},
|
||||
{pb.E_NoDefaultBytes, setBytes, nil},
|
||||
{pb.E_NoDefaultEnum, setEnum, nil},
|
||||
{pb.E_DefaultDouble, setFloat64, float64(3.1415)},
|
||||
{pb.E_DefaultFloat, setFloat32, float32(3.14)},
|
||||
{pb.E_DefaultInt32, setInt32, int32(42)},
|
||||
{pb.E_DefaultInt64, setInt64, int64(43)},
|
||||
{pb.E_DefaultUint32, setUint32, uint32(44)},
|
||||
{pb.E_DefaultUint64, setUint64, uint64(45)},
|
||||
{pb.E_DefaultSint32, setInt32, int32(46)},
|
||||
{pb.E_DefaultSint64, setInt64, int64(47)},
|
||||
{pb.E_DefaultFixed32, setUint32, uint32(48)},
|
||||
{pb.E_DefaultFixed64, setUint64, uint64(49)},
|
||||
{pb.E_DefaultSfixed32, setInt32, int32(50)},
|
||||
{pb.E_DefaultSfixed64, setInt64, int64(51)},
|
||||
{pb.E_DefaultBool, setBool, true},
|
||||
{pb.E_DefaultBool, setBool2, true},
|
||||
{pb.E_DefaultString, setString, "Hello, string"},
|
||||
{pb.E_DefaultBytes, setBytes, []byte("Hello, bytes")},
|
||||
{pb.E_DefaultEnum, setEnum, pb.DefaultsMessage_ONE},
|
||||
}
|
||||
|
||||
checkVal := func(test testcase, msg *pb.DefaultsMessage, valWant interface{}) error {
|
||||
val, err := proto.GetExtension(msg, test.ext)
|
||||
if err != nil {
|
||||
if valWant != nil {
|
||||
return fmt.Errorf("GetExtension(): %s", err)
|
||||
}
|
||||
if want := proto.ErrMissingExtension; err != want {
|
||||
return fmt.Errorf("Unexpected error: got %v, want %v", err, want)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// All proto2 extension values are either a pointer to a value or a slice of values.
|
||||
ty := reflect.TypeOf(val)
|
||||
tyWant := reflect.TypeOf(test.ext.ExtensionType)
|
||||
if got, want := ty, tyWant; got != want {
|
||||
return fmt.Errorf("unexpected reflect.TypeOf(): got %v want %v", got, want)
|
||||
}
|
||||
tye := ty.Elem()
|
||||
tyeWant := tyWant.Elem()
|
||||
if got, want := tye, tyeWant; got != want {
|
||||
return fmt.Errorf("unexpected reflect.TypeOf().Elem(): got %v want %v", got, want)
|
||||
}
|
||||
|
||||
// Check the name of the type of the value.
|
||||
// If it is an enum it will be type int32 with the name of the enum.
|
||||
if got, want := tye.Name(), tye.Name(); got != want {
|
||||
return fmt.Errorf("unexpected reflect.TypeOf().Elem().Name(): got %v want %v", got, want)
|
||||
}
|
||||
|
||||
// Check that value is what we expect.
|
||||
// If we have a pointer in val, get the value it points to.
|
||||
valExp := val
|
||||
if ty.Kind() == reflect.Ptr {
|
||||
valExp = reflect.ValueOf(val).Elem().Interface()
|
||||
}
|
||||
if got, want := valExp, valWant; !reflect.DeepEqual(got, want) {
|
||||
return fmt.Errorf("unexpected reflect.DeepEqual(): got %v want %v", got, want)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
setTo := func(test testcase) interface{} {
|
||||
setTo := reflect.ValueOf(test.want)
|
||||
if typ := reflect.TypeOf(test.ext.ExtensionType); typ.Kind() == reflect.Ptr {
|
||||
setTo = reflect.New(typ).Elem()
|
||||
setTo.Set(reflect.New(setTo.Type().Elem()))
|
||||
setTo.Elem().Set(reflect.ValueOf(test.want))
|
||||
}
|
||||
return setTo.Interface()
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
msg := &pb.DefaultsMessage{}
|
||||
name := test.ext.Name
|
||||
|
||||
// Check the initial value.
|
||||
if err := checkVal(test, msg, test.def); err != nil {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
}
|
||||
|
||||
// Set the per-type value and check value.
|
||||
name = fmt.Sprintf("%s (set to %T %v)", name, test.want, test.want)
|
||||
if err := proto.SetExtension(msg, test.ext, setTo(test)); err != nil {
|
||||
t.Errorf("%s: SetExtension(): %v", name, err)
|
||||
continue
|
||||
}
|
||||
if err := checkVal(test, msg, test.want); err != nil {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Set and check the value.
|
||||
name += " (cleared)"
|
||||
proto.ClearExtension(msg, test.ext)
|
||||
if err := checkVal(test, msg, test.def); err != nil {
|
||||
t.Errorf("%s: %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtensionsRoundTrip(t *testing.T) {
|
||||
msg := &pb.MyMessage{}
|
||||
ext1 := &pb.Ext{
|
||||
Data: proto.String("hi"),
|
||||
}
|
||||
ext2 := &pb.Ext{
|
||||
Data: proto.String("there"),
|
||||
}
|
||||
exists := proto.HasExtension(msg, pb.E_Ext_More)
|
||||
if exists {
|
||||
t.Error("Extension More present unexpectedly")
|
||||
}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_More, ext1); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_More, ext2); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
e, err := proto.GetExtension(msg, pb.E_Ext_More)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
x, ok := e.(*pb.Ext)
|
||||
if !ok {
|
||||
t.Errorf("e has type %T, expected testdata.Ext", e)
|
||||
} else if *x.Data != "there" {
|
||||
t.Errorf("SetExtension failed to overwrite, got %+v, not 'there'", x)
|
||||
}
|
||||
proto.ClearExtension(msg, pb.E_Ext_More)
|
||||
if _, err = proto.GetExtension(msg, pb.E_Ext_More); err != proto.ErrMissingExtension {
|
||||
t.Errorf("got %v, expected ErrMissingExtension", e)
|
||||
}
|
||||
if _, err := proto.GetExtension(msg, pb.E_X215); err == nil {
|
||||
t.Error("expected bad extension error, got nil")
|
||||
}
|
||||
if err := proto.SetExtension(msg, pb.E_X215, 12); err == nil {
|
||||
t.Error("expected extension err")
|
||||
}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_More, 12); err == nil {
|
||||
t.Error("expected some sort of type mismatch error, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilExtension(t *testing.T) {
|
||||
msg := &pb.MyMessage{
|
||||
Count: proto.Int32(1),
|
||||
}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_Text, proto.String("hello")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_More, (*pb.Ext)(nil)); err == nil {
|
||||
t.Error("expected SetExtension to fail due to a nil extension")
|
||||
} else if want := "proto: SetExtension called with nil value of type *testdata.Ext"; err.Error() != want {
|
||||
t.Errorf("expected error %v, got %v", want, err)
|
||||
}
|
||||
// Note: if the behavior of Marshal is ever changed to ignore nil extensions, update
|
||||
// this test to verify that E_Ext_Text is properly propagated through marshal->unmarshal.
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalRepeatedExtension(t *testing.T) {
|
||||
// Add a repeated extension to the result.
|
||||
tests := []struct {
|
||||
name string
|
||||
ext []*pb.ComplexExtension
|
||||
}{
|
||||
{
|
||||
"two fields",
|
||||
[]*pb.ComplexExtension{
|
||||
{First: proto.Int32(7)},
|
||||
{Second: proto.Int32(11)},
|
||||
},
|
||||
},
|
||||
{
|
||||
"repeated field",
|
||||
[]*pb.ComplexExtension{
|
||||
{Third: []int32{1000}},
|
||||
{Third: []int32{2000}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"two fields and repeated field",
|
||||
[]*pb.ComplexExtension{
|
||||
{Third: []int32{1000}},
|
||||
{First: proto.Int32(9)},
|
||||
{Second: proto.Int32(21)},
|
||||
{Third: []int32{2000}},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
// Marshal message with a repeated extension.
|
||||
msg1 := new(pb.OtherMessage)
|
||||
err := proto.SetExtension(msg1, pb.E_RComplex, test.ext)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error setting extension: %v", test.name, err)
|
||||
}
|
||||
b, err := proto.Marshal(msg1)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error marshaling message: %v", test.name, err)
|
||||
}
|
||||
|
||||
// Unmarshal and read the merged proto.
|
||||
msg2 := new(pb.OtherMessage)
|
||||
err = proto.Unmarshal(b, msg2)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error unmarshaling message: %v", test.name, err)
|
||||
}
|
||||
e, err := proto.GetExtension(msg2, pb.E_RComplex)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error getting extension: %v", test.name, err)
|
||||
}
|
||||
ext := e.([]*pb.ComplexExtension)
|
||||
if ext == nil {
|
||||
t.Fatalf("[%s] Invalid extension", test.name)
|
||||
}
|
||||
if !reflect.DeepEqual(ext, test.ext) {
|
||||
t.Errorf("[%s] Wrong value for ComplexExtension: got: %v want: %v\n", test.name, ext, test.ext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalRepeatingNonRepeatedExtension(t *testing.T) {
|
||||
// We may see multiple instances of the same extension in the wire
|
||||
// format. For example, the proto compiler may encode custom options in
|
||||
// this way. Here, we verify that we merge the extensions together.
|
||||
tests := []struct {
|
||||
name string
|
||||
ext []*pb.ComplexExtension
|
||||
}{
|
||||
{
|
||||
"two fields",
|
||||
[]*pb.ComplexExtension{
|
||||
{First: proto.Int32(7)},
|
||||
{Second: proto.Int32(11)},
|
||||
},
|
||||
},
|
||||
{
|
||||
"repeated field",
|
||||
[]*pb.ComplexExtension{
|
||||
{Third: []int32{1000}},
|
||||
{Third: []int32{2000}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"two fields and repeated field",
|
||||
[]*pb.ComplexExtension{
|
||||
{Third: []int32{1000}},
|
||||
{First: proto.Int32(9)},
|
||||
{Second: proto.Int32(21)},
|
||||
{Third: []int32{2000}},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
var buf bytes.Buffer
|
||||
var want pb.ComplexExtension
|
||||
|
||||
// Generate a serialized representation of a repeated extension
|
||||
// by catenating bytes together.
|
||||
for i, e := range test.ext {
|
||||
// Merge to create the wanted proto.
|
||||
proto.Merge(&want, e)
|
||||
|
||||
// serialize the message
|
||||
msg := new(pb.OtherMessage)
|
||||
err := proto.SetExtension(msg, pb.E_Complex, e)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error setting extension %d: %v", test.name, i, err)
|
||||
}
|
||||
b, err := proto.Marshal(msg)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error marshaling message %d: %v", test.name, i, err)
|
||||
}
|
||||
buf.Write(b)
|
||||
}
|
||||
|
||||
// Unmarshal and read the merged proto.
|
||||
msg2 := new(pb.OtherMessage)
|
||||
err := proto.Unmarshal(buf.Bytes(), msg2)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error unmarshaling message: %v", test.name, err)
|
||||
}
|
||||
e, err := proto.GetExtension(msg2, pb.E_Complex)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Error getting extension: %v", test.name, err)
|
||||
}
|
||||
ext := e.(*pb.ComplexExtension)
|
||||
if ext == nil {
|
||||
t.Fatalf("[%s] Invalid extension", test.name)
|
||||
}
|
||||
if !reflect.DeepEqual(*ext, want) {
|
||||
t.Errorf("[%s] Wrong value for ComplexExtension: got: %s want: %s\n", test.name, ext, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearAllExtensions(t *testing.T) {
|
||||
// unregistered extension
|
||||
desc := &proto.ExtensionDesc{
|
||||
ExtendedType: (*pb.MyMessage)(nil),
|
||||
ExtensionType: (*bool)(nil),
|
||||
Field: 101010100,
|
||||
Name: "emptyextension",
|
||||
Tag: "varint,0,opt",
|
||||
}
|
||||
m := &pb.MyMessage{}
|
||||
if proto.HasExtension(m, desc) {
|
||||
t.Errorf("proto.HasExtension(%s): got true, want false", proto.MarshalTextString(m))
|
||||
}
|
||||
if err := proto.SetExtension(m, desc, proto.Bool(true)); err != nil {
|
||||
t.Errorf("proto.SetExtension(m, desc, true): got error %q, want nil", err)
|
||||
}
|
||||
if !proto.HasExtension(m, desc) {
|
||||
t.Errorf("proto.HasExtension(%s): got false, want true", proto.MarshalTextString(m))
|
||||
}
|
||||
proto.ClearAllExtensions(m)
|
||||
if proto.HasExtension(m, desc) {
|
||||
t.Errorf("proto.HasExtension(%s): got true, want false", proto.MarshalTextString(m))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalRace(t *testing.T) {
|
||||
// unregistered extension
|
||||
desc := &proto.ExtensionDesc{
|
||||
ExtendedType: (*pb.MyMessage)(nil),
|
||||
ExtensionType: (*bool)(nil),
|
||||
Field: 101010100,
|
||||
Name: "emptyextension",
|
||||
Tag: "varint,0,opt",
|
||||
}
|
||||
|
||||
m := &pb.MyMessage{Count: proto.Int32(4)}
|
||||
if err := proto.SetExtension(m, desc, proto.Bool(true)); err != nil {
|
||||
t.Errorf("proto.SetExtension(m, desc, true): got error %q, want nil", err)
|
||||
}
|
||||
|
||||
var g errgroup.Group
|
||||
for n := 3; n > 0; n-- {
|
||||
g.Go(func() error {
|
||||
_, err := proto.Marshal(m)
|
||||
return err
|
||||
})
|
||||
}
|
||||
if err := g.Wait(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
ppb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
)
|
||||
|
||||
func marshalled() []byte {
|
||||
m := &ppb.IntMaps{}
|
||||
for i := 0; i < 1000; i++ {
|
||||
m.Maps = append(m.Maps, &ppb.IntMap{
|
||||
Rtt: map[int32]int32{1: 2},
|
||||
})
|
||||
}
|
||||
b, err := proto.Marshal(m)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Can't marshal %+v: %v", m, err))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func BenchmarkConcurrentMapUnmarshal(b *testing.B) {
|
||||
in := marshalled()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var out ppb.IntMaps
|
||||
if err := proto.Unmarshal(in, &out); err != nil {
|
||||
b.Errorf("Can't unmarshal ppb.IntMaps: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSequentialMapUnmarshal(b *testing.B) {
|
||||
in := marshalled()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var out ppb.IntMaps
|
||||
if err := proto.Unmarshal(in, &out); err != nil {
|
||||
b.Errorf("Can't unmarshal ppb.IntMaps: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnmarshalMessageSetWithDuplicate(t *testing.T) {
|
||||
// Check that a repeated message set entry will be concatenated.
|
||||
in := &messageSet{
|
||||
Item: []*_MessageSet_Item{
|
||||
{TypeId: Int32(12345), Message: []byte("hoo")},
|
||||
{TypeId: Int32(12345), Message: []byte("hah")},
|
||||
},
|
||||
}
|
||||
b, err := Marshal(in)
|
||||
if err != nil {
|
||||
t.Fatalf("Marshal: %v", err)
|
||||
}
|
||||
t.Logf("Marshaled bytes: %q", b)
|
||||
|
||||
var extensions XXX_InternalExtensions
|
||||
if err := UnmarshalMessageSet(b, &extensions); err != nil {
|
||||
t.Fatalf("UnmarshalMessageSet: %v", err)
|
||||
}
|
||||
ext, ok := extensions.p.extensionMap[12345]
|
||||
if !ok {
|
||||
t.Fatalf("Didn't retrieve extension 12345; map is %v", extensions.p.extensionMap)
|
||||
}
|
||||
// Skip wire type/field number and length varints.
|
||||
got := skipVarint(skipVarint(ext.enc))
|
||||
if want := []byte("hoohah"); !bytes.Equal(got, want) {
|
||||
t.Errorf("Combined extension is %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
-135
@@ -1,135 +0,0 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
pb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
tpb "github.com/golang/protobuf/proto/testdata"
|
||||
)
|
||||
|
||||
func TestProto3ZeroValues(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
m proto.Message
|
||||
}{
|
||||
{"zero message", &pb.Message{}},
|
||||
{"empty bytes field", &pb.Message{Data: []byte{}}},
|
||||
}
|
||||
for _, test := range tests {
|
||||
b, err := proto.Marshal(test.m)
|
||||
if err != nil {
|
||||
t.Errorf("%s: proto.Marshal: %v", test.desc, err)
|
||||
continue
|
||||
}
|
||||
if len(b) > 0 {
|
||||
t.Errorf("%s: Encoding is non-empty: %q", test.desc, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoundTripProto3(t *testing.T) {
|
||||
m := &pb.Message{
|
||||
Name: "David", // (2 | 1<<3): 0x0a 0x05 "David"
|
||||
Hilarity: pb.Message_PUNS, // (0 | 2<<3): 0x10 0x01
|
||||
HeightInCm: 178, // (0 | 3<<3): 0x18 0xb2 0x01
|
||||
Data: []byte("roboto"), // (2 | 4<<3): 0x20 0x06 "roboto"
|
||||
ResultCount: 47, // (0 | 7<<3): 0x38 0x2f
|
||||
TrueScotsman: true, // (0 | 8<<3): 0x40 0x01
|
||||
Score: 8.1, // (5 | 9<<3): 0x4d <8.1>
|
||||
|
||||
Key: []uint64{1, 0xdeadbeef},
|
||||
Nested: &pb.Nested{
|
||||
Bunny: "Monty",
|
||||
},
|
||||
}
|
||||
t.Logf(" m: %v", m)
|
||||
|
||||
b, err := proto.Marshal(m)
|
||||
if err != nil {
|
||||
t.Fatalf("proto.Marshal: %v", err)
|
||||
}
|
||||
t.Logf(" b: %q", b)
|
||||
|
||||
m2 := new(pb.Message)
|
||||
if err := proto.Unmarshal(b, m2); err != nil {
|
||||
t.Fatalf("proto.Unmarshal: %v", err)
|
||||
}
|
||||
t.Logf("m2: %v", m2)
|
||||
|
||||
if !proto.Equal(m, m2) {
|
||||
t.Errorf("proto.Equal returned false:\n m: %v\nm2: %v", m, m2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGettersForBasicTypesExist(t *testing.T) {
|
||||
var m pb.Message
|
||||
if got := m.GetNested().GetBunny(); got != "" {
|
||||
t.Errorf("m.GetNested().GetBunny() = %q, want empty string", got)
|
||||
}
|
||||
if got := m.GetNested().GetCute(); got {
|
||||
t.Errorf("m.GetNested().GetCute() = %t, want false", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProto3SetDefaults(t *testing.T) {
|
||||
in := &pb.Message{
|
||||
Terrain: map[string]*pb.Nested{
|
||||
"meadow": new(pb.Nested),
|
||||
},
|
||||
Proto2Field: new(tpb.SubDefaults),
|
||||
Proto2Value: map[string]*tpb.SubDefaults{
|
||||
"badlands": new(tpb.SubDefaults),
|
||||
},
|
||||
}
|
||||
|
||||
got := proto.Clone(in).(*pb.Message)
|
||||
proto.SetDefaults(got)
|
||||
|
||||
// There are no defaults in proto3. Everything should be the zero value, but
|
||||
// we need to remember to set defaults for nested proto2 messages.
|
||||
want := &pb.Message{
|
||||
Terrain: map[string]*pb.Nested{
|
||||
"meadow": new(pb.Nested),
|
||||
},
|
||||
Proto2Field: &tpb.SubDefaults{N: proto.Int64(7)},
|
||||
Proto2Value: map[string]*tpb.SubDefaults{
|
||||
"badlands": &tpb.SubDefaults{N: proto.Int64(7)},
|
||||
},
|
||||
}
|
||||
|
||||
if !proto.Equal(got, want) {
|
||||
t.Errorf("with in = %v\nproto.SetDefaults(in) =>\ngot %v\nwant %v", in, got, want)
|
||||
}
|
||||
}
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This is a separate file and package from size_test.go because that one uses
|
||||
// generated messages and thus may not be in package proto without having a circular
|
||||
// dependency, whereas this file tests unexported details of size.go.
|
||||
|
||||
func TestVarintSize(t *testing.T) {
|
||||
// Check the edge cases carefully.
|
||||
testCases := []struct {
|
||||
n uint64
|
||||
size int
|
||||
}{
|
||||
{0, 1},
|
||||
{1, 1},
|
||||
{127, 1},
|
||||
{128, 2},
|
||||
{16383, 2},
|
||||
{16384, 3},
|
||||
{1<<63 - 1, 9},
|
||||
{1 << 63, 10},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
size := sizeVarint(tc.n)
|
||||
if size != tc.size {
|
||||
t.Errorf("sizeVarint(%d) = %d, want %d", tc.n, size, tc.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
-164
@@ -1,164 +0,0 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/golang/protobuf/proto"
|
||||
proto3pb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
pb "github.com/golang/protobuf/proto/testdata"
|
||||
)
|
||||
|
||||
var messageWithExtension1 = &pb.MyMessage{Count: Int32(7)}
|
||||
|
||||
// messageWithExtension2 is in equal_test.go.
|
||||
var messageWithExtension3 = &pb.MyMessage{Count: Int32(8)}
|
||||
|
||||
func init() {
|
||||
if err := SetExtension(messageWithExtension1, pb.E_Ext_More, &pb.Ext{Data: String("Abbott")}); err != nil {
|
||||
log.Panicf("SetExtension: %v", err)
|
||||
}
|
||||
if err := SetExtension(messageWithExtension3, pb.E_Ext_More, &pb.Ext{Data: String("Costello")}); err != nil {
|
||||
log.Panicf("SetExtension: %v", err)
|
||||
}
|
||||
|
||||
// Force messageWithExtension3 to have the extension encoded.
|
||||
Marshal(messageWithExtension3)
|
||||
|
||||
}
|
||||
|
||||
var SizeTests = []struct {
|
||||
desc string
|
||||
pb Message
|
||||
}{
|
||||
{"empty", &pb.OtherMessage{}},
|
||||
// Basic types.
|
||||
{"bool", &pb.Defaults{F_Bool: Bool(true)}},
|
||||
{"int32", &pb.Defaults{F_Int32: Int32(12)}},
|
||||
{"negative int32", &pb.Defaults{F_Int32: Int32(-1)}},
|
||||
{"small int64", &pb.Defaults{F_Int64: Int64(1)}},
|
||||
{"big int64", &pb.Defaults{F_Int64: Int64(1 << 20)}},
|
||||
{"negative int64", &pb.Defaults{F_Int64: Int64(-1)}},
|
||||
{"fixed32", &pb.Defaults{F_Fixed32: Uint32(71)}},
|
||||
{"fixed64", &pb.Defaults{F_Fixed64: Uint64(72)}},
|
||||
{"uint32", &pb.Defaults{F_Uint32: Uint32(123)}},
|
||||
{"uint64", &pb.Defaults{F_Uint64: Uint64(124)}},
|
||||
{"float", &pb.Defaults{F_Float: Float32(12.6)}},
|
||||
{"double", &pb.Defaults{F_Double: Float64(13.9)}},
|
||||
{"string", &pb.Defaults{F_String: String("niles")}},
|
||||
{"bytes", &pb.Defaults{F_Bytes: []byte("wowsa")}},
|
||||
{"bytes, empty", &pb.Defaults{F_Bytes: []byte{}}},
|
||||
{"sint32", &pb.Defaults{F_Sint32: Int32(65)}},
|
||||
{"sint64", &pb.Defaults{F_Sint64: Int64(67)}},
|
||||
{"enum", &pb.Defaults{F_Enum: pb.Defaults_BLUE.Enum()}},
|
||||
// Repeated.
|
||||
{"empty repeated bool", &pb.MoreRepeated{Bools: []bool{}}},
|
||||
{"repeated bool", &pb.MoreRepeated{Bools: []bool{false, true, true, false}}},
|
||||
{"packed repeated bool", &pb.MoreRepeated{BoolsPacked: []bool{false, true, true, false, true, true, true}}},
|
||||
{"repeated int32", &pb.MoreRepeated{Ints: []int32{1, 12203, 1729, -1}}},
|
||||
{"repeated int32 packed", &pb.MoreRepeated{IntsPacked: []int32{1, 12203, 1729}}},
|
||||
{"repeated int64 packed", &pb.MoreRepeated{Int64SPacked: []int64{
|
||||
// Need enough large numbers to verify that the header is counting the number of bytes
|
||||
// for the field, not the number of elements.
|
||||
1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62,
|
||||
1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62, 1 << 62,
|
||||
}}},
|
||||
{"repeated string", &pb.MoreRepeated{Strings: []string{"r", "ken", "gri"}}},
|
||||
{"repeated fixed", &pb.MoreRepeated{Fixeds: []uint32{1, 2, 3, 4}}},
|
||||
// Nested.
|
||||
{"nested", &pb.OldMessage{Nested: &pb.OldMessage_Nested{Name: String("whatever")}}},
|
||||
{"group", &pb.GroupOld{G: &pb.GroupOld_G{X: Int32(12345)}}},
|
||||
// Other things.
|
||||
{"unrecognized", &pb.MoreRepeated{XXX_unrecognized: []byte{13<<3 | 0, 4}}},
|
||||
{"extension (unencoded)", messageWithExtension1},
|
||||
{"extension (encoded)", messageWithExtension3},
|
||||
// proto3 message
|
||||
{"proto3 empty", &proto3pb.Message{}},
|
||||
{"proto3 bool", &proto3pb.Message{TrueScotsman: true}},
|
||||
{"proto3 int64", &proto3pb.Message{ResultCount: 1}},
|
||||
{"proto3 uint32", &proto3pb.Message{HeightInCm: 123}},
|
||||
{"proto3 float", &proto3pb.Message{Score: 12.6}},
|
||||
{"proto3 string", &proto3pb.Message{Name: "Snezana"}},
|
||||
{"proto3 bytes", &proto3pb.Message{Data: []byte("wowsa")}},
|
||||
{"proto3 bytes, empty", &proto3pb.Message{Data: []byte{}}},
|
||||
{"proto3 enum", &proto3pb.Message{Hilarity: proto3pb.Message_PUNS}},
|
||||
{"proto3 map field with empty bytes", &proto3pb.MessageWithMap{ByteMapping: map[bool][]byte{false: []byte{}}}},
|
||||
|
||||
{"map field", &pb.MessageWithMap{NameMapping: map[int32]string{1: "Rob", 7: "Andrew"}}},
|
||||
{"map field with message", &pb.MessageWithMap{MsgMapping: map[int64]*pb.FloatingPoint{0x7001: &pb.FloatingPoint{F: Float64(2.0)}}}},
|
||||
{"map field with bytes", &pb.MessageWithMap{ByteMapping: map[bool][]byte{true: []byte("this time for sure")}}},
|
||||
{"map field with empty bytes", &pb.MessageWithMap{ByteMapping: map[bool][]byte{true: []byte{}}}},
|
||||
|
||||
{"map field with big entry", &pb.MessageWithMap{NameMapping: map[int32]string{8: strings.Repeat("x", 125)}}},
|
||||
{"map field with big key and val", &pb.MessageWithMap{StrToStr: map[string]string{strings.Repeat("x", 70): strings.Repeat("y", 70)}}},
|
||||
{"map field with big numeric key", &pb.MessageWithMap{NameMapping: map[int32]string{0xf00d: "om nom nom"}}},
|
||||
|
||||
{"oneof not set", &pb.Oneof{}},
|
||||
{"oneof bool", &pb.Oneof{Union: &pb.Oneof_F_Bool{true}}},
|
||||
{"oneof zero int32", &pb.Oneof{Union: &pb.Oneof_F_Int32{0}}},
|
||||
{"oneof big int32", &pb.Oneof{Union: &pb.Oneof_F_Int32{1 << 20}}},
|
||||
{"oneof int64", &pb.Oneof{Union: &pb.Oneof_F_Int64{42}}},
|
||||
{"oneof fixed32", &pb.Oneof{Union: &pb.Oneof_F_Fixed32{43}}},
|
||||
{"oneof fixed64", &pb.Oneof{Union: &pb.Oneof_F_Fixed64{44}}},
|
||||
{"oneof uint32", &pb.Oneof{Union: &pb.Oneof_F_Uint32{45}}},
|
||||
{"oneof uint64", &pb.Oneof{Union: &pb.Oneof_F_Uint64{46}}},
|
||||
{"oneof float", &pb.Oneof{Union: &pb.Oneof_F_Float{47.1}}},
|
||||
{"oneof double", &pb.Oneof{Union: &pb.Oneof_F_Double{48.9}}},
|
||||
{"oneof string", &pb.Oneof{Union: &pb.Oneof_F_String{"Rhythmic Fman"}}},
|
||||
{"oneof bytes", &pb.Oneof{Union: &pb.Oneof_F_Bytes{[]byte("let go")}}},
|
||||
{"oneof sint32", &pb.Oneof{Union: &pb.Oneof_F_Sint32{50}}},
|
||||
{"oneof sint64", &pb.Oneof{Union: &pb.Oneof_F_Sint64{51}}},
|
||||
{"oneof enum", &pb.Oneof{Union: &pb.Oneof_F_Enum{pb.MyMessage_BLUE}}},
|
||||
{"message for oneof", &pb.GoTestField{Label: String("k"), Type: String("v")}},
|
||||
{"oneof message", &pb.Oneof{Union: &pb.Oneof_F_Message{&pb.GoTestField{Label: String("k"), Type: String("v")}}}},
|
||||
{"oneof group", &pb.Oneof{Union: &pb.Oneof_FGroup{&pb.Oneof_F_Group{X: Int32(52)}}}},
|
||||
{"oneof largest tag", &pb.Oneof{Union: &pb.Oneof_F_Largest_Tag{1}}},
|
||||
{"multiple oneofs", &pb.Oneof{Union: &pb.Oneof_F_Int32{1}, Tormato: &pb.Oneof_Value{2}}},
|
||||
}
|
||||
|
||||
func TestSize(t *testing.T) {
|
||||
for _, tc := range SizeTests {
|
||||
size := Size(tc.pb)
|
||||
b, err := Marshal(tc.pb)
|
||||
if err != nil {
|
||||
t.Errorf("%v: Marshal failed: %v", tc.desc, err)
|
||||
continue
|
||||
}
|
||||
if size != len(b) {
|
||||
t.Errorf("%v: Size(%v) = %d, want %d", tc.desc, tc.pb, size, len(b))
|
||||
t.Logf("%v: bytes: %#v", tc.desc, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
-673
@@ -1,673 +0,0 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
. "github.com/golang/protobuf/proto"
|
||||
proto3pb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
. "github.com/golang/protobuf/proto/testdata"
|
||||
)
|
||||
|
||||
type UnmarshalTextTest struct {
|
||||
in string
|
||||
err string // if "", no error expected
|
||||
out *MyMessage
|
||||
}
|
||||
|
||||
func buildExtStructTest(text string) UnmarshalTextTest {
|
||||
msg := &MyMessage{
|
||||
Count: Int32(42),
|
||||
}
|
||||
SetExtension(msg, E_Ext_More, &Ext{
|
||||
Data: String("Hello, world!"),
|
||||
})
|
||||
return UnmarshalTextTest{in: text, out: msg}
|
||||
}
|
||||
|
||||
func buildExtDataTest(text string) UnmarshalTextTest {
|
||||
msg := &MyMessage{
|
||||
Count: Int32(42),
|
||||
}
|
||||
SetExtension(msg, E_Ext_Text, String("Hello, world!"))
|
||||
SetExtension(msg, E_Ext_Number, Int32(1729))
|
||||
return UnmarshalTextTest{in: text, out: msg}
|
||||
}
|
||||
|
||||
func buildExtRepStringTest(text string) UnmarshalTextTest {
|
||||
msg := &MyMessage{
|
||||
Count: Int32(42),
|
||||
}
|
||||
if err := SetExtension(msg, E_Greeting, []string{"bula", "hola"}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return UnmarshalTextTest{in: text, out: msg}
|
||||
}
|
||||
|
||||
var unMarshalTextTests = []UnmarshalTextTest{
|
||||
// Basic
|
||||
{
|
||||
in: " count:42\n name:\"Dave\" ",
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("Dave"),
|
||||
},
|
||||
},
|
||||
|
||||
// Empty quoted string
|
||||
{
|
||||
in: `count:42 name:""`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String(""),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string concatenation with double quotes
|
||||
{
|
||||
in: `count:42 name: "My name is "` + "\n" + `"elsewhere"`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("My name is elsewhere"),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string concatenation with single quotes
|
||||
{
|
||||
in: "count:42 name: 'My name is '\n'elsewhere'",
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("My name is elsewhere"),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string concatenations with mixed quotes
|
||||
{
|
||||
in: "count:42 name: 'My name is '\n\"elsewhere\"",
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("My name is elsewhere"),
|
||||
},
|
||||
},
|
||||
{
|
||||
in: "count:42 name: \"My name is \"\n'elsewhere'",
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("My name is elsewhere"),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string with escaped apostrophe
|
||||
{
|
||||
in: `count:42 name: "HOLIDAY - New Year\'s Day"`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("HOLIDAY - New Year's Day"),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string with single quote
|
||||
{
|
||||
in: `count:42 name: 'Roger "The Ramster" Ramjet'`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String(`Roger "The Ramster" Ramjet`),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string with all the accepted special characters from the C++ test
|
||||
{
|
||||
in: `count:42 name: ` + "\"\\\"A string with \\' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"",
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces"),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string with quoted backslash
|
||||
{
|
||||
in: `count:42 name: "\\'xyz"`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String(`\'xyz`),
|
||||
},
|
||||
},
|
||||
|
||||
// Quoted string with UTF-8 bytes.
|
||||
{
|
||||
in: "count:42 name: '\303\277\302\201\xAB'",
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("\303\277\302\201\xAB"),
|
||||
},
|
||||
},
|
||||
|
||||
// Bad quoted string
|
||||
{
|
||||
in: `inner: < host: "\0" >` + "\n",
|
||||
err: `line 1.15: invalid quoted string "\0": \0 requires 2 following digits`,
|
||||
},
|
||||
|
||||
// Number too large for int64
|
||||
{
|
||||
in: "count: 1 others { key: 123456789012345678901 }",
|
||||
err: "line 1.23: invalid int64: 123456789012345678901",
|
||||
},
|
||||
|
||||
// Number too large for int32
|
||||
{
|
||||
in: "count: 1234567890123",
|
||||
err: "line 1.7: invalid int32: 1234567890123",
|
||||
},
|
||||
|
||||
// Number in hexadecimal
|
||||
{
|
||||
in: "count: 0x2beef",
|
||||
out: &MyMessage{
|
||||
Count: Int32(0x2beef),
|
||||
},
|
||||
},
|
||||
|
||||
// Number in octal
|
||||
{
|
||||
in: "count: 024601",
|
||||
out: &MyMessage{
|
||||
Count: Int32(024601),
|
||||
},
|
||||
},
|
||||
|
||||
// Floating point number with "f" suffix
|
||||
{
|
||||
in: "count: 4 others:< weight: 17.0f >",
|
||||
out: &MyMessage{
|
||||
Count: Int32(4),
|
||||
Others: []*OtherMessage{
|
||||
{
|
||||
Weight: Float32(17),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Floating point positive infinity
|
||||
{
|
||||
in: "count: 4 bigfloat: inf",
|
||||
out: &MyMessage{
|
||||
Count: Int32(4),
|
||||
Bigfloat: Float64(math.Inf(1)),
|
||||
},
|
||||
},
|
||||
|
||||
// Floating point negative infinity
|
||||
{
|
||||
in: "count: 4 bigfloat: -inf",
|
||||
out: &MyMessage{
|
||||
Count: Int32(4),
|
||||
Bigfloat: Float64(math.Inf(-1)),
|
||||
},
|
||||
},
|
||||
|
||||
// Number too large for float32
|
||||
{
|
||||
in: "others:< weight: 12345678901234567890123456789012345678901234567890 >",
|
||||
err: "line 1.17: invalid float32: 12345678901234567890123456789012345678901234567890",
|
||||
},
|
||||
|
||||
// Number posing as a quoted string
|
||||
{
|
||||
in: `inner: < host: 12 >` + "\n",
|
||||
err: `line 1.15: invalid string: 12`,
|
||||
},
|
||||
|
||||
// Quoted string posing as int32
|
||||
{
|
||||
in: `count: "12"`,
|
||||
err: `line 1.7: invalid int32: "12"`,
|
||||
},
|
||||
|
||||
// Quoted string posing a float32
|
||||
{
|
||||
in: `others:< weight: "17.4" >`,
|
||||
err: `line 1.17: invalid float32: "17.4"`,
|
||||
},
|
||||
|
||||
// Enum
|
||||
{
|
||||
in: `count:42 bikeshed: BLUE`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Bikeshed: MyMessage_BLUE.Enum(),
|
||||
},
|
||||
},
|
||||
|
||||
// Repeated field
|
||||
{
|
||||
in: `count:42 pet: "horsey" pet:"bunny"`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Pet: []string{"horsey", "bunny"},
|
||||
},
|
||||
},
|
||||
|
||||
// Repeated field with list notation
|
||||
{
|
||||
in: `count:42 pet: ["horsey", "bunny"]`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Pet: []string{"horsey", "bunny"},
|
||||
},
|
||||
},
|
||||
|
||||
// Repeated message with/without colon and <>/{}
|
||||
{
|
||||
in: `count:42 others:{} others{} others:<> others:{}`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Others: []*OtherMessage{
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Missing colon for inner message
|
||||
{
|
||||
in: `count:42 inner < host: "cauchy.syd" >`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Inner: &InnerMessage{
|
||||
Host: String("cauchy.syd"),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Missing colon for string field
|
||||
{
|
||||
in: `name "Dave"`,
|
||||
err: `line 1.5: expected ':', found "\"Dave\""`,
|
||||
},
|
||||
|
||||
// Missing colon for int32 field
|
||||
{
|
||||
in: `count 42`,
|
||||
err: `line 1.6: expected ':', found "42"`,
|
||||
},
|
||||
|
||||
// Missing required field
|
||||
{
|
||||
in: `name: "Pawel"`,
|
||||
err: `proto: required field "testdata.MyMessage.count" not set`,
|
||||
out: &MyMessage{
|
||||
Name: String("Pawel"),
|
||||
},
|
||||
},
|
||||
|
||||
// Missing required field in a required submessage
|
||||
{
|
||||
in: `count: 42 we_must_go_deeper < leo_finally_won_an_oscar <> >`,
|
||||
err: `proto: required field "testdata.InnerMessage.host" not set`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
WeMustGoDeeper: &RequiredInnerMessage{LeoFinallyWonAnOscar: &InnerMessage{}},
|
||||
},
|
||||
},
|
||||
|
||||
// Repeated non-repeated field
|
||||
{
|
||||
in: `name: "Rob" name: "Russ"`,
|
||||
err: `line 1.12: non-repeated field "name" was repeated`,
|
||||
},
|
||||
|
||||
// Group
|
||||
{
|
||||
in: `count: 17 SomeGroup { group_field: 12 }`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(17),
|
||||
Somegroup: &MyMessage_SomeGroup{
|
||||
GroupField: Int32(12),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Semicolon between fields
|
||||
{
|
||||
in: `count:3;name:"Calvin"`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(3),
|
||||
Name: String("Calvin"),
|
||||
},
|
||||
},
|
||||
// Comma between fields
|
||||
{
|
||||
in: `count:4,name:"Ezekiel"`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(4),
|
||||
Name: String("Ezekiel"),
|
||||
},
|
||||
},
|
||||
|
||||
// Boolean false
|
||||
{
|
||||
in: `count:42 inner { host: "example.com" connected: false }`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Inner: &InnerMessage{
|
||||
Host: String("example.com"),
|
||||
Connected: Bool(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
// Boolean true
|
||||
{
|
||||
in: `count:42 inner { host: "example.com" connected: true }`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Inner: &InnerMessage{
|
||||
Host: String("example.com"),
|
||||
Connected: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
// Boolean 0
|
||||
{
|
||||
in: `count:42 inner { host: "example.com" connected: 0 }`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Inner: &InnerMessage{
|
||||
Host: String("example.com"),
|
||||
Connected: Bool(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
// Boolean 1
|
||||
{
|
||||
in: `count:42 inner { host: "example.com" connected: 1 }`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Inner: &InnerMessage{
|
||||
Host: String("example.com"),
|
||||
Connected: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
// Boolean f
|
||||
{
|
||||
in: `count:42 inner { host: "example.com" connected: f }`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Inner: &InnerMessage{
|
||||
Host: String("example.com"),
|
||||
Connected: Bool(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
// Boolean t
|
||||
{
|
||||
in: `count:42 inner { host: "example.com" connected: t }`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Inner: &InnerMessage{
|
||||
Host: String("example.com"),
|
||||
Connected: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
// Boolean False
|
||||
{
|
||||
in: `count:42 inner { host: "example.com" connected: False }`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Inner: &InnerMessage{
|
||||
Host: String("example.com"),
|
||||
Connected: Bool(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
// Boolean True
|
||||
{
|
||||
in: `count:42 inner { host: "example.com" connected: True }`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Inner: &InnerMessage{
|
||||
Host: String("example.com"),
|
||||
Connected: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// Extension
|
||||
buildExtStructTest(`count: 42 [testdata.Ext.more]:<data:"Hello, world!" >`),
|
||||
buildExtStructTest(`count: 42 [testdata.Ext.more] {data:"Hello, world!"}`),
|
||||
buildExtDataTest(`count: 42 [testdata.Ext.text]:"Hello, world!" [testdata.Ext.number]:1729`),
|
||||
buildExtRepStringTest(`count: 42 [testdata.greeting]:"bula" [testdata.greeting]:"hola"`),
|
||||
|
||||
// Big all-in-one
|
||||
{
|
||||
in: "count:42 # Meaning\n" +
|
||||
`name:"Dave" ` +
|
||||
`quote:"\"I didn't want to go.\"" ` +
|
||||
`pet:"bunny" ` +
|
||||
`pet:"kitty" ` +
|
||||
`pet:"horsey" ` +
|
||||
`inner:<` +
|
||||
` host:"footrest.syd" ` +
|
||||
` port:7001 ` +
|
||||
` connected:true ` +
|
||||
`> ` +
|
||||
`others:<` +
|
||||
` key:3735928559 ` +
|
||||
` value:"\x01A\a\f" ` +
|
||||
`> ` +
|
||||
`others:<` +
|
||||
" weight:58.9 # Atomic weight of Co\n" +
|
||||
` inner:<` +
|
||||
` host:"lesha.mtv" ` +
|
||||
` port:8002 ` +
|
||||
` >` +
|
||||
`>`,
|
||||
out: &MyMessage{
|
||||
Count: Int32(42),
|
||||
Name: String("Dave"),
|
||||
Quote: String(`"I didn't want to go."`),
|
||||
Pet: []string{"bunny", "kitty", "horsey"},
|
||||
Inner: &InnerMessage{
|
||||
Host: String("footrest.syd"),
|
||||
Port: Int32(7001),
|
||||
Connected: Bool(true),
|
||||
},
|
||||
Others: []*OtherMessage{
|
||||
{
|
||||
Key: Int64(3735928559),
|
||||
Value: []byte{0x1, 'A', '\a', '\f'},
|
||||
},
|
||||
{
|
||||
Weight: Float32(58.9),
|
||||
Inner: &InnerMessage{
|
||||
Host: String("lesha.mtv"),
|
||||
Port: Int32(8002),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestUnmarshalText(t *testing.T) {
|
||||
for i, test := range unMarshalTextTests {
|
||||
pb := new(MyMessage)
|
||||
err := UnmarshalText(test.in, pb)
|
||||
if test.err == "" {
|
||||
// We don't expect failure.
|
||||
if err != nil {
|
||||
t.Errorf("Test %d: Unexpected error: %v", i, err)
|
||||
} else if !reflect.DeepEqual(pb, test.out) {
|
||||
t.Errorf("Test %d: Incorrect populated \nHave: %v\nWant: %v",
|
||||
i, pb, test.out)
|
||||
}
|
||||
} else {
|
||||
// We do expect failure.
|
||||
if err == nil {
|
||||
t.Errorf("Test %d: Didn't get expected error: %v", i, test.err)
|
||||
} else if err.Error() != test.err {
|
||||
t.Errorf("Test %d: Incorrect error.\nHave: %v\nWant: %v",
|
||||
i, err.Error(), test.err)
|
||||
} else if _, ok := err.(*RequiredNotSetError); ok && test.out != nil && !reflect.DeepEqual(pb, test.out) {
|
||||
t.Errorf("Test %d: Incorrect populated \nHave: %v\nWant: %v",
|
||||
i, pb, test.out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalTextCustomMessage(t *testing.T) {
|
||||
msg := &textMessage{}
|
||||
if err := UnmarshalText("custom", msg); err != nil {
|
||||
t.Errorf("Unexpected error from custom unmarshal: %v", err)
|
||||
}
|
||||
if UnmarshalText("not custom", msg) == nil {
|
||||
t.Errorf("Didn't get expected error from custom unmarshal")
|
||||
}
|
||||
}
|
||||
|
||||
// Regression test; this caused a panic.
|
||||
func TestRepeatedEnum(t *testing.T) {
|
||||
pb := new(RepeatedEnum)
|
||||
if err := UnmarshalText("color: RED", pb); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
exp := &RepeatedEnum{
|
||||
Color: []RepeatedEnum_Color{RepeatedEnum_RED},
|
||||
}
|
||||
if !Equal(pb, exp) {
|
||||
t.Errorf("Incorrect populated \nHave: %v\nWant: %v", pb, exp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProto3TextParsing(t *testing.T) {
|
||||
m := new(proto3pb.Message)
|
||||
const in = `name: "Wallace" true_scotsman: true`
|
||||
want := &proto3pb.Message{
|
||||
Name: "Wallace",
|
||||
TrueScotsman: true,
|
||||
}
|
||||
if err := UnmarshalText(in, m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !Equal(m, want) {
|
||||
t.Errorf("\n got %v\nwant %v", m, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapParsing(t *testing.T) {
|
||||
m := new(MessageWithMap)
|
||||
const in = `name_mapping:<key:1234 value:"Feist"> name_mapping:<key:1 value:"Beatles">` +
|
||||
`msg_mapping:<key:-4, value:<f: 2.0>,>` + // separating commas are okay
|
||||
`msg_mapping<key:-2 value<f: 4.0>>` + // no colon after "value"
|
||||
`msg_mapping:<value:<f: 5.0>>` + // omitted key
|
||||
`msg_mapping:<key:1>` + // omitted value
|
||||
`byte_mapping:<key:true value:"so be it">` +
|
||||
`byte_mapping:<>` // omitted key and value
|
||||
want := &MessageWithMap{
|
||||
NameMapping: map[int32]string{
|
||||
1: "Beatles",
|
||||
1234: "Feist",
|
||||
},
|
||||
MsgMapping: map[int64]*FloatingPoint{
|
||||
-4: {F: Float64(2.0)},
|
||||
-2: {F: Float64(4.0)},
|
||||
0: {F: Float64(5.0)},
|
||||
1: nil,
|
||||
},
|
||||
ByteMapping: map[bool][]byte{
|
||||
false: nil,
|
||||
true: []byte("so be it"),
|
||||
},
|
||||
}
|
||||
if err := UnmarshalText(in, m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !Equal(m, want) {
|
||||
t.Errorf("\n got %v\nwant %v", m, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOneofParsing(t *testing.T) {
|
||||
const in = `name:"Shrek"`
|
||||
m := new(Communique)
|
||||
want := &Communique{Union: &Communique_Name{"Shrek"}}
|
||||
if err := UnmarshalText(in, m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !Equal(m, want) {
|
||||
t.Errorf("\n got %v\nwant %v", m, want)
|
||||
}
|
||||
|
||||
const inOverwrite = `name:"Shrek" number:42`
|
||||
m = new(Communique)
|
||||
testErr := "line 1.13: field 'number' would overwrite already parsed oneof 'Union'"
|
||||
if err := UnmarshalText(inOverwrite, m); err == nil {
|
||||
t.Errorf("TestOneofParsing: Didn't get expected error: %v", testErr)
|
||||
} else if err.Error() != testErr {
|
||||
t.Errorf("TestOneofParsing: Incorrect error.\nHave: %v\nWant: %v",
|
||||
err.Error(), testErr)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var benchInput string
|
||||
|
||||
func init() {
|
||||
benchInput = "count: 4\n"
|
||||
for i := 0; i < 1000; i++ {
|
||||
benchInput += "pet: \"fido\"\n"
|
||||
}
|
||||
|
||||
// Check it is valid input.
|
||||
pb := new(MyMessage)
|
||||
err := UnmarshalText(benchInput, pb)
|
||||
if err != nil {
|
||||
panic("Bad benchmark input: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalText(b *testing.B) {
|
||||
pb := new(MyMessage)
|
||||
for i := 0; i < b.N; i++ {
|
||||
UnmarshalText(benchInput, pb)
|
||||
}
|
||||
b.SetBytes(int64(len(benchInput)))
|
||||
}
|
||||
-474
@@ -1,474 +0,0 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
proto3pb "github.com/golang/protobuf/proto/proto3_proto"
|
||||
pb "github.com/golang/protobuf/proto/testdata"
|
||||
)
|
||||
|
||||
// textMessage implements the methods that allow it to marshal and unmarshal
|
||||
// itself as text.
|
||||
type textMessage struct {
|
||||
}
|
||||
|
||||
func (*textMessage) MarshalText() ([]byte, error) {
|
||||
return []byte("custom"), nil
|
||||
}
|
||||
|
||||
func (*textMessage) UnmarshalText(bytes []byte) error {
|
||||
if string(bytes) != "custom" {
|
||||
return errors.New("expected 'custom'")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*textMessage) Reset() {}
|
||||
func (*textMessage) String() string { return "" }
|
||||
func (*textMessage) ProtoMessage() {}
|
||||
|
||||
func newTestMessage() *pb.MyMessage {
|
||||
msg := &pb.MyMessage{
|
||||
Count: proto.Int32(42),
|
||||
Name: proto.String("Dave"),
|
||||
Quote: proto.String(`"I didn't want to go."`),
|
||||
Pet: []string{"bunny", "kitty", "horsey"},
|
||||
Inner: &pb.InnerMessage{
|
||||
Host: proto.String("footrest.syd"),
|
||||
Port: proto.Int32(7001),
|
||||
Connected: proto.Bool(true),
|
||||
},
|
||||
Others: []*pb.OtherMessage{
|
||||
{
|
||||
Key: proto.Int64(0xdeadbeef),
|
||||
Value: []byte{1, 65, 7, 12},
|
||||
},
|
||||
{
|
||||
Weight: proto.Float32(6.022),
|
||||
Inner: &pb.InnerMessage{
|
||||
Host: proto.String("lesha.mtv"),
|
||||
Port: proto.Int32(8002),
|
||||
},
|
||||
},
|
||||
},
|
||||
Bikeshed: pb.MyMessage_BLUE.Enum(),
|
||||
Somegroup: &pb.MyMessage_SomeGroup{
|
||||
GroupField: proto.Int32(8),
|
||||
},
|
||||
// One normally wouldn't do this.
|
||||
// This is an undeclared tag 13, as a varint (wire type 0) with value 4.
|
||||
XXX_unrecognized: []byte{13<<3 | 0, 4},
|
||||
}
|
||||
ext := &pb.Ext{
|
||||
Data: proto.String("Big gobs for big rats"),
|
||||
}
|
||||
if err := proto.SetExtension(msg, pb.E_Ext_More, ext); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
greetings := []string{"adg", "easy", "cow"}
|
||||
if err := proto.SetExtension(msg, pb.E_Greeting, greetings); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Add an unknown extension. We marshal a pb.Ext, and fake the ID.
|
||||
b, err := proto.Marshal(&pb.Ext{Data: proto.String("3G skiing")})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
b = append(proto.EncodeVarint(201<<3|proto.WireBytes), b...)
|
||||
proto.SetRawExtension(msg, 201, b)
|
||||
|
||||
// Extensions can be plain fields, too, so let's test that.
|
||||
b = append(proto.EncodeVarint(202<<3|proto.WireVarint), 19)
|
||||
proto.SetRawExtension(msg, 202, b)
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
const text = `count: 42
|
||||
name: "Dave"
|
||||
quote: "\"I didn't want to go.\""
|
||||
pet: "bunny"
|
||||
pet: "kitty"
|
||||
pet: "horsey"
|
||||
inner: <
|
||||
host: "footrest.syd"
|
||||
port: 7001
|
||||
connected: true
|
||||
>
|
||||
others: <
|
||||
key: 3735928559
|
||||
value: "\001A\007\014"
|
||||
>
|
||||
others: <
|
||||
weight: 6.022
|
||||
inner: <
|
||||
host: "lesha.mtv"
|
||||
port: 8002
|
||||
>
|
||||
>
|
||||
bikeshed: BLUE
|
||||
SomeGroup {
|
||||
group_field: 8
|
||||
}
|
||||
/* 2 unknown bytes */
|
||||
13: 4
|
||||
[testdata.Ext.more]: <
|
||||
data: "Big gobs for big rats"
|
||||
>
|
||||
[testdata.greeting]: "adg"
|
||||
[testdata.greeting]: "easy"
|
||||
[testdata.greeting]: "cow"
|
||||
/* 13 unknown bytes */
|
||||
201: "\t3G skiing"
|
||||
/* 3 unknown bytes */
|
||||
202: 19
|
||||
`
|
||||
|
||||
func TestMarshalText(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := proto.MarshalText(buf, newTestMessage()); err != nil {
|
||||
t.Fatalf("proto.MarshalText: %v", err)
|
||||
}
|
||||
s := buf.String()
|
||||
if s != text {
|
||||
t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, text)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalTextCustomMessage(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := proto.MarshalText(buf, &textMessage{}); err != nil {
|
||||
t.Fatalf("proto.MarshalText: %v", err)
|
||||
}
|
||||
s := buf.String()
|
||||
if s != "custom" {
|
||||
t.Errorf("Got %q, expected %q", s, "custom")
|
||||
}
|
||||
}
|
||||
func TestMarshalTextNil(t *testing.T) {
|
||||
want := "<nil>"
|
||||
tests := []proto.Message{nil, (*pb.MyMessage)(nil)}
|
||||
for i, test := range tests {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := proto.MarshalText(buf, test); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := buf.String(); got != want {
|
||||
t.Errorf("%d: got %q want %q", i, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalTextUnknownEnum(t *testing.T) {
|
||||
// The Color enum only specifies values 0-2.
|
||||
m := &pb.MyMessage{Bikeshed: pb.MyMessage_Color(3).Enum()}
|
||||
got := m.String()
|
||||
const want = `bikeshed:3 `
|
||||
if got != want {
|
||||
t.Errorf("\n got %q\nwant %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTextOneof(t *testing.T) {
|
||||
tests := []struct {
|
||||
m proto.Message
|
||||
want string
|
||||
}{
|
||||
// zero message
|
||||
{&pb.Communique{}, ``},
|
||||
// scalar field
|
||||
{&pb.Communique{Union: &pb.Communique_Number{4}}, `number:4`},
|
||||
// message field
|
||||
{&pb.Communique{Union: &pb.Communique_Msg{
|
||||
&pb.Strings{StringField: proto.String("why hello!")},
|
||||
}}, `msg:<string_field:"why hello!" >`},
|
||||
// bad oneof (should not panic)
|
||||
{&pb.Communique{Union: &pb.Communique_Msg{nil}}, `msg:/* nil */`},
|
||||
}
|
||||
for _, test := range tests {
|
||||
got := strings.TrimSpace(test.m.String())
|
||||
if got != test.want {
|
||||
t.Errorf("\n got %s\nwant %s", got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalTextBuffered(b *testing.B) {
|
||||
buf := new(bytes.Buffer)
|
||||
m := newTestMessage()
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf.Reset()
|
||||
proto.MarshalText(buf, m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalTextUnbuffered(b *testing.B) {
|
||||
w := ioutil.Discard
|
||||
m := newTestMessage()
|
||||
for i := 0; i < b.N; i++ {
|
||||
proto.MarshalText(w, m)
|
||||
}
|
||||
}
|
||||
|
||||
func compact(src string) string {
|
||||
// s/[ \n]+/ /g; s/ $//;
|
||||
dst := make([]byte, len(src))
|
||||
space, comment := false, false
|
||||
j := 0
|
||||
for i := 0; i < len(src); i++ {
|
||||
if strings.HasPrefix(src[i:], "/*") {
|
||||
comment = true
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if comment && strings.HasPrefix(src[i:], "*/") {
|
||||
comment = false
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if comment {
|
||||
continue
|
||||
}
|
||||
c := src[i]
|
||||
if c == ' ' || c == '\n' {
|
||||
space = true
|
||||
continue
|
||||
}
|
||||
if j > 0 && (dst[j-1] == ':' || dst[j-1] == '<' || dst[j-1] == '{') {
|
||||
space = false
|
||||
}
|
||||
if c == '{' {
|
||||
space = false
|
||||
}
|
||||
if space {
|
||||
dst[j] = ' '
|
||||
j++
|
||||
space = false
|
||||
}
|
||||
dst[j] = c
|
||||
j++
|
||||
}
|
||||
if space {
|
||||
dst[j] = ' '
|
||||
j++
|
||||
}
|
||||
return string(dst[0:j])
|
||||
}
|
||||
|
||||
var compactText = compact(text)
|
||||
|
||||
func TestCompactText(t *testing.T) {
|
||||
s := proto.CompactTextString(newTestMessage())
|
||||
if s != compactText {
|
||||
t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v\n===\n", s, compactText)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringEscaping(t *testing.T) {
|
||||
testCases := []struct {
|
||||
in *pb.Strings
|
||||
out string
|
||||
}{
|
||||
{
|
||||
// Test data from C++ test (TextFormatTest.StringEscape).
|
||||
// Single divergence: we don't escape apostrophes.
|
||||
&pb.Strings{StringField: proto.String("\"A string with ' characters \n and \r newlines and \t tabs and \001 slashes \\ and multiple spaces")},
|
||||
"string_field: \"\\\"A string with ' characters \\n and \\r newlines and \\t tabs and \\001 slashes \\\\ and multiple spaces\"\n",
|
||||
},
|
||||
{
|
||||
// Test data from the same C++ test.
|
||||
&pb.Strings{StringField: proto.String("\350\260\267\346\255\214")},
|
||||
"string_field: \"\\350\\260\\267\\346\\255\\214\"\n",
|
||||
},
|
||||
{
|
||||
// Some UTF-8.
|
||||
&pb.Strings{StringField: proto.String("\x00\x01\xff\x81")},
|
||||
`string_field: "\000\001\377\201"` + "\n",
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
var buf bytes.Buffer
|
||||
if err := proto.MarshalText(&buf, tc.in); err != nil {
|
||||
t.Errorf("proto.MarsalText: %v", err)
|
||||
continue
|
||||
}
|
||||
s := buf.String()
|
||||
if s != tc.out {
|
||||
t.Errorf("#%d: Got:\n%s\nExpected:\n%s\n", i, s, tc.out)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check round-trip.
|
||||
pb := new(pb.Strings)
|
||||
if err := proto.UnmarshalText(s, pb); err != nil {
|
||||
t.Errorf("#%d: UnmarshalText: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !proto.Equal(pb, tc.in) {
|
||||
t.Errorf("#%d: Round-trip failed:\nstart: %v\n end: %v", i, tc.in, pb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A limitedWriter accepts some output before it fails.
|
||||
// This is a proxy for something like a nearly-full or imminently-failing disk,
|
||||
// or a network connection that is about to die.
|
||||
type limitedWriter struct {
|
||||
b bytes.Buffer
|
||||
limit int
|
||||
}
|
||||
|
||||
var outOfSpace = errors.New("proto: insufficient space")
|
||||
|
||||
func (w *limitedWriter) Write(p []byte) (n int, err error) {
|
||||
var avail = w.limit - w.b.Len()
|
||||
if avail <= 0 {
|
||||
return 0, outOfSpace
|
||||
}
|
||||
if len(p) <= avail {
|
||||
return w.b.Write(p)
|
||||
}
|
||||
n, _ = w.b.Write(p[:avail])
|
||||
return n, outOfSpace
|
||||
}
|
||||
|
||||
func TestMarshalTextFailing(t *testing.T) {
|
||||
// Try lots of different sizes to exercise more error code-paths.
|
||||
for lim := 0; lim < len(text); lim++ {
|
||||
buf := new(limitedWriter)
|
||||
buf.limit = lim
|
||||
err := proto.MarshalText(buf, newTestMessage())
|
||||
// We expect a certain error, but also some partial results in the buffer.
|
||||
if err != outOfSpace {
|
||||
t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", err, outOfSpace)
|
||||
}
|
||||
s := buf.b.String()
|
||||
x := text[:buf.limit]
|
||||
if s != x {
|
||||
t.Errorf("Got:\n===\n%v===\nExpected:\n===\n%v===\n", s, x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloats(t *testing.T) {
|
||||
tests := []struct {
|
||||
f float64
|
||||
want string
|
||||
}{
|
||||
{0, "0"},
|
||||
{4.7, "4.7"},
|
||||
{math.Inf(1), "inf"},
|
||||
{math.Inf(-1), "-inf"},
|
||||
{math.NaN(), "nan"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
msg := &pb.FloatingPoint{F: &test.f}
|
||||
got := strings.TrimSpace(msg.String())
|
||||
want := `f:` + test.want
|
||||
if got != want {
|
||||
t.Errorf("f=%f: got %q, want %q", test.f, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepeatedNilText(t *testing.T) {
|
||||
m := &pb.MessageList{
|
||||
Message: []*pb.MessageList_Message{
|
||||
nil,
|
||||
&pb.MessageList_Message{
|
||||
Name: proto.String("Horse"),
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
want := `Message <nil>
|
||||
Message {
|
||||
name: "Horse"
|
||||
}
|
||||
Message <nil>
|
||||
`
|
||||
if s := proto.MarshalTextString(m); s != want {
|
||||
t.Errorf(" got: %s\nwant: %s", s, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProto3Text(t *testing.T) {
|
||||
tests := []struct {
|
||||
m proto.Message
|
||||
want string
|
||||
}{
|
||||
// zero message
|
||||
{&proto3pb.Message{}, ``},
|
||||
// zero message except for an empty byte slice
|
||||
{&proto3pb.Message{Data: []byte{}}, ``},
|
||||
// trivial case
|
||||
{&proto3pb.Message{Name: "Rob", HeightInCm: 175}, `name:"Rob" height_in_cm:175`},
|
||||
// empty map
|
||||
{&pb.MessageWithMap{}, ``},
|
||||
// non-empty map; map format is the same as a repeated struct,
|
||||
// and they are sorted by key (numerically for numeric keys).
|
||||
{
|
||||
&pb.MessageWithMap{NameMapping: map[int32]string{
|
||||
-1: "Negatory",
|
||||
7: "Lucky",
|
||||
1234: "Feist",
|
||||
6345789: "Otis",
|
||||
}},
|
||||
`name_mapping:<key:-1 value:"Negatory" > ` +
|
||||
`name_mapping:<key:7 value:"Lucky" > ` +
|
||||
`name_mapping:<key:1234 value:"Feist" > ` +
|
||||
`name_mapping:<key:6345789 value:"Otis" >`,
|
||||
},
|
||||
// map with nil value; not well-defined, but we shouldn't crash
|
||||
{
|
||||
&pb.MessageWithMap{MsgMapping: map[int64]*pb.FloatingPoint{7: nil}},
|
||||
`msg_mapping:<key:7 >`,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
got := strings.TrimSpace(test.m.String())
|
||||
if got != test.want {
|
||||
t.Errorf("\n got %s\nwant %s", got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testUUID = Must(Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479"))
|
||||
|
||||
func TestJSON(t *testing.T) {
|
||||
type S struct {
|
||||
ID1 UUID
|
||||
ID2 UUID
|
||||
}
|
||||
s1 := S{ID1: testUUID}
|
||||
data, err := json.Marshal(&s1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var s2 S
|
||||
if err := json.Unmarshal(data, &s2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(&s1, &s2) {
|
||||
t.Errorf("got %#v, want %#v", s2, s1)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUUID_MarshalJSON(b *testing.B) {
|
||||
x := &struct {
|
||||
UUID UUID `json:"uuid"`
|
||||
}{}
|
||||
var err error
|
||||
x.UUID, err = Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
js, err := json.Marshal(x)
|
||||
if err != nil {
|
||||
b.Fatalf("marshal json: %#v (%v)", js, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUUID_UnmarshalJSON(b *testing.B) {
|
||||
js := []byte(`{"uuid":"f47ac10b-58cc-0372-8567-0e02b2c3d479"}`)
|
||||
var x *struct {
|
||||
UUID UUID `json:"uuid"`
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := json.Unmarshal(js, &x)
|
||||
if err != nil {
|
||||
b.Fatalf("marshal json: %#v (%v)", js, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This test is only run when --regressions is passed on the go test line.
|
||||
var regressions = flag.Bool("regressions", false, "run uuid regression tests")
|
||||
|
||||
// TestClockSeqRace tests for a particular race condition of returning two
|
||||
// identical Version1 UUIDs. The duration of 1 minute was chosen as the race
|
||||
// condition, before being fixed, nearly always occured in under 30 seconds.
|
||||
func TestClockSeqRace(t *testing.T) {
|
||||
if !*regressions {
|
||||
t.Skip("skipping regression tests")
|
||||
}
|
||||
duration := time.Minute
|
||||
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
ch := make(chan UUID, 10000)
|
||||
ncpu := runtime.NumCPU()
|
||||
switch ncpu {
|
||||
case 0, 1:
|
||||
// We can't run the test effectively.
|
||||
t.Skip("skipping race test, only one CPU detected")
|
||||
return
|
||||
default:
|
||||
runtime.GOMAXPROCS(ncpu)
|
||||
}
|
||||
for i := 0; i < ncpu; i++ {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case ch <- Must(NewUUID()):
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
uuids := make(map[string]bool)
|
||||
cnt := 0
|
||||
start := time.Now()
|
||||
for u := range ch {
|
||||
s := u.String()
|
||||
if uuids[s] {
|
||||
t.Errorf("duplicate uuid after %d in %v: %s", cnt, time.Since(start), s)
|
||||
return
|
||||
}
|
||||
uuids[s] = true
|
||||
if time.Since(start) > duration {
|
||||
return
|
||||
}
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
-102
@@ -1,102 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestScan(t *testing.T) {
|
||||
var stringTest string = "f47ac10b-58cc-0372-8567-0e02b2c3d479"
|
||||
var badTypeTest int = 6
|
||||
var invalidTest string = "f47ac10b-58cc-0372-8567-0e02b2c3d4"
|
||||
|
||||
byteTest := make([]byte, 16)
|
||||
byteTestUUID := Must(Parse(stringTest))
|
||||
copy(byteTest, byteTestUUID[:])
|
||||
|
||||
// sunny day tests
|
||||
|
||||
var uuid UUID
|
||||
err := (&uuid).Scan(stringTest)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = (&uuid).Scan([]byte(stringTest))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = (&uuid).Scan(byteTest)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// bad type tests
|
||||
|
||||
err = (&uuid).Scan(badTypeTest)
|
||||
if err == nil {
|
||||
t.Error("int correctly parsed and shouldn't have")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "unable to scan type") {
|
||||
t.Error("attempting to parse an int returned an incorrect error message")
|
||||
}
|
||||
|
||||
// invalid/incomplete uuids
|
||||
|
||||
err = (&uuid).Scan(invalidTest)
|
||||
if err == nil {
|
||||
t.Error("invalid uuid was parsed without error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "invalid UUID") {
|
||||
t.Error("attempting to parse an invalid UUID returned an incorrect error message")
|
||||
}
|
||||
|
||||
err = (&uuid).Scan(byteTest[:len(byteTest)-2])
|
||||
if err == nil {
|
||||
t.Error("invalid byte uuid was parsed without error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "invalid UUID") {
|
||||
t.Error("attempting to parse an invalid byte UUID returned an incorrect error message")
|
||||
}
|
||||
|
||||
// empty tests
|
||||
|
||||
uuid = UUID{}
|
||||
var emptySlice []byte
|
||||
err = (&uuid).Scan(emptySlice)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, v := range uuid {
|
||||
if v != 0 {
|
||||
t.Error("UUID was not nil after scanning empty byte slice")
|
||||
}
|
||||
}
|
||||
|
||||
uuid = UUID{}
|
||||
var emptyString string
|
||||
err = (&uuid).Scan(emptyString)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, v := range uuid {
|
||||
if v != 0 {
|
||||
t.Error("UUID was not nil after scanning empty byte slice")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValue(t *testing.T) {
|
||||
stringTest := "f47ac10b-58cc-0372-8567-0e02b2c3d479"
|
||||
uuid := Must(Parse(stringTest))
|
||||
val, _ := uuid.Value()
|
||||
if val != stringTest {
|
||||
t.Error("Value() did not return expected string")
|
||||
}
|
||||
}
|
||||
-526
@@ -1,526 +0,0 @@
|
||||
// Copyright 2016 Google Inc. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type test struct {
|
||||
in string
|
||||
version Version
|
||||
variant Variant
|
||||
isuuid bool
|
||||
}
|
||||
|
||||
var tests = []test{
|
||||
{"f47ac10b-58cc-0372-8567-0e02b2c3d479", 0, RFC4122, true},
|
||||
{"f47ac10b-58cc-1372-8567-0e02b2c3d479", 1, RFC4122, true},
|
||||
{"f47ac10b-58cc-2372-8567-0e02b2c3d479", 2, RFC4122, true},
|
||||
{"f47ac10b-58cc-3372-8567-0e02b2c3d479", 3, RFC4122, true},
|
||||
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
|
||||
{"f47ac10b-58cc-5372-8567-0e02b2c3d479", 5, RFC4122, true},
|
||||
{"f47ac10b-58cc-6372-8567-0e02b2c3d479", 6, RFC4122, true},
|
||||
{"f47ac10b-58cc-7372-8567-0e02b2c3d479", 7, RFC4122, true},
|
||||
{"f47ac10b-58cc-8372-8567-0e02b2c3d479", 8, RFC4122, true},
|
||||
{"f47ac10b-58cc-9372-8567-0e02b2c3d479", 9, RFC4122, true},
|
||||
{"f47ac10b-58cc-a372-8567-0e02b2c3d479", 10, RFC4122, true},
|
||||
{"f47ac10b-58cc-b372-8567-0e02b2c3d479", 11, RFC4122, true},
|
||||
{"f47ac10b-58cc-c372-8567-0e02b2c3d479", 12, RFC4122, true},
|
||||
{"f47ac10b-58cc-d372-8567-0e02b2c3d479", 13, RFC4122, true},
|
||||
{"f47ac10b-58cc-e372-8567-0e02b2c3d479", 14, RFC4122, true},
|
||||
{"f47ac10b-58cc-f372-8567-0e02b2c3d479", 15, RFC4122, true},
|
||||
|
||||
{"urn:uuid:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"URN:UUID:f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-0567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-1567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-2567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-3567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-4567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-5567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-6567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-7567-0e02b2c3d479", 4, Reserved, true},
|
||||
{"f47ac10b-58cc-4372-8567-0e02b2c3d479", 4, RFC4122, true},
|
||||
{"f47ac10b-58cc-4372-9567-0e02b2c3d479", 4, RFC4122, true},
|
||||
{"f47ac10b-58cc-4372-a567-0e02b2c3d479", 4, RFC4122, true},
|
||||
{"f47ac10b-58cc-4372-b567-0e02b2c3d479", 4, RFC4122, true},
|
||||
{"f47ac10b-58cc-4372-c567-0e02b2c3d479", 4, Microsoft, true},
|
||||
{"f47ac10b-58cc-4372-d567-0e02b2c3d479", 4, Microsoft, true},
|
||||
{"f47ac10b-58cc-4372-e567-0e02b2c3d479", 4, Future, true},
|
||||
{"f47ac10b-58cc-4372-f567-0e02b2c3d479", 4, Future, true},
|
||||
|
||||
{"f47ac10b158cc-5372-a567-0e02b2c3d479", 0, Invalid, false},
|
||||
{"f47ac10b-58cc25372-a567-0e02b2c3d479", 0, Invalid, false},
|
||||
{"f47ac10b-58cc-53723a567-0e02b2c3d479", 0, Invalid, false},
|
||||
{"f47ac10b-58cc-5372-a56740e02b2c3d479", 0, Invalid, false},
|
||||
{"f47ac10b-58cc-5372-a567-0e02-2c3d479", 0, Invalid, false},
|
||||
{"g47ac10b-58cc-4372-a567-0e02b2c3d479", 0, Invalid, false},
|
||||
}
|
||||
|
||||
var constants = []struct {
|
||||
c interface{}
|
||||
name string
|
||||
}{
|
||||
{Person, "Person"},
|
||||
{Group, "Group"},
|
||||
{Org, "Org"},
|
||||
{Invalid, "Invalid"},
|
||||
{RFC4122, "RFC4122"},
|
||||
{Reserved, "Reserved"},
|
||||
{Microsoft, "Microsoft"},
|
||||
{Future, "Future"},
|
||||
{Domain(17), "Domain17"},
|
||||
{Variant(42), "BadVariant42"},
|
||||
}
|
||||
|
||||
func testTest(t *testing.T, in string, tt test) {
|
||||
uuid, err := Parse(in)
|
||||
if ok := (err == nil); ok != tt.isuuid {
|
||||
t.Errorf("Parse(%s) got %v expected %v\b", in, ok, tt.isuuid)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if v := uuid.Variant(); v != tt.variant {
|
||||
t.Errorf("Variant(%s) got %d expected %d\b", in, v, tt.variant)
|
||||
}
|
||||
if v := uuid.Version(); v != tt.version {
|
||||
t.Errorf("Version(%s) got %d expected %d\b", in, v, tt.version)
|
||||
}
|
||||
}
|
||||
|
||||
func testBytes(t *testing.T, in []byte, tt test) {
|
||||
uuid, err := ParseBytes(in)
|
||||
if ok := (err == nil); ok != tt.isuuid {
|
||||
t.Errorf("ParseBytes(%s) got %v expected %v\b", in, ok, tt.isuuid)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
suuid, _ := Parse(string(in))
|
||||
if uuid != suuid {
|
||||
t.Errorf("ParseBytes(%s) got %v expected %v\b", in, uuid, suuid)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUUID(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
testTest(t, tt.in, tt)
|
||||
testTest(t, strings.ToUpper(tt.in), tt)
|
||||
testBytes(t, []byte(tt.in), tt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstants(t *testing.T) {
|
||||
for x, tt := range constants {
|
||||
v, ok := tt.c.(fmt.Stringer)
|
||||
if !ok {
|
||||
t.Errorf("%x: %v: not a stringer", x, v)
|
||||
} else if s := v.String(); s != tt.name {
|
||||
v, _ := tt.c.(int)
|
||||
t.Errorf("%x: Constant %T:%d gives %q, expected %q", x, tt.c, v, s, tt.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomUUID(t *testing.T) {
|
||||
m := make(map[string]bool)
|
||||
for x := 1; x < 32; x++ {
|
||||
uuid := New()
|
||||
s := uuid.String()
|
||||
if m[s] {
|
||||
t.Errorf("NewRandom returned duplicated UUID %s", s)
|
||||
}
|
||||
m[s] = true
|
||||
if v := uuid.Version(); v != 4 {
|
||||
t.Errorf("Random UUID of version %s", v)
|
||||
}
|
||||
if uuid.Variant() != RFC4122 {
|
||||
t.Errorf("Random UUID is variant %d", uuid.Variant())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
m := make(map[UUID]bool)
|
||||
for x := 1; x < 32; x++ {
|
||||
s := New()
|
||||
if m[s] {
|
||||
t.Errorf("New returned duplicated UUID %s", s)
|
||||
}
|
||||
m[s] = true
|
||||
uuid, err := Parse(s.String())
|
||||
if err != nil {
|
||||
t.Errorf("New.String() returned %q which does not decode", s)
|
||||
continue
|
||||
}
|
||||
if v := uuid.Version(); v != 4 {
|
||||
t.Errorf("Random UUID of version %s", v)
|
||||
}
|
||||
if uuid.Variant() != RFC4122 {
|
||||
t.Errorf("Random UUID is variant %d", uuid.Variant())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClockSeq(t *testing.T) {
|
||||
// Fake time.Now for this test to return a monotonically advancing time; restore it at end.
|
||||
defer func(orig func() time.Time) { timeNow = orig }(timeNow)
|
||||
monTime := time.Now()
|
||||
timeNow = func() time.Time {
|
||||
monTime = monTime.Add(1 * time.Second)
|
||||
return monTime
|
||||
}
|
||||
|
||||
SetClockSequence(-1)
|
||||
uuid1, err := NewUUID()
|
||||
if err != nil {
|
||||
t.Fatalf("could not create UUID: %v", err)
|
||||
}
|
||||
uuid2, err := NewUUID()
|
||||
if err != nil {
|
||||
t.Fatalf("could not create UUID: %v", err)
|
||||
}
|
||||
|
||||
if s1, s2 := uuid1.ClockSequence(), uuid2.ClockSequence(); s1 != s2 {
|
||||
t.Errorf("clock sequence %d != %d", s1, s2)
|
||||
}
|
||||
|
||||
SetClockSequence(-1)
|
||||
uuid2, err = NewUUID()
|
||||
if err != nil {
|
||||
t.Fatalf("could not create UUID: %v", err)
|
||||
}
|
||||
|
||||
// Just on the very off chance we generated the same sequence
|
||||
// two times we try again.
|
||||
if uuid1.ClockSequence() == uuid2.ClockSequence() {
|
||||
SetClockSequence(-1)
|
||||
uuid2, err = NewUUID()
|
||||
if err != nil {
|
||||
t.Fatalf("could not create UUID: %v", err)
|
||||
}
|
||||
}
|
||||
if s1, s2 := uuid1.ClockSequence(), uuid2.ClockSequence(); s1 == s2 {
|
||||
t.Errorf("Duplicate clock sequence %d", s1)
|
||||
}
|
||||
|
||||
SetClockSequence(0x1234)
|
||||
uuid1, err = NewUUID()
|
||||
if err != nil {
|
||||
t.Fatalf("could not create UUID: %v", err)
|
||||
}
|
||||
if seq := uuid1.ClockSequence(); seq != 0x1234 {
|
||||
t.Errorf("%s: expected seq 0x1234 got 0x%04x", uuid1, seq)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoding(t *testing.T) {
|
||||
text := "7d444840-9dc0-11d1-b245-5ffdce74fad2"
|
||||
urn := "urn:uuid:7d444840-9dc0-11d1-b245-5ffdce74fad2"
|
||||
data := UUID{
|
||||
0x7d, 0x44, 0x48, 0x40,
|
||||
0x9d, 0xc0,
|
||||
0x11, 0xd1,
|
||||
0xb2, 0x45,
|
||||
0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2,
|
||||
}
|
||||
if v := data.String(); v != text {
|
||||
t.Errorf("%x: encoded to %s, expected %s", data, v, text)
|
||||
}
|
||||
if v := data.URN(); v != urn {
|
||||
t.Errorf("%x: urn is %s, expected %s", data, v, urn)
|
||||
}
|
||||
|
||||
uuid, err := Parse(text)
|
||||
if err != nil {
|
||||
t.Errorf("Parse returned unexpected error %v", err)
|
||||
}
|
||||
if data != data {
|
||||
t.Errorf("%s: decoded to %s, expected %s", text, uuid, data)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersion1(t *testing.T) {
|
||||
uuid1, err := NewUUID()
|
||||
if err != nil {
|
||||
t.Fatalf("could not create UUID: %v", err)
|
||||
}
|
||||
uuid2, err := NewUUID()
|
||||
if err != nil {
|
||||
t.Fatalf("could not create UUID: %v", err)
|
||||
}
|
||||
|
||||
if uuid1 == uuid2 {
|
||||
t.Errorf("%s:duplicate uuid", uuid1)
|
||||
}
|
||||
if v := uuid1.Version(); v != 1 {
|
||||
t.Errorf("%s: version %s expected 1", uuid1, v)
|
||||
}
|
||||
if v := uuid2.Version(); v != 1 {
|
||||
t.Errorf("%s: version %s expected 1", uuid2, v)
|
||||
}
|
||||
n1 := uuid1.NodeID()
|
||||
n2 := uuid2.NodeID()
|
||||
if !bytes.Equal(n1, n2) {
|
||||
t.Errorf("Different nodes %x != %x", n1, n2)
|
||||
}
|
||||
t1 := uuid1.Time()
|
||||
t2 := uuid2.Time()
|
||||
q1 := uuid1.ClockSequence()
|
||||
q2 := uuid2.ClockSequence()
|
||||
|
||||
switch {
|
||||
case t1 == t2 && q1 == q2:
|
||||
t.Error("time stopped")
|
||||
case t1 > t2 && q1 == q2:
|
||||
t.Error("time reversed")
|
||||
case t1 < t2 && q1 != q2:
|
||||
t.Error("clock sequence chaned unexpectedly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNode(t *testing.T) {
|
||||
// This test is mostly to make sure we don't leave nodeMu locked.
|
||||
ifname = ""
|
||||
if ni := NodeInterface(); ni != "" {
|
||||
t.Errorf("NodeInterface got %q, want %q", ni, "")
|
||||
}
|
||||
if SetNodeInterface("xyzzy") {
|
||||
t.Error("SetNodeInterface succeeded on a bad interface name")
|
||||
}
|
||||
if !SetNodeInterface("") {
|
||||
t.Error("SetNodeInterface failed")
|
||||
}
|
||||
if ni := NodeInterface(); ni == "" {
|
||||
t.Error("NodeInterface returned an empty string")
|
||||
}
|
||||
|
||||
ni := NodeID()
|
||||
if len(ni) != 6 {
|
||||
t.Errorf("ni got %d bytes, want 6", len(ni))
|
||||
}
|
||||
hasData := false
|
||||
for _, b := range ni {
|
||||
if b != 0 {
|
||||
hasData = true
|
||||
}
|
||||
}
|
||||
if !hasData {
|
||||
t.Error("nodeid is all zeros")
|
||||
}
|
||||
|
||||
id := []byte{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
SetNodeID(id)
|
||||
ni = NodeID()
|
||||
if !bytes.Equal(ni, id[:6]) {
|
||||
t.Errorf("got nodeid %v, want %v", ni, id[:6])
|
||||
}
|
||||
|
||||
if ni := NodeInterface(); ni != "user" {
|
||||
t.Errorf("got inteface %q, want %q", ni, "user")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeAndTime(t *testing.T) {
|
||||
// Time is February 5, 1998 12:30:23.136364800 AM GMT
|
||||
|
||||
uuid, err := Parse("7d444840-9dc0-11d1-b245-5ffdce74fad2")
|
||||
if err != nil {
|
||||
t.Fatalf("Parser returned unexpected error %v", err)
|
||||
}
|
||||
node := []byte{0x5f, 0xfd, 0xce, 0x74, 0xfa, 0xd2}
|
||||
|
||||
ts := uuid.Time()
|
||||
c := time.Unix(ts.UnixTime())
|
||||
want := time.Date(1998, 2, 5, 0, 30, 23, 136364800, time.UTC)
|
||||
if !c.Equal(want) {
|
||||
t.Errorf("Got time %v, want %v", c, want)
|
||||
}
|
||||
if !bytes.Equal(node, uuid.NodeID()) {
|
||||
t.Errorf("Expected node %v got %v", node, uuid.NodeID())
|
||||
}
|
||||
}
|
||||
|
||||
func TestMD5(t *testing.T) {
|
||||
uuid := NewMD5(NameSpaceDNS, []byte("python.org")).String()
|
||||
want := "6fa459ea-ee8a-3ca4-894e-db77e160355e"
|
||||
if uuid != want {
|
||||
t.Errorf("MD5: got %q expected %q", uuid, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSHA1(t *testing.T) {
|
||||
uuid := NewSHA1(NameSpaceDNS, []byte("python.org")).String()
|
||||
want := "886313e1-3b8a-5372-9b90-0c9aee199e5d"
|
||||
if uuid != want {
|
||||
t.Errorf("SHA1: got %q expected %q", uuid, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeID(t *testing.T) {
|
||||
nid := []byte{1, 2, 3, 4, 5, 6}
|
||||
SetNodeInterface("")
|
||||
s := NodeInterface()
|
||||
if s == "" || s == "user" {
|
||||
t.Errorf("NodeInterface %q after SetInteface", s)
|
||||
}
|
||||
node1 := NodeID()
|
||||
if node1 == nil {
|
||||
t.Error("NodeID nil after SetNodeInterface", s)
|
||||
}
|
||||
SetNodeID(nid)
|
||||
s = NodeInterface()
|
||||
if s != "user" {
|
||||
t.Errorf("Expected NodeInterface %q got %q", "user", s)
|
||||
}
|
||||
node2 := NodeID()
|
||||
if node2 == nil {
|
||||
t.Error("NodeID nil after SetNodeID", s)
|
||||
}
|
||||
if bytes.Equal(node1, node2) {
|
||||
t.Error("NodeID not changed after SetNodeID", s)
|
||||
} else if !bytes.Equal(nid, node2) {
|
||||
t.Errorf("NodeID is %x, expected %x", node2, nid)
|
||||
}
|
||||
}
|
||||
|
||||
func testDCE(t *testing.T, name string, uuid UUID, err error, domain Domain, id uint32) {
|
||||
if err != nil {
|
||||
t.Errorf("%s failed: %v", name, err)
|
||||
return
|
||||
}
|
||||
if v := uuid.Version(); v != 2 {
|
||||
t.Errorf("%s: %s: expected version 2, got %s", name, uuid, v)
|
||||
return
|
||||
}
|
||||
if v := uuid.Domain(); v != domain {
|
||||
t.Errorf("%s: %s: expected domain %d, got %d", name, uuid, domain, v)
|
||||
}
|
||||
if v := uuid.ID(); v != id {
|
||||
t.Errorf("%s: %s: expected id %d, got %d", name, uuid, id, v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDCE(t *testing.T) {
|
||||
uuid, err := NewDCESecurity(42, 12345678)
|
||||
testDCE(t, "NewDCESecurity", uuid, err, 42, 12345678)
|
||||
uuid, err = NewDCEPerson()
|
||||
testDCE(t, "NewDCEPerson", uuid, err, Person, uint32(os.Getuid()))
|
||||
uuid, err = NewDCEGroup()
|
||||
testDCE(t, "NewDCEGroup", uuid, err, Group, uint32(os.Getgid()))
|
||||
}
|
||||
|
||||
type badRand struct{}
|
||||
|
||||
func (r badRand) Read(buf []byte) (int, error) {
|
||||
for i, _ := range buf {
|
||||
buf[i] = byte(i)
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func TestBadRand(t *testing.T) {
|
||||
SetRand(badRand{})
|
||||
uuid1 := New()
|
||||
uuid2 := New()
|
||||
if uuid1 != uuid2 {
|
||||
t.Errorf("execpted duplicates, got %q and %q", uuid1, uuid2)
|
||||
}
|
||||
SetRand(nil)
|
||||
uuid1 = New()
|
||||
uuid2 = New()
|
||||
if uuid1 == uuid2 {
|
||||
t.Errorf("unexecpted duplicates, got %q", uuid1)
|
||||
}
|
||||
}
|
||||
|
||||
var asString = "f47ac10b-58cc-0372-8567-0e02b2c3d479"
|
||||
var asBytes = []byte(asString)
|
||||
|
||||
func BenchmarkParse(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := Parse(asString)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParseBytes(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := ParseBytes(asBytes)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseBytesUnsafe is to benchmark using unsafe.
|
||||
func parseBytesUnsafe(b []byte) (UUID, error) {
|
||||
return Parse(*(*string)(unsafe.Pointer(&b)))
|
||||
}
|
||||
|
||||
|
||||
func BenchmarkParseBytesUnsafe(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := parseBytesUnsafe(asBytes)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseBytesCopy is to benchmark not using unsafe.
|
||||
func parseBytesCopy(b []byte) (UUID, error) {
|
||||
return Parse(string(b))
|
||||
}
|
||||
|
||||
func BenchmarkParseBytesCopy(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := parseBytesCopy(asBytes)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNew(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
New()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUUID_String(b *testing.B) {
|
||||
uuid, err := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
if uuid.String() == "" {
|
||||
b.Fatal("invalid uuid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUUID_URN(b *testing.B) {
|
||||
uuid, err := Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
if uuid.URN() == "" {
|
||||
b.Fatal("invalid uuid")
|
||||
}
|
||||
}
|
||||
}
|
||||
-161
@@ -1,161 +0,0 @@
|
||||
// Copyright 2012 The Gorilla 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 context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type keyType int
|
||||
|
||||
const (
|
||||
key1 keyType = iota
|
||||
key2
|
||||
)
|
||||
|
||||
func TestContext(t *testing.T) {
|
||||
assertEqual := func(val interface{}, exp interface{}) {
|
||||
if val != exp {
|
||||
t.Errorf("Expected %v, got %v.", exp, val)
|
||||
}
|
||||
}
|
||||
|
||||
r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
|
||||
// Get()
|
||||
assertEqual(Get(r, key1), nil)
|
||||
|
||||
// Set()
|
||||
Set(r, key1, "1")
|
||||
assertEqual(Get(r, key1), "1")
|
||||
assertEqual(len(data[r]), 1)
|
||||
|
||||
Set(r, key2, "2")
|
||||
assertEqual(Get(r, key2), "2")
|
||||
assertEqual(len(data[r]), 2)
|
||||
|
||||
//GetOk
|
||||
value, ok := GetOk(r, key1)
|
||||
assertEqual(value, "1")
|
||||
assertEqual(ok, true)
|
||||
|
||||
value, ok = GetOk(r, "not exists")
|
||||
assertEqual(value, nil)
|
||||
assertEqual(ok, false)
|
||||
|
||||
Set(r, "nil value", nil)
|
||||
value, ok = GetOk(r, "nil value")
|
||||
assertEqual(value, nil)
|
||||
assertEqual(ok, true)
|
||||
|
||||
// GetAll()
|
||||
values := GetAll(r)
|
||||
assertEqual(len(values), 3)
|
||||
|
||||
// GetAll() for empty request
|
||||
values = GetAll(emptyR)
|
||||
if values != nil {
|
||||
t.Error("GetAll didn't return nil value for invalid request")
|
||||
}
|
||||
|
||||
// GetAllOk()
|
||||
values, ok = GetAllOk(r)
|
||||
assertEqual(len(values), 3)
|
||||
assertEqual(ok, true)
|
||||
|
||||
// GetAllOk() for empty request
|
||||
values, ok = GetAllOk(emptyR)
|
||||
assertEqual(len(values), 0)
|
||||
assertEqual(ok, false)
|
||||
|
||||
// Delete()
|
||||
Delete(r, key1)
|
||||
assertEqual(Get(r, key1), nil)
|
||||
assertEqual(len(data[r]), 2)
|
||||
|
||||
Delete(r, key2)
|
||||
assertEqual(Get(r, key2), nil)
|
||||
assertEqual(len(data[r]), 1)
|
||||
|
||||
// Clear()
|
||||
Clear(r)
|
||||
assertEqual(len(data), 0)
|
||||
}
|
||||
|
||||
func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) {
|
||||
<-wait
|
||||
for i := 0; i < iterations; i++ {
|
||||
Get(r, key)
|
||||
}
|
||||
done <- struct{}{}
|
||||
|
||||
}
|
||||
|
||||
func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) {
|
||||
<-wait
|
||||
for i := 0; i < iterations; i++ {
|
||||
Set(r, key, value)
|
||||
}
|
||||
done <- struct{}{}
|
||||
|
||||
}
|
||||
|
||||
func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) {
|
||||
|
||||
b.StopTimer()
|
||||
r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
done := make(chan struct{})
|
||||
b.StartTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
wait := make(chan struct{})
|
||||
|
||||
for i := 0; i < numReaders; i++ {
|
||||
go parallelReader(r, "test", iterations, wait, done)
|
||||
}
|
||||
|
||||
for i := 0; i < numWriters; i++ {
|
||||
go parallelWriter(r, "test", "123", iterations, wait, done)
|
||||
}
|
||||
|
||||
close(wait)
|
||||
|
||||
for i := 0; i < numReaders+numWriters; i++ {
|
||||
<-done
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkMutexSameReadWrite1(b *testing.B) {
|
||||
benchmarkMutex(b, 1, 1, 32)
|
||||
}
|
||||
func BenchmarkMutexSameReadWrite2(b *testing.B) {
|
||||
benchmarkMutex(b, 2, 2, 32)
|
||||
}
|
||||
func BenchmarkMutexSameReadWrite4(b *testing.B) {
|
||||
benchmarkMutex(b, 4, 4, 32)
|
||||
}
|
||||
func BenchmarkMutex1(b *testing.B) {
|
||||
benchmarkMutex(b, 2, 8, 32)
|
||||
}
|
||||
func BenchmarkMutex2(b *testing.B) {
|
||||
benchmarkMutex(b, 16, 4, 64)
|
||||
}
|
||||
func BenchmarkMutex3(b *testing.B) {
|
||||
benchmarkMutex(b, 1, 2, 128)
|
||||
}
|
||||
func BenchmarkMutex4(b *testing.B) {
|
||||
benchmarkMutex(b, 128, 32, 256)
|
||||
}
|
||||
func BenchmarkMutex5(b *testing.B) {
|
||||
benchmarkMutex(b, 1024, 2048, 64)
|
||||
}
|
||||
func BenchmarkMutex6(b *testing.B) {
|
||||
benchmarkMutex(b, 2048, 1024, 512)
|
||||
}
|
||||
-1
@@ -7,7 +7,6 @@ matrix:
|
||||
- go: 1.4
|
||||
- go: 1.5
|
||||
- go: 1.6
|
||||
- go: 1.7
|
||||
- go: tip
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
-4
@@ -1,8 +1,6 @@
|
||||
securecookie
|
||||
============
|
||||
[](https://godoc.org/github.com/gorilla/securecookie) [](https://travis-ci.org/gorilla/securecookie)
|
||||
[](https://sourcegraph.com/github.com/gorilla/securecookie?badge)
|
||||
|
||||
|
||||
securecookie encodes and decodes authenticated and optionally encrypted
|
||||
cookie values.
|
||||
@@ -47,8 +45,6 @@ func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
|
||||
Name: "cookie-name",
|
||||
Value: encoded,
|
||||
Path: "/",
|
||||
Secure: true,
|
||||
HttpOnly: true,
|
||||
}
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
|
||||
+4
-4
@@ -102,7 +102,6 @@ var (
|
||||
errTimestampExpired = cookieError{typ: decodeError, msg: "expired timestamp"}
|
||||
errDecryptionFailed = cookieError{typ: decodeError, msg: "the value could not be decrypted"}
|
||||
errValueNotByte = cookieError{typ: decodeError, msg: "value not a []byte."}
|
||||
errValueNotBytePtr = cookieError{typ: decodeError, msg: "value not a pointer to []byte."}
|
||||
|
||||
// ErrMacInvalid indicates that cookie decoding failed because the HMAC
|
||||
// could not be extracted and verified. Direct use of this error
|
||||
@@ -475,11 +474,12 @@ func (e NopEncoder) Serialize(src interface{}) ([]byte, error) {
|
||||
|
||||
// Deserialize passes a []byte through as-is.
|
||||
func (e NopEncoder) Deserialize(src []byte, dst interface{}) error {
|
||||
if dat, ok := dst.(*[]byte); ok {
|
||||
*dat = src
|
||||
if _, ok := dst.([]byte); ok {
|
||||
dst = src
|
||||
return nil
|
||||
}
|
||||
return errValueNotBytePtr
|
||||
|
||||
return errValueNotByte
|
||||
}
|
||||
|
||||
// Encoding -------------------------------------------------------------------
|
||||
|
||||
-301
@@ -1,301 +0,0 @@
|
||||
// Copyright 2012 The Gorilla 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 securecookie
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Asserts that cookieError and MultiError are Error implementations.
|
||||
var _ Error = cookieError{}
|
||||
var _ Error = MultiError{}
|
||||
|
||||
var testCookies = []interface{}{
|
||||
map[string]string{"foo": "bar"},
|
||||
map[string]string{"baz": "ding"},
|
||||
}
|
||||
|
||||
var testStrings = []string{"foo", "bar", "baz"}
|
||||
|
||||
func TestSecureCookie(t *testing.T) {
|
||||
// TODO test too old / too new timestamps
|
||||
s1 := New([]byte("12345"), []byte("1234567890123456"))
|
||||
s2 := New([]byte("54321"), []byte("6543210987654321"))
|
||||
value := map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"baz": 128,
|
||||
}
|
||||
|
||||
for i := 0; i < 50; i++ {
|
||||
// Running this multiple times to check if any special character
|
||||
// breaks encoding/decoding.
|
||||
encoded, err1 := s1.Encode("sid", value)
|
||||
if err1 != nil {
|
||||
t.Error(err1)
|
||||
continue
|
||||
}
|
||||
dst := make(map[string]interface{})
|
||||
err2 := s1.Decode("sid", encoded, &dst)
|
||||
if err2 != nil {
|
||||
t.Fatalf("%v: %v", err2, encoded)
|
||||
}
|
||||
if !reflect.DeepEqual(dst, value) {
|
||||
t.Fatalf("Expected %v, got %v.", value, dst)
|
||||
}
|
||||
dst2 := make(map[string]interface{})
|
||||
err3 := s2.Decode("sid", encoded, &dst2)
|
||||
if err3 == nil {
|
||||
t.Fatalf("Expected failure decoding.")
|
||||
}
|
||||
err4, ok := err3.(Error)
|
||||
if !ok {
|
||||
t.Fatalf("Expected error to implement Error, got: %#v", err3)
|
||||
}
|
||||
if !err4.IsDecode() {
|
||||
t.Fatalf("Expected DecodeError, got: %#v", err4)
|
||||
}
|
||||
|
||||
// Test other error type flags.
|
||||
if err4.IsUsage() {
|
||||
t.Fatalf("Expected IsUsage() == false, got: %#v", err4)
|
||||
}
|
||||
if err4.IsInternal() {
|
||||
t.Fatalf("Expected IsInternal() == false, got: %#v", err4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecureCookieNilKey(t *testing.T) {
|
||||
s1 := New(nil, nil)
|
||||
value := map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"baz": 128,
|
||||
}
|
||||
_, err := s1.Encode("sid", value)
|
||||
if err != errHashKeyNotSet {
|
||||
t.Fatal("Wrong error returned:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeInvalid(t *testing.T) {
|
||||
// List of invalid cookies, which must not be accepted, base64-decoded
|
||||
// (they will be encoded before passing to Decode).
|
||||
invalidCookies := []string{
|
||||
"",
|
||||
" ",
|
||||
"\n",
|
||||
"||",
|
||||
"|||",
|
||||
"cookie",
|
||||
}
|
||||
s := New([]byte("12345"), nil)
|
||||
var dst string
|
||||
for i, v := range invalidCookies {
|
||||
for _, enc := range []*base64.Encoding{
|
||||
base64.StdEncoding,
|
||||
base64.URLEncoding,
|
||||
} {
|
||||
err := s.Decode("name", enc.EncodeToString([]byte(v)), &dst)
|
||||
if err == nil {
|
||||
t.Fatalf("%d: expected failure decoding", i)
|
||||
}
|
||||
err2, ok := err.(Error)
|
||||
if !ok || !err2.IsDecode() {
|
||||
t.Fatalf("%d: Expected IsDecode(), got: %#v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthentication(t *testing.T) {
|
||||
hash := hmac.New(sha256.New, []byte("secret-key"))
|
||||
for _, value := range testStrings {
|
||||
hash.Reset()
|
||||
signed := createMac(hash, []byte(value))
|
||||
hash.Reset()
|
||||
err := verifyMac(hash, []byte(value), signed)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryption(t *testing.T) {
|
||||
block, err := aes.NewCipher([]byte("1234567890123456"))
|
||||
if err != nil {
|
||||
t.Fatalf("Block could not be created")
|
||||
}
|
||||
var encrypted, decrypted []byte
|
||||
for _, value := range testStrings {
|
||||
if encrypted, err = encrypt(block, []byte(value)); err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
if decrypted, err = decrypt(block, encrypted); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if string(decrypted) != value {
|
||||
t.Errorf("Expected %v, got %v.", value, string(decrypted))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGobSerialization(t *testing.T) {
|
||||
var (
|
||||
sz GobEncoder
|
||||
serialized []byte
|
||||
deserialized map[string]string
|
||||
err error
|
||||
)
|
||||
for _, value := range testCookies {
|
||||
if serialized, err = sz.Serialize(value); err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
deserialized = make(map[string]string)
|
||||
if err = sz.Deserialize(serialized, &deserialized); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fmt.Sprintf("%v", deserialized) != fmt.Sprintf("%v", value) {
|
||||
t.Errorf("Expected %v, got %v.", value, deserialized)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONSerialization(t *testing.T) {
|
||||
var (
|
||||
sz JSONEncoder
|
||||
serialized []byte
|
||||
deserialized map[string]string
|
||||
err error
|
||||
)
|
||||
for _, value := range testCookies {
|
||||
if serialized, err = sz.Serialize(value); err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
deserialized = make(map[string]string)
|
||||
if err = sz.Deserialize(serialized, &deserialized); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if fmt.Sprintf("%v", deserialized) != fmt.Sprintf("%v", value) {
|
||||
t.Errorf("Expected %v, got %v.", value, deserialized)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNopSerialization(t *testing.T) {
|
||||
cookieData := "fooobar123"
|
||||
sz := NopEncoder{}
|
||||
|
||||
if _, err := sz.Serialize(cookieData); err != errValueNotByte {
|
||||
t.Fatal("Expected error passing string")
|
||||
}
|
||||
dat, err := sz.Serialize([]byte(cookieData))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if (string(dat)) != cookieData {
|
||||
t.Fatal("Expected serialized data to be same as source")
|
||||
}
|
||||
|
||||
var dst []byte
|
||||
if err = sz.Deserialize(dat, dst); err != errValueNotBytePtr {
|
||||
t.Fatal("Expect error unless you pass a *[]byte")
|
||||
}
|
||||
if err = sz.Deserialize(dat, &dst); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if (string(dst)) != cookieData {
|
||||
t.Fatal("Expected deserialized data to be same as source")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncoding(t *testing.T) {
|
||||
for _, value := range testStrings {
|
||||
encoded := encode([]byte(value))
|
||||
decoded, err := decode(encoded)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if string(decoded) != value {
|
||||
t.Errorf("Expected %v, got %s.", value, string(decoded))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiError(t *testing.T) {
|
||||
s1, s2 := New(nil, nil), New(nil, nil)
|
||||
_, err := EncodeMulti("sid", "value", s1, s2)
|
||||
if len(err.(MultiError)) != 2 {
|
||||
t.Errorf("Expected 2 errors, got %s.", err)
|
||||
} else {
|
||||
if strings.Index(err.Error(), "hash key is not set") == -1 {
|
||||
t.Errorf("Expected missing hash key error, got %s.", err.Error())
|
||||
}
|
||||
ourErr, ok := err.(Error)
|
||||
if !ok || !ourErr.IsUsage() {
|
||||
t.Fatalf("Expected error to be a usage error; got %#v", err)
|
||||
}
|
||||
if ourErr.IsDecode() {
|
||||
t.Errorf("Expected error NOT to be a decode error; got %#v", ourErr)
|
||||
}
|
||||
if ourErr.IsInternal() {
|
||||
t.Errorf("Expected error NOT to be an internal error; got %#v", ourErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiNoCodecs(t *testing.T) {
|
||||
_, err := EncodeMulti("foo", "bar")
|
||||
if err != errNoCodecs {
|
||||
t.Errorf("EncodeMulti: bad value for error, got: %v", err)
|
||||
}
|
||||
|
||||
var dst []byte
|
||||
err = DecodeMulti("foo", "bar", &dst)
|
||||
if err != errNoCodecs {
|
||||
t.Errorf("DecodeMulti: bad value for error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingKey(t *testing.T) {
|
||||
s1 := New(nil, nil)
|
||||
|
||||
var dst []byte
|
||||
err := s1.Decode("sid", "value", &dst)
|
||||
if err != errHashKeyNotSet {
|
||||
t.Fatalf("Expected %#v, got %#v", errHashKeyNotSet, err)
|
||||
}
|
||||
if err2, ok := err.(Error); !ok || !err2.IsUsage() {
|
||||
t.Errorf("Expected missing hash key to be IsUsage(); was %#v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type FooBar struct {
|
||||
Foo int
|
||||
Bar string
|
||||
}
|
||||
|
||||
func TestCustomType(t *testing.T) {
|
||||
s1 := New([]byte("12345"), []byte("1234567890123456"))
|
||||
// Type is not registered in gob. (!!!)
|
||||
src := &FooBar{42, "bar"}
|
||||
encoded, _ := s1.Encode("sid", src)
|
||||
|
||||
dst := &FooBar{}
|
||||
_ = s1.Decode("sid", encoded, dst)
|
||||
if dst.Foo != 42 || dst.Bar != "bar" {
|
||||
t.Fatalf("Expected %#v, got %#v", src, dst)
|
||||
}
|
||||
}
|
||||
+3
-14
@@ -1,8 +1,6 @@
|
||||
sessions
|
||||
========
|
||||
[](https://godoc.org/github.com/gorilla/sessions) [](https://travis-ci.org/gorilla/sessions)
|
||||
[](https://sourcegraph.com/github.com/gorilla/sessions?badge)
|
||||
|
||||
|
||||
gorilla/sessions provides cookie and filesystem sessions and infrastructure for
|
||||
custom session backends.
|
||||
@@ -44,22 +42,16 @@ Let's start with an example that shows the sessions API in a nutshell:
|
||||
|
||||
First we initialize a session store calling `NewCookieStore()` and passing a
|
||||
secret key used to authenticate the session. Inside the handler, we call
|
||||
`store.Get()` to retrieve an existing session or create a new one. Then we set
|
||||
some session values in session.Values, which is a `map[interface{}]interface{}`.
|
||||
`store.Get()` to retrieve an existing session or a new one. Then we set some
|
||||
session values in session.Values, which is a `map[interface{}]interface{}`.
|
||||
And finally we call `session.Save()` to save the session in the response.
|
||||
|
||||
Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
|
||||
with
|
||||
[`context.ClearHandler`](http://www.gorillatoolkit.org/pkg/context#ClearHandler)
|
||||
or else you will leak memory! An easy way to do this is to wrap the top-level
|
||||
as or else you will leak memory! An easy way to do this is to wrap the top-level
|
||||
mux when calling http.ListenAndServe:
|
||||
|
||||
```go
|
||||
http.ListenAndServe(":8080", context.ClearHandler(http.DefaultServeMux))
|
||||
```
|
||||
|
||||
The ClearHandler function is provided by the gorilla/context package.
|
||||
|
||||
More examples are available [on the Gorilla
|
||||
website](http://www.gorillatoolkit.org/pkg/sessions).
|
||||
|
||||
@@ -71,19 +63,16 @@ Other implementations of the `sessions.Store` interface:
|
||||
* [github.com/yosssi/boltstore](https://github.com/yosssi/boltstore) - Bolt
|
||||
* [github.com/srinathgs/couchbasestore](https://github.com/srinathgs/couchbasestore) - Couchbase
|
||||
* [github.com/denizeren/dynamostore](https://github.com/denizeren/dynamostore) - Dynamodb on AWS
|
||||
* [github.com/savaki/dynastore](https://github.com/savaki/dynastore) - DynamoDB on AWS (Official AWS library)
|
||||
* [github.com/bradleypeabody/gorilla-sessions-memcache](https://github.com/bradleypeabody/gorilla-sessions-memcache) - Memcache
|
||||
* [github.com/dsoprea/go-appengine-sessioncascade](https://github.com/dsoprea/go-appengine-sessioncascade) - Memcache/Datastore/Context in AppEngine
|
||||
* [github.com/kidstuff/mongostore](https://github.com/kidstuff/mongostore) - MongoDB
|
||||
* [github.com/srinathgs/mysqlstore](https://github.com/srinathgs/mysqlstore) - MySQL
|
||||
* [github.com/EnumApps/clustersqlstore](https://github.com/EnumApps/clustersqlstore) - MySQL Cluster
|
||||
* [github.com/antonlindstrom/pgstore](https://github.com/antonlindstrom/pgstore) - PostgreSQL
|
||||
* [github.com/boj/redistore](https://github.com/boj/redistore) - Redis
|
||||
* [github.com/boj/rethinkstore](https://github.com/boj/rethinkstore) - RethinkDB
|
||||
* [github.com/boj/riakstore](https://github.com/boj/riakstore) - Riak
|
||||
* [github.com/michaeljs1990/sqlitestore](https://github.com/michaeljs1990/sqlitestore) - SQLite
|
||||
* [github.com/wader/gormstore](https://github.com/wader/gormstore) - GORM (MySQL, PostgreSQL, SQLite)
|
||||
* [github.com/gernest/qlstore](https://github.com/gernest/qlstore) - ql
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+2
-1
@@ -29,7 +29,8 @@ Let's start with an example that shows the sessions API in a nutshell:
|
||||
var store = sessions.NewCookieStore([]byte("something-very-secret"))
|
||||
|
||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Get a session. Get() always returns a session, even if empty.
|
||||
// Get a session. We're ignoring the error resulted from decoding an
|
||||
// existing session: Get() always returns a session, even if empty.
|
||||
session, err := store.Get(r, "session-name")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
||||
-160
@@ -1,160 +0,0 @@
|
||||
// Copyright 2012 The Gorilla 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 sessions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// NewRecorder returns an initialized ResponseRecorder.
|
||||
func NewRecorder() *httptest.ResponseRecorder {
|
||||
return &httptest.ResponseRecorder{
|
||||
HeaderMap: make(http.Header),
|
||||
Body: new(bytes.Buffer),
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultRemoteAddr is the default remote address to return in RemoteAddr if
|
||||
// an explicit DefaultRemoteAddr isn't set on ResponseRecorder.
|
||||
const DefaultRemoteAddr = "1.2.3.4"
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
type FlashMessage struct {
|
||||
Type int
|
||||
Message string
|
||||
}
|
||||
|
||||
func TestFlashes(t *testing.T) {
|
||||
var req *http.Request
|
||||
var rsp *httptest.ResponseRecorder
|
||||
var hdr http.Header
|
||||
var err error
|
||||
var ok bool
|
||||
var cookies []string
|
||||
var session *Session
|
||||
var flashes []interface{}
|
||||
|
||||
store := NewCookieStore([]byte("secret-key"))
|
||||
|
||||
// Round 1 ----------------------------------------------------------------
|
||||
|
||||
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
rsp = NewRecorder()
|
||||
// Get a session.
|
||||
if session, err = store.Get(req, "session-key"); err != nil {
|
||||
t.Fatalf("Error getting session: %v", err)
|
||||
}
|
||||
// Get a flash.
|
||||
flashes = session.Flashes()
|
||||
if len(flashes) != 0 {
|
||||
t.Errorf("Expected empty flashes; Got %v", flashes)
|
||||
}
|
||||
// Add some flashes.
|
||||
session.AddFlash("foo")
|
||||
session.AddFlash("bar")
|
||||
// Custom key.
|
||||
session.AddFlash("baz", "custom_key")
|
||||
// Save.
|
||||
if err = Save(req, rsp); err != nil {
|
||||
t.Fatalf("Error saving session: %v", err)
|
||||
}
|
||||
hdr = rsp.Header()
|
||||
cookies, ok = hdr["Set-Cookie"]
|
||||
if !ok || len(cookies) != 1 {
|
||||
t.Fatal("No cookies. Header:", hdr)
|
||||
}
|
||||
|
||||
if _, err = store.Get(req, "session:key"); err.Error() != "sessions: invalid character in cookie name: session:key" {
|
||||
t.Fatalf("Expected error due to invalid cookie name")
|
||||
}
|
||||
|
||||
// Round 2 ----------------------------------------------------------------
|
||||
|
||||
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
req.Header.Add("Cookie", cookies[0])
|
||||
rsp = NewRecorder()
|
||||
// Get a session.
|
||||
if session, err = store.Get(req, "session-key"); err != nil {
|
||||
t.Fatalf("Error getting session: %v", err)
|
||||
}
|
||||
// Check all saved values.
|
||||
flashes = session.Flashes()
|
||||
if len(flashes) != 2 {
|
||||
t.Fatalf("Expected flashes; Got %v", flashes)
|
||||
}
|
||||
if flashes[0] != "foo" || flashes[1] != "bar" {
|
||||
t.Errorf("Expected foo,bar; Got %v", flashes)
|
||||
}
|
||||
flashes = session.Flashes()
|
||||
if len(flashes) != 0 {
|
||||
t.Errorf("Expected dumped flashes; Got %v", flashes)
|
||||
}
|
||||
// Custom key.
|
||||
flashes = session.Flashes("custom_key")
|
||||
if len(flashes) != 1 {
|
||||
t.Errorf("Expected flashes; Got %v", flashes)
|
||||
} else if flashes[0] != "baz" {
|
||||
t.Errorf("Expected baz; Got %v", flashes)
|
||||
}
|
||||
flashes = session.Flashes("custom_key")
|
||||
if len(flashes) != 0 {
|
||||
t.Errorf("Expected dumped flashes; Got %v", flashes)
|
||||
}
|
||||
|
||||
// Round 3 ----------------------------------------------------------------
|
||||
// Custom type
|
||||
|
||||
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
rsp = NewRecorder()
|
||||
// Get a session.
|
||||
if session, err = store.Get(req, "session-key"); err != nil {
|
||||
t.Fatalf("Error getting session: %v", err)
|
||||
}
|
||||
// Get a flash.
|
||||
flashes = session.Flashes()
|
||||
if len(flashes) != 0 {
|
||||
t.Errorf("Expected empty flashes; Got %v", flashes)
|
||||
}
|
||||
// Add some flashes.
|
||||
session.AddFlash(&FlashMessage{42, "foo"})
|
||||
// Save.
|
||||
if err = Save(req, rsp); err != nil {
|
||||
t.Fatalf("Error saving session: %v", err)
|
||||
}
|
||||
hdr = rsp.Header()
|
||||
cookies, ok = hdr["Set-Cookie"]
|
||||
if !ok || len(cookies) != 1 {
|
||||
t.Fatal("No cookies. Header:", hdr)
|
||||
}
|
||||
|
||||
// Round 4 ----------------------------------------------------------------
|
||||
// Custom type
|
||||
|
||||
req, _ = http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||
req.Header.Add("Cookie", cookies[0])
|
||||
rsp = NewRecorder()
|
||||
// Get a session.
|
||||
if session, err = store.Get(req, "session-key"); err != nil {
|
||||
t.Fatalf("Error getting session: %v", err)
|
||||
}
|
||||
// Check all saved values.
|
||||
flashes = session.Flashes()
|
||||
if len(flashes) != 1 {
|
||||
t.Fatalf("Expected flashes; Got %v", flashes)
|
||||
}
|
||||
custom := flashes[0].(FlashMessage)
|
||||
if custom.Type != 42 || custom.Message != "foo" {
|
||||
t.Errorf("Expected %#v, got %#v", FlashMessage{42, "foo"}, custom)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
gob.Register(FlashMessage{})
|
||||
}
|
||||
-129
@@ -1,129 +0,0 @@
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test for GH-8 for CookieStore
|
||||
func TestGH8CookieStore(t *testing.T) {
|
||||
originalPath := "/"
|
||||
store := NewCookieStore()
|
||||
store.Options.Path = originalPath
|
||||
req, err := http.NewRequest("GET", "http://www.example.com", nil)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create request", err)
|
||||
}
|
||||
|
||||
session, err := store.New(req, "hello")
|
||||
if err != nil {
|
||||
t.Fatal("failed to create session", err)
|
||||
}
|
||||
|
||||
store.Options.Path = "/foo"
|
||||
if session.Options.Path != originalPath {
|
||||
t.Fatalf("bad session path: got %q, want %q", session.Options.Path, originalPath)
|
||||
}
|
||||
}
|
||||
|
||||
// Test for GH-8 for FilesystemStore
|
||||
func TestGH8FilesystemStore(t *testing.T) {
|
||||
originalPath := "/"
|
||||
store := NewFilesystemStore("")
|
||||
store.Options.Path = originalPath
|
||||
req, err := http.NewRequest("GET", "http://www.example.com", nil)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create request", err)
|
||||
}
|
||||
|
||||
session, err := store.New(req, "hello")
|
||||
if err != nil {
|
||||
t.Fatal("failed to create session", err)
|
||||
}
|
||||
|
||||
store.Options.Path = "/foo"
|
||||
if session.Options.Path != originalPath {
|
||||
t.Fatalf("bad session path: got %q, want %q", session.Options.Path, originalPath)
|
||||
}
|
||||
}
|
||||
|
||||
// Test for GH-2.
|
||||
func TestGH2MaxLength(t *testing.T) {
|
||||
store := NewFilesystemStore("", []byte("some key"))
|
||||
req, err := http.NewRequest("GET", "http://www.example.com", nil)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create request", err)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
session, err := store.New(req, "my session")
|
||||
if err != nil {
|
||||
t.Fatal("failed to create session", err)
|
||||
}
|
||||
|
||||
session.Values["big"] = make([]byte, base64.StdEncoding.DecodedLen(4096*2))
|
||||
err = session.Save(req, w)
|
||||
if err == nil {
|
||||
t.Fatal("expected an error, got nil")
|
||||
}
|
||||
|
||||
store.MaxLength(4096 * 3) // A bit more than the value size to account for encoding overhead.
|
||||
err = session.Save(req, w)
|
||||
if err != nil {
|
||||
t.Fatal("failed to Save:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test delete filesystem store with max-age: -1
|
||||
func TestGH8FilesystemStoreDelete(t *testing.T) {
|
||||
store := NewFilesystemStore("", []byte("some key"))
|
||||
req, err := http.NewRequest("GET", "http://www.example.com", nil)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create request", err)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
session, err := store.New(req, "hello")
|
||||
if err != nil {
|
||||
t.Fatal("failed to create session", err)
|
||||
}
|
||||
|
||||
err = session.Save(req, w)
|
||||
if err != nil {
|
||||
t.Fatal("failed to save session", err)
|
||||
}
|
||||
|
||||
session.Options.MaxAge = -1
|
||||
err = session.Save(req, w)
|
||||
if err != nil {
|
||||
t.Fatal("failed to delete session", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test delete filesystem store with max-age: 0
|
||||
func TestGH8FilesystemStoreDelete2(t *testing.T) {
|
||||
store := NewFilesystemStore("", []byte("some key"))
|
||||
req, err := http.NewRequest("GET", "http://www.example.com", nil)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create request", err)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
session, err := store.New(req, "hello")
|
||||
if err != nil {
|
||||
t.Fatal("failed to create session", err)
|
||||
}
|
||||
|
||||
err = session.Save(req, w)
|
||||
if err != nil {
|
||||
t.Fatal("failed to save session", err)
|
||||
}
|
||||
|
||||
session.Options.MaxAge = 0
|
||||
err = session.Save(req, w)
|
||||
if err != nil {
|
||||
t.Fatal("failed to delete session", err)
|
||||
}
|
||||
}
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
package logutils
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var messages [][]byte
|
||||
|
||||
func init() {
|
||||
messages = [][]byte{
|
||||
[]byte("[TRACE] foo"),
|
||||
[]byte("[DEBUG] foo"),
|
||||
[]byte("[INFO] foo"),
|
||||
[]byte("[WARN] foo"),
|
||||
[]byte("[ERROR] foo"),
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDiscard(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ioutil.Discard.Write(messages[i%len(messages)])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLevelFilter(b *testing.B) {
|
||||
filter := &LevelFilter{
|
||||
Levels: []LogLevel{"TRACE", "DEBUG", "INFO", "WARN", "ERROR"},
|
||||
MinLevel: "WARN",
|
||||
Writer: ioutil.Discard,
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
filter.Write(messages[i%len(messages)])
|
||||
}
|
||||
}
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
package logutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLevelFilter_impl(t *testing.T) {
|
||||
var _ io.Writer = new(LevelFilter)
|
||||
}
|
||||
|
||||
func TestLevelFilter(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
filter := &LevelFilter{
|
||||
Levels: []LogLevel{"DEBUG", "WARN", "ERROR"},
|
||||
MinLevel: "WARN",
|
||||
Writer: buf,
|
||||
}
|
||||
|
||||
logger := log.New(filter, "", 0)
|
||||
logger.Print("[WARN] foo")
|
||||
logger.Println("[ERROR] bar")
|
||||
logger.Println("[DEBUG] baz")
|
||||
logger.Println("[WARN] buzz")
|
||||
|
||||
result := buf.String()
|
||||
expected := "[WARN] foo\n[ERROR] bar\n[WARN] buzz\n"
|
||||
if result != expected {
|
||||
t.Fatalf("bad: %#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLevelFilterCheck(t *testing.T) {
|
||||
filter := &LevelFilter{
|
||||
Levels: []LogLevel{"DEBUG", "WARN", "ERROR"},
|
||||
MinLevel: "WARN",
|
||||
Writer: nil,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
line string
|
||||
check bool
|
||||
}{
|
||||
{"[WARN] foo\n", true},
|
||||
{"[ERROR] bar\n", true},
|
||||
{"[DEBUG] baz\n", false},
|
||||
{"[WARN] buzz\n", true},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
result := filter.Check([]byte(testCase.line))
|
||||
if result != testCase.check {
|
||||
t.Errorf("Fail: %s", testCase.line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLevelFilter_SetMinLevel(t *testing.T) {
|
||||
filter := &LevelFilter{
|
||||
Levels: []LogLevel{"DEBUG", "WARN", "ERROR"},
|
||||
MinLevel: "ERROR",
|
||||
Writer: nil,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
line string
|
||||
checkBefore bool
|
||||
checkAfter bool
|
||||
}{
|
||||
{"[WARN] foo\n", false, true},
|
||||
{"[ERROR] bar\n", true, true},
|
||||
{"[DEBUG] baz\n", false, false},
|
||||
{"[WARN] buzz\n", false, true},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
result := filter.Check([]byte(testCase.line))
|
||||
if result != testCase.checkBefore {
|
||||
t.Errorf("Fail: %s", testCase.line)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the minimum level to WARN
|
||||
filter.SetMinLevel("WARN")
|
||||
|
||||
for _, testCase := range testCases {
|
||||
result := filter.Check([]byte(testCase.line))
|
||||
if result != testCase.checkAfter {
|
||||
t.Errorf("Fail: %s", testCase.line)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.6
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
|
||||
install:
|
||||
# go-flags
|
||||
|
||||
+4
-1
@@ -12,9 +12,12 @@ type Arg struct {
|
||||
// A description of the positional argument (used in the help)
|
||||
Description string
|
||||
|
||||
// Whether a positional argument is required
|
||||
// The minimal number of required positional arguments
|
||||
Required int
|
||||
|
||||
// The maximum number of required positional arguments
|
||||
RequiredMaximum int
|
||||
|
||||
value reflect.Value
|
||||
tag multiTag
|
||||
}
|
||||
|
||||
-133
@@ -1,133 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPositional(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Positional struct {
|
||||
Command int
|
||||
Filename string
|
||||
Rest []string
|
||||
} `positional-args:"yes" required:"yes"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
ret, err := p.ParseArgs([]string{"10", "arg_test.go", "a", "b"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if opts.Positional.Command != 10 {
|
||||
t.Fatalf("Expected opts.Positional.Command to be 10, but got %v", opts.Positional.Command)
|
||||
}
|
||||
|
||||
if opts.Positional.Filename != "arg_test.go" {
|
||||
t.Fatalf("Expected opts.Positional.Filename to be \"arg_test.go\", but got %v", opts.Positional.Filename)
|
||||
}
|
||||
|
||||
assertStringArray(t, opts.Positional.Rest, []string{"a", "b"})
|
||||
assertStringArray(t, ret, []string{})
|
||||
}
|
||||
|
||||
func TestPositionalRequired(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Positional struct {
|
||||
Command int
|
||||
Filename string
|
||||
Rest []string
|
||||
} `positional-args:"yes" required:"yes"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{"10"})
|
||||
|
||||
assertError(t, err, ErrRequired, "the required argument `Filename` was not provided")
|
||||
}
|
||||
|
||||
func TestPositionalRequiredRest1Fail(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Positional struct {
|
||||
Rest []string `required:"yes"`
|
||||
} `positional-args:"yes"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{})
|
||||
|
||||
assertError(t, err, ErrRequired, "the required argument `Rest (at least 1 argument)` was not provided")
|
||||
}
|
||||
|
||||
func TestPositionalRequiredRest1Pass(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Positional struct {
|
||||
Rest []string `required:"yes"`
|
||||
} `positional-args:"yes"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{"rest1"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(opts.Positional.Rest) != 1 {
|
||||
t.Fatalf("Expected 1 positional rest argument")
|
||||
}
|
||||
|
||||
assertString(t, opts.Positional.Rest[0], "rest1")
|
||||
}
|
||||
|
||||
func TestPositionalRequiredRest2Fail(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Positional struct {
|
||||
Rest []string `required:"2"`
|
||||
} `positional-args:"yes"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{"rest1"})
|
||||
|
||||
assertError(t, err, ErrRequired, "the required argument `Rest (at least 2 arguments, but got only 1)` was not provided")
|
||||
}
|
||||
|
||||
func TestPositionalRequiredRest2Pass(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Positional struct {
|
||||
Rest []string `required:"2"`
|
||||
} `positional-args:"yes"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{"rest1", "rest2", "rest3"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(opts.Positional.Rest) != 3 {
|
||||
t.Fatalf("Expected 3 positional rest argument")
|
||||
}
|
||||
|
||||
assertString(t, opts.Positional.Rest[0], "rest1")
|
||||
assertString(t, opts.Positional.Rest[1], "rest2")
|
||||
assertString(t, opts.Positional.Rest[2], "rest3")
|
||||
}
|
||||
-177
@@ -1,177 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assertCallerInfo() (string, int) {
|
||||
ptr := make([]uintptr, 15)
|
||||
n := runtime.Callers(1, ptr)
|
||||
|
||||
if n == 0 {
|
||||
return "", 0
|
||||
}
|
||||
|
||||
mef := runtime.FuncForPC(ptr[0])
|
||||
mefile, meline := mef.FileLine(ptr[0])
|
||||
|
||||
for i := 2; i < n; i++ {
|
||||
f := runtime.FuncForPC(ptr[i])
|
||||
file, line := f.FileLine(ptr[i])
|
||||
|
||||
if file != mefile {
|
||||
return file, line
|
||||
}
|
||||
}
|
||||
|
||||
return mefile, meline
|
||||
}
|
||||
|
||||
func assertErrorf(t *testing.T, format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
|
||||
file, line := assertCallerInfo()
|
||||
|
||||
t.Errorf("%s:%d: %s", path.Base(file), line, msg)
|
||||
}
|
||||
|
||||
func assertFatalf(t *testing.T, format string, args ...interface{}) {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
|
||||
file, line := assertCallerInfo()
|
||||
|
||||
t.Fatalf("%s:%d: %s", path.Base(file), line, msg)
|
||||
}
|
||||
|
||||
func assertString(t *testing.T, a string, b string) {
|
||||
if a != b {
|
||||
assertErrorf(t, "Expected %#v, but got %#v", b, a)
|
||||
}
|
||||
}
|
||||
|
||||
func assertStringArray(t *testing.T, a []string, b []string) {
|
||||
if len(a) != len(b) {
|
||||
assertErrorf(t, "Expected %#v, but got %#v", b, a)
|
||||
return
|
||||
}
|
||||
|
||||
for i, v := range a {
|
||||
if b[i] != v {
|
||||
assertErrorf(t, "Expected %#v, but got %#v", b, a)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertBoolArray(t *testing.T, a []bool, b []bool) {
|
||||
if len(a) != len(b) {
|
||||
assertErrorf(t, "Expected %#v, but got %#v", b, a)
|
||||
return
|
||||
}
|
||||
|
||||
for i, v := range a {
|
||||
if b[i] != v {
|
||||
assertErrorf(t, "Expected %#v, but got %#v", b, a)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertParserSuccess(t *testing.T, data interface{}, args ...string) (*Parser, []string) {
|
||||
parser := NewParser(data, Default&^PrintErrors)
|
||||
ret, err := parser.ParseArgs(args)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected parse error: %s", err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return parser, ret
|
||||
}
|
||||
|
||||
func assertParseSuccess(t *testing.T, data interface{}, args ...string) []string {
|
||||
_, ret := assertParserSuccess(t, data, args...)
|
||||
return ret
|
||||
}
|
||||
|
||||
func assertError(t *testing.T, err error, typ ErrorType, msg string) {
|
||||
if err == nil {
|
||||
assertFatalf(t, "Expected error: %s", msg)
|
||||
return
|
||||
}
|
||||
|
||||
if e, ok := err.(*Error); !ok {
|
||||
assertFatalf(t, "Expected Error type, but got %#v", err)
|
||||
} else {
|
||||
if e.Type != typ {
|
||||
assertErrorf(t, "Expected error type {%s}, but got {%s}", typ, e.Type)
|
||||
}
|
||||
|
||||
if e.Message != msg {
|
||||
assertErrorf(t, "Expected error message %#v, but got %#v", msg, e.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertParseFail(t *testing.T, typ ErrorType, msg string, data interface{}, args ...string) []string {
|
||||
parser := NewParser(data, Default&^PrintErrors)
|
||||
ret, err := parser.ParseArgs(args)
|
||||
|
||||
assertError(t, err, typ, msg)
|
||||
return ret
|
||||
}
|
||||
|
||||
func diff(a, b string) (string, error) {
|
||||
atmp, err := ioutil.TempFile("", "help-diff")
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
btmp, err := ioutil.TempFile("", "help-diff")
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := io.WriteString(atmp, a); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := io.WriteString(btmp, b); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ret, err := exec.Command("diff", "-u", "-d", "--label", "got", atmp.Name(), "--label", "expected", btmp.Name()).Output()
|
||||
|
||||
os.Remove(atmp.Name())
|
||||
os.Remove(btmp.Name())
|
||||
|
||||
if err.Error() == "exit status 1" {
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
return string(ret), err
|
||||
}
|
||||
|
||||
func assertDiff(t *testing.T, actual, expected, msg string) {
|
||||
if actual == expected {
|
||||
return
|
||||
}
|
||||
|
||||
ret, err := diff(actual, expected)
|
||||
|
||||
if err != nil {
|
||||
assertErrorf(t, "Unexpected diff error: %s", err)
|
||||
assertErrorf(t, "Unexpected %s, expected:\n\n%s\n\nbut got\n\n%s", msg, expected, actual)
|
||||
} else {
|
||||
assertErrorf(t, "Unexpected %s:\n\n%s", msg, ret)
|
||||
}
|
||||
}
|
||||
+20
-6
@@ -181,22 +181,36 @@ func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler {
|
||||
name = field.Name
|
||||
}
|
||||
|
||||
var required int
|
||||
required := -1
|
||||
requiredMaximum := -1
|
||||
|
||||
sreq := m.Get("required")
|
||||
|
||||
if sreq != "" {
|
||||
required = 1
|
||||
|
||||
if preq, err := strconv.ParseInt(sreq, 10, 32); err == nil {
|
||||
required = int(preq)
|
||||
rng := strings.SplitN(sreq, "-", 2)
|
||||
|
||||
if len(rng) > 1 {
|
||||
if preq, err := strconv.ParseInt(rng[0], 10, 32); err == nil {
|
||||
required = int(preq)
|
||||
}
|
||||
|
||||
if preq, err := strconv.ParseInt(rng[1], 10, 32); err == nil {
|
||||
requiredMaximum = int(preq)
|
||||
}
|
||||
} else {
|
||||
if preq, err := strconv.ParseInt(sreq, 10, 32); err == nil {
|
||||
required = int(preq)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
arg := &Arg{
|
||||
Name: name,
|
||||
Description: m.Get("description"),
|
||||
Required: required,
|
||||
Name: name,
|
||||
Description: m.Get("description"),
|
||||
Required: required,
|
||||
RequiredMaximum: requiredMaximum,
|
||||
|
||||
value: realval.Field(i),
|
||||
tag: m,
|
||||
|
||||
-582
@@ -1,582 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommandInline(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
G bool `short:"g"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
p, ret := assertParserSuccess(t, &opts, "-v", "cmd", "-g")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if p.Active == nil {
|
||||
t.Errorf("Expected active command")
|
||||
}
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.Command.G {
|
||||
t.Errorf("Expected Command.G to be true")
|
||||
}
|
||||
|
||||
if p.Command.Find("cmd") != p.Active {
|
||||
t.Errorf("Expected to find command `cmd' to be active")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandInlineMulti(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
C1 struct {
|
||||
} `command:"c1"`
|
||||
|
||||
C2 struct {
|
||||
G bool `short:"g"`
|
||||
} `command:"c2"`
|
||||
}{}
|
||||
|
||||
p, ret := assertParserSuccess(t, &opts, "-v", "c2", "-g")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if p.Active == nil {
|
||||
t.Errorf("Expected active command")
|
||||
}
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.C2.G {
|
||||
t.Errorf("Expected C2.G to be true")
|
||||
}
|
||||
|
||||
if p.Command.Find("c1") == nil {
|
||||
t.Errorf("Expected to find command `c1'")
|
||||
}
|
||||
|
||||
if c2 := p.Command.Find("c2"); c2 == nil {
|
||||
t.Errorf("Expected to find command `c2'")
|
||||
} else if c2 != p.Active {
|
||||
t.Errorf("Expected to find command `c2' to be active")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandFlagOrder1(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
G bool `short:"g"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrUnknownFlag, "unknown flag `g'", &opts, "-v", "-g", "cmd")
|
||||
}
|
||||
|
||||
func TestCommandFlagOrder2(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
G bool `short:"g"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cmd", "-v", "-g")
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.Command.G {
|
||||
t.Errorf("Expected Command.G to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandFlagOrderSub(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
G bool `short:"g"`
|
||||
|
||||
SubCommand struct {
|
||||
B bool `short:"b"`
|
||||
} `command:"sub"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cmd", "sub", "-v", "-g", "-b")
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.Command.G {
|
||||
t.Errorf("Expected Command.G to be true")
|
||||
}
|
||||
|
||||
if !opts.Command.SubCommand.B {
|
||||
t.Errorf("Expected Command.SubCommand.B to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandFlagOverride1(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
Value bool `short:"v"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "-v", "cmd")
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if opts.Command.Value {
|
||||
t.Errorf("Expected Command.Value to be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandFlagOverride2(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
Value bool `short:"v"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cmd", "-v")
|
||||
|
||||
if opts.Value {
|
||||
t.Errorf("Expected Value to be false")
|
||||
}
|
||||
|
||||
if !opts.Command.Value {
|
||||
t.Errorf("Expected Command.Value to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandFlagOverrideSub(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
SubCommand struct {
|
||||
Value bool `short:"v"`
|
||||
} `command:"sub"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cmd", "sub", "-v")
|
||||
|
||||
if opts.Value {
|
||||
t.Errorf("Expected Value to be false")
|
||||
}
|
||||
|
||||
if opts.Command.Value {
|
||||
t.Errorf("Expected Command.Value to be false")
|
||||
}
|
||||
|
||||
if !opts.Command.SubCommand.Value {
|
||||
t.Errorf("Expected Command.Value to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandFlagOverrideSub2(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
SubCommand struct {
|
||||
G bool `short:"g"`
|
||||
} `command:"sub"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cmd", "sub", "-v")
|
||||
|
||||
if opts.Value {
|
||||
t.Errorf("Expected Value to be false")
|
||||
}
|
||||
|
||||
if !opts.Command.Value {
|
||||
t.Errorf("Expected Command.Value to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandEstimate(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Cmd1 struct {
|
||||
} `command:"remove"`
|
||||
|
||||
Cmd2 struct {
|
||||
} `command:"add"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{})
|
||||
|
||||
assertError(t, err, ErrCommandRequired, "Please specify one command of: add or remove")
|
||||
}
|
||||
|
||||
func TestCommandEstimate2(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Cmd1 struct {
|
||||
} `command:"remove"`
|
||||
|
||||
Cmd2 struct {
|
||||
} `command:"add"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
_, err := p.ParseArgs([]string{"rmive"})
|
||||
|
||||
assertError(t, err, ErrUnknownCommand, "Unknown command `rmive', did you mean `remove'?")
|
||||
}
|
||||
|
||||
type testCommand struct {
|
||||
G bool `short:"g"`
|
||||
Executed bool
|
||||
EArgs []string
|
||||
}
|
||||
|
||||
func (c *testCommand) Execute(args []string) error {
|
||||
c.Executed = true
|
||||
c.EArgs = args
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCommandExecute(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command testCommand `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "-v", "cmd", "-g", "a", "b")
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.Command.Executed {
|
||||
t.Errorf("Did not execute command")
|
||||
}
|
||||
|
||||
if !opts.Command.G {
|
||||
t.Errorf("Expected Command.C to be true")
|
||||
}
|
||||
|
||||
assertStringArray(t, opts.Command.EArgs, []string{"a", "b"})
|
||||
}
|
||||
|
||||
func TestCommandClosest(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Cmd1 struct {
|
||||
} `command:"remove"`
|
||||
|
||||
Cmd2 struct {
|
||||
} `command:"add"`
|
||||
}{}
|
||||
|
||||
args := assertParseFail(t, ErrUnknownCommand, "Unknown command `addd', did you mean `add'?", &opts, "-v", "addd")
|
||||
|
||||
assertStringArray(t, args, []string{"addd"})
|
||||
}
|
||||
|
||||
func TestCommandAdd(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
}{}
|
||||
|
||||
var cmd = struct {
|
||||
G bool `short:"g"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
c, err := p.AddCommand("cmd", "", "", &cmd)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
ret, err := p.ParseArgs([]string{"-v", "cmd", "-g", "rest"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
assertStringArray(t, ret, []string{"rest"})
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !cmd.G {
|
||||
t.Errorf("Expected Command.G to be true")
|
||||
}
|
||||
|
||||
if p.Command.Find("cmd") != c {
|
||||
t.Errorf("Expected to find command `cmd'")
|
||||
}
|
||||
|
||||
if p.Commands()[0] != c {
|
||||
t.Errorf("Expected command %#v, but got %#v", c, p.Commands()[0])
|
||||
}
|
||||
|
||||
if c.Options()[0].ShortName != 'g' {
|
||||
t.Errorf("Expected short name `g' but got %v", c.Options()[0].ShortName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandNestedInline(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Command struct {
|
||||
G bool `short:"g"`
|
||||
|
||||
Nested struct {
|
||||
N string `long:"n"`
|
||||
} `command:"nested"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
p, ret := assertParserSuccess(t, &opts, "-v", "cmd", "-g", "nested", "--n", "n", "rest")
|
||||
|
||||
assertStringArray(t, ret, []string{"rest"})
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.Command.G {
|
||||
t.Errorf("Expected Command.G to be true")
|
||||
}
|
||||
|
||||
assertString(t, opts.Command.Nested.N, "n")
|
||||
|
||||
if c := p.Command.Find("cmd"); c == nil {
|
||||
t.Errorf("Expected to find command `cmd'")
|
||||
} else {
|
||||
if c != p.Active {
|
||||
t.Errorf("Expected `cmd' to be the active parser command")
|
||||
}
|
||||
|
||||
if nested := c.Find("nested"); nested == nil {
|
||||
t.Errorf("Expected to find command `nested'")
|
||||
} else if nested != c.Active {
|
||||
t.Errorf("Expected to find command `nested' to be the active `cmd' command")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequiredOnCommand(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v" required:"true"`
|
||||
|
||||
Command struct {
|
||||
G bool `short:"g"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrRequired, fmt.Sprintf("the required flag `%cv' was not specified", defaultShortOptDelimiter), &opts, "cmd")
|
||||
}
|
||||
|
||||
func TestRequiredAllOnCommand(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v" required:"true"`
|
||||
Missing bool `long:"missing" required:"true"`
|
||||
|
||||
Command struct {
|
||||
G bool `short:"g"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrRequired, fmt.Sprintf("the required flags `%smissing' and `%cv' were not specified", defaultLongOptDelimiter, defaultShortOptDelimiter), &opts, "cmd")
|
||||
}
|
||||
|
||||
func TestDefaultOnCommand(t *testing.T) {
|
||||
var opts = struct {
|
||||
Command struct {
|
||||
G string `short:"g" default:"value"`
|
||||
} `command:"cmd"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cmd")
|
||||
|
||||
if opts.Command.G != "value" {
|
||||
t.Errorf("Expected G to be \"value\"")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAfterNonCommand(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Cmd1 struct {
|
||||
} `command:"remove"`
|
||||
|
||||
Cmd2 struct {
|
||||
} `command:"add"`
|
||||
}{}
|
||||
|
||||
assertParseFail(t, ErrUnknownCommand, "Unknown command `nocmd'. Please specify one command of: add or remove", &opts, "nocmd", "remove")
|
||||
}
|
||||
|
||||
func TestSubcommandsOptional(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Cmd1 struct {
|
||||
} `command:"remove"`
|
||||
|
||||
Cmd2 struct {
|
||||
} `command:"add"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
p.SubcommandsOptional = true
|
||||
|
||||
_, err := p.ParseArgs([]string{"-v"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubcommandsOptionalAfterNonCommand(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Cmd1 struct {
|
||||
} `command:"remove"`
|
||||
|
||||
Cmd2 struct {
|
||||
} `command:"add"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, None)
|
||||
p.SubcommandsOptional = true
|
||||
|
||||
retargs, err := p.ParseArgs([]string{"nocmd", "remove"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
assertStringArray(t, retargs, []string{"nocmd", "remove"})
|
||||
}
|
||||
|
||||
func TestCommandAlias(t *testing.T) {
|
||||
var opts = struct {
|
||||
Command struct {
|
||||
G string `short:"g" default:"value"`
|
||||
} `command:"cmd" alias:"cm"`
|
||||
}{}
|
||||
|
||||
assertParseSuccess(t, &opts, "cm")
|
||||
|
||||
if opts.Command.G != "value" {
|
||||
t.Errorf("Expected G to be \"value\"")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubCommandFindOptionByLongFlag(t *testing.T) {
|
||||
var opts struct {
|
||||
Testing bool `long:"testing" description:"Testing"`
|
||||
}
|
||||
|
||||
var cmd struct {
|
||||
Other bool `long:"other" description:"Other"`
|
||||
}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
c, _ := p.AddCommand("command", "Short", "Long", &cmd)
|
||||
|
||||
opt := c.FindOptionByLongName("other")
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
assertString(t, opt.LongName, "other")
|
||||
|
||||
opt = c.FindOptionByLongName("testing")
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
assertString(t, opt.LongName, "testing")
|
||||
}
|
||||
|
||||
func TestSubCommandFindOptionByShortFlag(t *testing.T) {
|
||||
var opts struct {
|
||||
Testing bool `short:"t" description:"Testing"`
|
||||
}
|
||||
|
||||
var cmd struct {
|
||||
Other bool `short:"o" description:"Other"`
|
||||
}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
c, _ := p.AddCommand("command", "Short", "Long", &cmd)
|
||||
|
||||
opt := c.FindOptionByShortName('o')
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
if opt.ShortName != 'o' {
|
||||
t.Errorf("Expected 'o', but got %v", opt.ShortName)
|
||||
}
|
||||
|
||||
opt = c.FindOptionByShortName('t')
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
if opt.ShortName != 't' {
|
||||
t.Errorf("Expected 'o', but got %v", opt.ShortName)
|
||||
}
|
||||
}
|
||||
+45
-36
@@ -73,27 +73,8 @@ func (c *completion) skipPositional(s *parseState, n int) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *completion) completeOptionNames(names map[string]*Option, prefix string, match string) []Completion {
|
||||
n := make([]Completion, 0, len(names))
|
||||
|
||||
for k, opt := range names {
|
||||
if strings.HasPrefix(k, match) && !opt.Hidden {
|
||||
n = append(n, Completion{
|
||||
Item: prefix + k,
|
||||
Description: opt.Description,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func (c *completion) completeLongNames(s *parseState, prefix string, match string) []Completion {
|
||||
return c.completeOptionNames(s.lookup.longNames, prefix, match)
|
||||
}
|
||||
|
||||
func (c *completion) completeShortNames(s *parseState, prefix string, match string) []Completion {
|
||||
if len(match) != 0 {
|
||||
func (c *completion) completeOptionNames(s *parseState, prefix string, match string, short bool) []Completion {
|
||||
if short && len(match) != 0 {
|
||||
return []Completion{
|
||||
Completion{
|
||||
Item: prefix + match,
|
||||
@@ -101,7 +82,42 @@ func (c *completion) completeShortNames(s *parseState, prefix string, match stri
|
||||
}
|
||||
}
|
||||
|
||||
return c.completeOptionNames(s.lookup.shortNames, prefix, match)
|
||||
var results []Completion
|
||||
repeats := map[string]bool{}
|
||||
|
||||
for name, opt := range s.lookup.longNames {
|
||||
if strings.HasPrefix(name, match) && !opt.Hidden {
|
||||
results = append(results, Completion{
|
||||
Item: defaultLongOptDelimiter + name,
|
||||
Description: opt.Description,
|
||||
})
|
||||
|
||||
if short {
|
||||
repeats[string(opt.ShortName)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if short {
|
||||
for name, opt := range s.lookup.shortNames {
|
||||
if _, exist := repeats[name]; !exist && strings.HasPrefix(name, match) && !opt.Hidden {
|
||||
results = append(results, Completion{
|
||||
Item: string(defaultShortOptDelimiter) + name,
|
||||
Description: opt.Description,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func (c *completion) completeNamesForLongPrefix(s *parseState, prefix string, match string) []Completion {
|
||||
return c.completeOptionNames(s, prefix, match, false)
|
||||
}
|
||||
|
||||
func (c *completion) completeNamesForShortPrefix(s *parseState, prefix string, match string) []Completion {
|
||||
return c.completeOptionNames(s, prefix, match, true)
|
||||
}
|
||||
|
||||
func (c *completion) completeCommands(s *parseState, match string) []Completion {
|
||||
@@ -120,6 +136,9 @@ func (c *completion) completeCommands(s *parseState, match string) []Completion
|
||||
}
|
||||
|
||||
func (c *completion) completeValue(value reflect.Value, prefix string, match string) []Completion {
|
||||
if value.Kind() == reflect.Slice {
|
||||
value = reflect.New(value.Type().Elem())
|
||||
}
|
||||
i := value.Interface()
|
||||
|
||||
var ret []Completion
|
||||
@@ -139,16 +158,6 @@ func (c *completion) completeValue(value reflect.Value, prefix string, match str
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *completion) completeArg(arg *Arg, prefix string, match string) []Completion {
|
||||
if arg.isRemaining() {
|
||||
// For remaining positional args (that are parsed into a slice), complete
|
||||
// based on the element type.
|
||||
return c.completeValue(reflect.New(arg.value.Type().Elem()), prefix, match)
|
||||
}
|
||||
|
||||
return c.completeValue(arg.value, prefix, match)
|
||||
}
|
||||
|
||||
func (c *completion) complete(args []string) []Completion {
|
||||
if len(args) == 0 {
|
||||
args = []string{""}
|
||||
@@ -244,7 +253,7 @@ func (c *completion) complete(args []string) []Completion {
|
||||
if opt := s.lookup.shortNames[sname]; opt != nil && opt.canArgument() {
|
||||
ret = c.completeValue(opt.value, prefix+sname, optname[n:])
|
||||
} else {
|
||||
ret = c.completeShortNames(s, prefix, optname)
|
||||
ret = c.completeNamesForShortPrefix(s, prefix, optname)
|
||||
}
|
||||
} else if argument != nil {
|
||||
if islong {
|
||||
@@ -257,13 +266,13 @@ func (c *completion) complete(args []string) []Completion {
|
||||
ret = c.completeValue(opt.value, prefix+optname+split, *argument)
|
||||
}
|
||||
} else if islong {
|
||||
ret = c.completeLongNames(s, prefix, optname)
|
||||
ret = c.completeNamesForLongPrefix(s, prefix, optname)
|
||||
} else {
|
||||
ret = c.completeShortNames(s, prefix, optname)
|
||||
ret = c.completeNamesForShortPrefix(s, prefix, optname)
|
||||
}
|
||||
} else if len(s.positional) > 0 {
|
||||
// Complete for positional argument
|
||||
ret = c.completeArg(s.positional[0], "", lastarg)
|
||||
ret = c.completeValue(s.positional[0].value, "", lastarg)
|
||||
} else if len(s.command.commands) > 0 {
|
||||
// Complete for command
|
||||
ret = c.completeCommands(s, lastarg)
|
||||
|
||||
-295
@@ -1,295 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type TestComplete struct {
|
||||
}
|
||||
|
||||
func (t *TestComplete) Complete(match string) []Completion {
|
||||
options := []string{
|
||||
"hello world",
|
||||
"hello universe",
|
||||
"hello multiverse",
|
||||
}
|
||||
|
||||
ret := make([]Completion, 0, len(options))
|
||||
|
||||
for _, o := range options {
|
||||
if strings.HasPrefix(o, match) {
|
||||
ret = append(ret, Completion{
|
||||
Item: o,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
var completionTestOptions struct {
|
||||
Verbose bool `short:"v" long:"verbose" description:"Verbose messages"`
|
||||
Debug bool `short:"d" long:"debug" description:"Enable debug"`
|
||||
Version bool `long:"version" description:"Show version"`
|
||||
Required bool `long:"required" required:"true" description:"This is required"`
|
||||
Hidden bool `long:"hidden" hidden:"true" description:"This is hidden"`
|
||||
|
||||
AddCommand struct {
|
||||
Positional struct {
|
||||
Filename Filename
|
||||
} `positional-args:"yes"`
|
||||
} `command:"add" description:"add an item"`
|
||||
|
||||
AddMultiCommand struct {
|
||||
Positional struct {
|
||||
Filename []Filename
|
||||
} `positional-args:"yes"`
|
||||
} `command:"add-multi" description:"add multiple items"`
|
||||
|
||||
RemoveCommand struct {
|
||||
Other bool `short:"o"`
|
||||
File Filename `short:"f" long:"filename"`
|
||||
} `command:"rm" description:"remove an item"`
|
||||
|
||||
RenameCommand struct {
|
||||
Completed TestComplete `short:"c" long:"completed"`
|
||||
} `command:"rename" description:"rename an item"`
|
||||
}
|
||||
|
||||
type completionTest struct {
|
||||
Args []string
|
||||
Completed []string
|
||||
ShowDescriptions bool
|
||||
}
|
||||
|
||||
var completionTests []completionTest
|
||||
|
||||
func init() {
|
||||
_, sourcefile, _, _ := runtime.Caller(0)
|
||||
completionTestSourcedir := filepath.Join(filepath.SplitList(path.Dir(sourcefile))...)
|
||||
|
||||
completionTestFilename := []string{filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion_test.go")}
|
||||
|
||||
completionTests = []completionTest{
|
||||
{
|
||||
// Short names
|
||||
[]string{"-"},
|
||||
[]string{"-d", "-v"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Short names concatenated
|
||||
[]string{"-dv"},
|
||||
[]string{"-dv"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Long names
|
||||
[]string{"--"},
|
||||
[]string{"--debug", "--required", "--verbose", "--version"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Long names with descriptions
|
||||
[]string{"--"},
|
||||
[]string{
|
||||
"--debug # Enable debug",
|
||||
"--required # This is required",
|
||||
"--verbose # Verbose messages",
|
||||
"--version # Show version",
|
||||
},
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
// Long names partial
|
||||
[]string{"--ver"},
|
||||
[]string{"--verbose", "--version"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Commands
|
||||
[]string{""},
|
||||
[]string{"add", "add-multi", "rename", "rm"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Commands with descriptions
|
||||
[]string{""},
|
||||
[]string{
|
||||
"add # add an item",
|
||||
"add-multi # add multiple items",
|
||||
"rename # rename an item",
|
||||
"rm # remove an item",
|
||||
},
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
// Commands partial
|
||||
[]string{"r"},
|
||||
[]string{"rename", "rm"},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Positional filename
|
||||
[]string{"add", filepath.Join(completionTestSourcedir, "completion")},
|
||||
completionTestFilename,
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Multiple positional filename (1 arg)
|
||||
[]string{"add-multi", filepath.Join(completionTestSourcedir, "completion")},
|
||||
completionTestFilename,
|
||||
false,
|
||||
},
|
||||
{
|
||||
// Multiple positional filename (2 args)
|
||||
[]string{"add-multi", filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion")},
|
||||
completionTestFilename,
|
||||
false,
|
||||
},
|
||||
{
|
||||
// Multiple positional filename (3 args)
|
||||
[]string{"add-multi", filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion")},
|
||||
completionTestFilename,
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Flag filename
|
||||
[]string{"rm", "-f", path.Join(completionTestSourcedir, "completion")},
|
||||
completionTestFilename,
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Flag short concat last filename
|
||||
[]string{"rm", "-of", path.Join(completionTestSourcedir, "completion")},
|
||||
completionTestFilename,
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Flag concat filename
|
||||
[]string{"rm", "-f" + path.Join(completionTestSourcedir, "completion")},
|
||||
[]string{"-f" + completionTestFilename[0], "-f" + completionTestFilename[1]},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Flag equal concat filename
|
||||
[]string{"rm", "-f=" + path.Join(completionTestSourcedir, "completion")},
|
||||
[]string{"-f=" + completionTestFilename[0], "-f=" + completionTestFilename[1]},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Flag concat long filename
|
||||
[]string{"rm", "--filename=" + path.Join(completionTestSourcedir, "completion")},
|
||||
[]string{"--filename=" + completionTestFilename[0], "--filename=" + completionTestFilename[1]},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Flag long filename
|
||||
[]string{"rm", "--filename", path.Join(completionTestSourcedir, "completion")},
|
||||
completionTestFilename,
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
// Custom completed
|
||||
[]string{"rename", "-c", "hello un"},
|
||||
[]string{"hello universe"},
|
||||
false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompletion(t *testing.T) {
|
||||
p := NewParser(&completionTestOptions, Default)
|
||||
c := &completion{parser: p}
|
||||
|
||||
for _, test := range completionTests {
|
||||
if test.ShowDescriptions {
|
||||
continue
|
||||
}
|
||||
|
||||
ret := c.complete(test.Args)
|
||||
items := make([]string, len(ret))
|
||||
|
||||
for i, v := range ret {
|
||||
items[i] = v.Item
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(items, test.Completed) {
|
||||
t.Errorf("Args: %#v, %#v\n Expected: %#v\n Got: %#v", test.Args, test.ShowDescriptions, test.Completed, items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserCompletion(t *testing.T) {
|
||||
for _, test := range completionTests {
|
||||
if test.ShowDescriptions {
|
||||
os.Setenv("GO_FLAGS_COMPLETION", "verbose")
|
||||
} else {
|
||||
os.Setenv("GO_FLAGS_COMPLETION", "1")
|
||||
}
|
||||
|
||||
tmp := os.Stdout
|
||||
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
out := make(chan string)
|
||||
|
||||
go func() {
|
||||
var buf bytes.Buffer
|
||||
|
||||
io.Copy(&buf, r)
|
||||
|
||||
out <- buf.String()
|
||||
}()
|
||||
|
||||
p := NewParser(&completionTestOptions, None)
|
||||
|
||||
p.CompletionHandler = func(items []Completion) {
|
||||
comp := &completion{parser: p}
|
||||
comp.print(items, test.ShowDescriptions)
|
||||
}
|
||||
|
||||
_, err := p.ParseArgs(test.Args)
|
||||
|
||||
w.Close()
|
||||
|
||||
os.Stdout = tmp
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
|
||||
got := strings.Split(strings.Trim(<-out, "\n"), "\n")
|
||||
|
||||
if !reflect.DeepEqual(got, test.Completed) {
|
||||
t.Errorf("Expected: %#v\nGot: %#v", test.Completed, got)
|
||||
}
|
||||
}
|
||||
|
||||
os.Setenv("GO_FLAGS_COMPLETION", "")
|
||||
}
|
||||
+7
@@ -154,6 +154,13 @@ func convertToString(val reflect.Value, options multiTag) (string, error) {
|
||||
func convertUnmarshal(val string, retval reflect.Value) (bool, error) {
|
||||
if retval.Type().NumMethod() > 0 && retval.CanInterface() {
|
||||
if unmarshaler, ok := retval.Interface().(Unmarshaler); ok {
|
||||
if retval.IsNil() {
|
||||
retval.Set(reflect.New(retval.Type().Elem()))
|
||||
|
||||
// Re-assign from the new value
|
||||
unmarshaler = retval.Interface().(Unmarshaler)
|
||||
}
|
||||
|
||||
return true, unmarshaler.UnmarshalFlag(val)
|
||||
}
|
||||
}
|
||||
|
||||
-159
@@ -1,159 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func expectConvert(t *testing.T, o *Option, expected string) {
|
||||
s, err := convertToString(o.value, o.tag)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
assertString(t, s, expected)
|
||||
}
|
||||
|
||||
func TestConvertToString(t *testing.T) {
|
||||
d, _ := time.ParseDuration("1h2m4s")
|
||||
|
||||
var opts = struct {
|
||||
String string `long:"string"`
|
||||
|
||||
Int int `long:"int"`
|
||||
Int8 int8 `long:"int8"`
|
||||
Int16 int16 `long:"int16"`
|
||||
Int32 int32 `long:"int32"`
|
||||
Int64 int64 `long:"int64"`
|
||||
|
||||
Uint uint `long:"uint"`
|
||||
Uint8 uint8 `long:"uint8"`
|
||||
Uint16 uint16 `long:"uint16"`
|
||||
Uint32 uint32 `long:"uint32"`
|
||||
Uint64 uint64 `long:"uint64"`
|
||||
|
||||
Float32 float32 `long:"float32"`
|
||||
Float64 float64 `long:"float64"`
|
||||
|
||||
Duration time.Duration `long:"duration"`
|
||||
|
||||
Bool bool `long:"bool"`
|
||||
|
||||
IntSlice []int `long:"int-slice"`
|
||||
IntFloatMap map[int]float64 `long:"int-float-map"`
|
||||
|
||||
PtrBool *bool `long:"ptr-bool"`
|
||||
Interface interface{} `long:"interface"`
|
||||
|
||||
Int32Base int32 `long:"int32-base" base:"16"`
|
||||
Uint32Base uint32 `long:"uint32-base" base:"16"`
|
||||
}{
|
||||
"string",
|
||||
|
||||
-2,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
|
||||
1.2,
|
||||
-3.4,
|
||||
|
||||
d,
|
||||
true,
|
||||
|
||||
[]int{-3, 4, -2},
|
||||
map[int]float64{-2: 4.5},
|
||||
|
||||
new(bool),
|
||||
float32(5.2),
|
||||
|
||||
-5823,
|
||||
4232,
|
||||
}
|
||||
|
||||
p := NewNamedParser("test", Default)
|
||||
grp, _ := p.AddGroup("test group", "", &opts)
|
||||
|
||||
expects := []string{
|
||||
"string",
|
||||
"-2",
|
||||
"-1",
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
|
||||
"1.2",
|
||||
"-3.4",
|
||||
|
||||
"1h2m4s",
|
||||
"true",
|
||||
|
||||
"[-3, 4, -2]",
|
||||
"{-2:4.5}",
|
||||
|
||||
"false",
|
||||
"5.2",
|
||||
|
||||
"-16bf",
|
||||
"1088",
|
||||
}
|
||||
|
||||
for i, v := range grp.Options() {
|
||||
expectConvert(t, v, expects[i])
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertToStringInvalidIntBase(t *testing.T) {
|
||||
var opts = struct {
|
||||
Int int `long:"int" base:"no"`
|
||||
}{
|
||||
2,
|
||||
}
|
||||
|
||||
p := NewNamedParser("test", Default)
|
||||
grp, _ := p.AddGroup("test group", "", &opts)
|
||||
o := grp.Options()[0]
|
||||
|
||||
_, err := convertToString(o.value, o.tag)
|
||||
|
||||
if err != nil {
|
||||
err = newErrorf(ErrMarshal, "%v", err)
|
||||
}
|
||||
|
||||
assertError(t, err, ErrMarshal, "strconv.ParseInt: parsing \"no\": invalid syntax")
|
||||
}
|
||||
|
||||
func TestConvertToStringInvalidUintBase(t *testing.T) {
|
||||
var opts = struct {
|
||||
Uint uint `long:"uint" base:"no"`
|
||||
}{
|
||||
2,
|
||||
}
|
||||
|
||||
p := NewNamedParser("test", Default)
|
||||
grp, _ := p.AddGroup("test group", "", &opts)
|
||||
o := grp.Options()[0]
|
||||
|
||||
_, err := convertToString(o.value, o.tag)
|
||||
|
||||
if err != nil {
|
||||
err = newErrorf(ErrMarshal, "%v", err)
|
||||
}
|
||||
|
||||
assertError(t, err, ErrMarshal, "strconv.ParseInt: parsing \"no\": invalid syntax")
|
||||
}
|
||||
-110
@@ -1,110 +0,0 @@
|
||||
// Example of use of the flags package.
|
||||
package flags
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
var opts struct {
|
||||
// Slice of bool will append 'true' each time the option
|
||||
// is encountered (can be set multiple times, like -vvv)
|
||||
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
|
||||
|
||||
// Example of automatic marshalling to desired type (uint)
|
||||
Offset uint `long:"offset" description:"Offset"`
|
||||
|
||||
// Example of a callback, called each time the option is found.
|
||||
Call func(string) `short:"c" description:"Call phone number"`
|
||||
|
||||
// Example of a required flag
|
||||
Name string `short:"n" long:"name" description:"A name" required:"true"`
|
||||
|
||||
// Example of a value name
|
||||
File string `short:"f" long:"file" description:"A file" value-name:"FILE"`
|
||||
|
||||
// Example of a pointer
|
||||
Ptr *int `short:"p" description:"A pointer to an integer"`
|
||||
|
||||
// Example of a slice of strings
|
||||
StringSlice []string `short:"s" description:"A slice of strings"`
|
||||
|
||||
// Example of a slice of pointers
|
||||
PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"`
|
||||
|
||||
// Example of a map
|
||||
IntMap map[string]int `long:"intmap" description:"A map from string to int"`
|
||||
|
||||
// Example of a filename (useful for completion)
|
||||
Filename Filename `long:"filename" description:"A filename"`
|
||||
|
||||
// Example of positional arguments
|
||||
Args struct {
|
||||
ID string
|
||||
Num int
|
||||
Rest []string
|
||||
} `positional-args:"yes" required:"yes"`
|
||||
}
|
||||
|
||||
// Callback which will invoke callto:<argument> to call a number.
|
||||
// Note that this works just on OS X (and probably only with
|
||||
// Skype) but it shows the idea.
|
||||
opts.Call = func(num string) {
|
||||
cmd := exec.Command("open", "callto:"+num)
|
||||
cmd.Start()
|
||||
cmd.Process.Release()
|
||||
}
|
||||
|
||||
// Make some fake arguments to parse.
|
||||
args := []string{
|
||||
"-vv",
|
||||
"--offset=5",
|
||||
"-n", "Me",
|
||||
"-p", "3",
|
||||
"-s", "hello",
|
||||
"-s", "world",
|
||||
"--ptrslice", "hello",
|
||||
"--ptrslice", "world",
|
||||
"--intmap", "a:1",
|
||||
"--intmap", "b:5",
|
||||
"--filename", "hello.go",
|
||||
"id",
|
||||
"10",
|
||||
"remaining1",
|
||||
"remaining2",
|
||||
}
|
||||
|
||||
// Parse flags from `args'. Note that here we use flags.ParseArgs for
|
||||
// the sake of making a working example. Normally, you would simply use
|
||||
// flags.Parse(&opts) which uses os.Args
|
||||
_, err := ParseArgs(&opts, args)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Verbosity: %v\n", opts.Verbose)
|
||||
fmt.Printf("Offset: %d\n", opts.Offset)
|
||||
fmt.Printf("Name: %s\n", opts.Name)
|
||||
fmt.Printf("Ptr: %d\n", *opts.Ptr)
|
||||
fmt.Printf("StringSlice: %v\n", opts.StringSlice)
|
||||
fmt.Printf("PtrSlice: [%v %v]\n", *opts.PtrSlice[0], *opts.PtrSlice[1])
|
||||
fmt.Printf("IntMap: [a:%v b:%v]\n", opts.IntMap["a"], opts.IntMap["b"])
|
||||
fmt.Printf("Filename: %v\n", opts.Filename)
|
||||
fmt.Printf("Args.ID: %s\n", opts.Args.ID)
|
||||
fmt.Printf("Args.Num: %d\n", opts.Args.Num)
|
||||
fmt.Printf("Args.Rest: %v\n", opts.Args.Rest)
|
||||
|
||||
// Output: Verbosity: [true true]
|
||||
// Offset: 5
|
||||
// Name: Me
|
||||
// Ptr: 3
|
||||
// StringSlice: [hello world]
|
||||
// PtrSlice: [hello world]
|
||||
// IntMap: [a:1 b:5]
|
||||
// Filename: hello.go
|
||||
// Args.ID: id
|
||||
// Args.Num: 10
|
||||
// Args.Rest: [remaining1 remaining2]
|
||||
}
|
||||
+7
-5
@@ -35,6 +35,8 @@ Additional features specific to Windows:
|
||||
Options with long names (/verbose)
|
||||
Windows-style options with arguments use a colon as the delimiter
|
||||
Modify generated help message with Windows-style / options
|
||||
Windows style options can be disabled at build time using the "forceposix"
|
||||
build tag
|
||||
|
||||
|
||||
Basic usage
|
||||
@@ -76,15 +78,15 @@ The following is a list of tags for struct fields supported by go-flags:
|
||||
|
||||
short: the short name of the option (single character)
|
||||
long: the long name of the option
|
||||
required: whether an option is required to appear on the command
|
||||
required: if non empty, makes the option required to appear on the command
|
||||
line. If a required option is not present, the parser will
|
||||
return ErrRequired (optional)
|
||||
description: the description of the option (optional)
|
||||
long-description: the long description of the option. Currently only
|
||||
displayed in generated man pages (optional)
|
||||
no-flag: if non-empty this field is ignored as an option (optional)
|
||||
no-flag: if non-empty, this field is ignored as an option (optional)
|
||||
|
||||
optional: whether an argument of the option is optional. When an
|
||||
optional: if non-empty, makes the argument of the option optional. When an
|
||||
argument is optional it can only be specified using
|
||||
--option=argument (optional)
|
||||
optional-value: the value of an optional option when the option occurs
|
||||
@@ -107,8 +109,8 @@ The following is a list of tags for struct fields supported by go-flags:
|
||||
value-name: the name of the argument value (to be shown in the help)
|
||||
(optional)
|
||||
choice: limits the values for an option to a set of values.
|
||||
This tag can be specified mltiple times (optional)
|
||||
hidden: the option is not visible in the help or man page.
|
||||
This tag can be specified multiple times (optional)
|
||||
hidden: if non-empty, the option is not visible in the help or man page.
|
||||
|
||||
base: a base (radix) used to convert strings to integer values, the
|
||||
default base is 10 (i.e. decimal) (optional)
|
||||
|
||||
+14
-4
@@ -174,6 +174,10 @@ func (g *Group) eachGroup(f func(*Group)) {
|
||||
}
|
||||
}
|
||||
|
||||
func isStringFalsy(s string) bool {
|
||||
return s == "" || s == "false" || s == "no" || s == "0"
|
||||
}
|
||||
|
||||
func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, handler scanHandler) error {
|
||||
stype := realval.Type()
|
||||
|
||||
@@ -213,13 +217,19 @@ func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, h
|
||||
return err
|
||||
}
|
||||
} else if kind == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
|
||||
flagCountBefore := len(g.options) + len(g.groups)
|
||||
|
||||
if fld.IsNil() {
|
||||
fld.Set(reflect.New(fld.Type().Elem()))
|
||||
fld = reflect.New(fld.Type().Elem())
|
||||
}
|
||||
|
||||
if err := g.scanStruct(reflect.Indirect(fld), &field, handler); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(g.options)+len(g.groups) != flagCountBefore {
|
||||
realval.Field(i).Set(fld)
|
||||
}
|
||||
}
|
||||
|
||||
longname := mtag.Get("long")
|
||||
@@ -249,10 +259,10 @@ func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, h
|
||||
valueName := mtag.Get("value-name")
|
||||
defaultMask := mtag.Get("default-mask")
|
||||
|
||||
optional := (mtag.Get("optional") != "")
|
||||
required := (mtag.Get("required") != "")
|
||||
optional := !isStringFalsy(mtag.Get("optional"))
|
||||
required := !isStringFalsy(mtag.Get("required"))
|
||||
choices := mtag.GetMany("choice")
|
||||
hidden := (mtag.Get("hidden") != "")
|
||||
hidden := !isStringFalsy(mtag.Get("hidden"))
|
||||
|
||||
option := &Option{
|
||||
Description: description,
|
||||
|
||||
-255
@@ -1,255 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGroupInline(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Group struct {
|
||||
G bool `short:"g"`
|
||||
} `group:"Grouped Options"`
|
||||
}{}
|
||||
|
||||
p, ret := assertParserSuccess(t, &opts, "-v", "-g")
|
||||
|
||||
assertStringArray(t, ret, []string{})
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.Group.G {
|
||||
t.Errorf("Expected Group.G to be true")
|
||||
}
|
||||
|
||||
if p.Command.Group.Find("Grouped Options") == nil {
|
||||
t.Errorf("Expected to find group `Grouped Options'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupAdd(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
}{}
|
||||
|
||||
var grp = struct {
|
||||
G bool `short:"g"`
|
||||
}{}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
g, err := p.AddGroup("Grouped Options", "", &grp)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
ret, err := p.ParseArgs([]string{"-v", "-g", "rest"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
assertStringArray(t, ret, []string{"rest"})
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !grp.G {
|
||||
t.Errorf("Expected Group.G to be true")
|
||||
}
|
||||
|
||||
if p.Command.Group.Find("Grouped Options") != g {
|
||||
t.Errorf("Expected to find group `Grouped Options'")
|
||||
}
|
||||
|
||||
if p.Groups()[1] != g {
|
||||
t.Errorf("Expected group %#v, but got %#v", g, p.Groups()[0])
|
||||
}
|
||||
|
||||
if g.Options()[0].ShortName != 'g' {
|
||||
t.Errorf("Expected short name `g' but got %v", g.Options()[0].ShortName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupNestedInline(t *testing.T) {
|
||||
var opts = struct {
|
||||
Value bool `short:"v"`
|
||||
|
||||
Group struct {
|
||||
G bool `short:"g"`
|
||||
|
||||
Nested struct {
|
||||
N string `long:"n"`
|
||||
} `group:"Nested Options"`
|
||||
} `group:"Grouped Options"`
|
||||
}{}
|
||||
|
||||
p, ret := assertParserSuccess(t, &opts, "-v", "-g", "--n", "n", "rest")
|
||||
|
||||
assertStringArray(t, ret, []string{"rest"})
|
||||
|
||||
if !opts.Value {
|
||||
t.Errorf("Expected Value to be true")
|
||||
}
|
||||
|
||||
if !opts.Group.G {
|
||||
t.Errorf("Expected Group.G to be true")
|
||||
}
|
||||
|
||||
assertString(t, opts.Group.Nested.N, "n")
|
||||
|
||||
if p.Command.Group.Find("Grouped Options") == nil {
|
||||
t.Errorf("Expected to find group `Grouped Options'")
|
||||
}
|
||||
|
||||
if p.Command.Group.Find("Nested Options") == nil {
|
||||
t.Errorf("Expected to find group `Nested Options'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupNestedInlineNamespace(t *testing.T) {
|
||||
var opts = struct {
|
||||
Opt string `long:"opt"`
|
||||
|
||||
Group struct {
|
||||
Opt string `long:"opt"`
|
||||
Group struct {
|
||||
Opt string `long:"opt"`
|
||||
} `group:"Subsubgroup" namespace:"sap"`
|
||||
} `group:"Subgroup" namespace:"sip"`
|
||||
}{}
|
||||
|
||||
p, ret := assertParserSuccess(t, &opts, "--opt", "a", "--sip.opt", "b", "--sip.sap.opt", "c", "rest")
|
||||
|
||||
assertStringArray(t, ret, []string{"rest"})
|
||||
|
||||
assertString(t, opts.Opt, "a")
|
||||
assertString(t, opts.Group.Opt, "b")
|
||||
assertString(t, opts.Group.Group.Opt, "c")
|
||||
|
||||
for _, name := range []string{"Subgroup", "Subsubgroup"} {
|
||||
if p.Command.Group.Find(name) == nil {
|
||||
t.Errorf("Expected to find group '%s'", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicateShortFlags(t *testing.T) {
|
||||
var opts struct {
|
||||
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
|
||||
Variables []string `short:"v" long:"variable" description:"Set a variable value."`
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"--verbose",
|
||||
"-v", "123",
|
||||
"-v", "456",
|
||||
}
|
||||
|
||||
_, err := ParseArgs(&opts, args)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error with type ErrDuplicatedFlag")
|
||||
} else {
|
||||
err2 := err.(*Error)
|
||||
if err2.Type != ErrDuplicatedFlag {
|
||||
t.Errorf("Expected an error with type ErrDuplicatedFlag")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuplicateLongFlags(t *testing.T) {
|
||||
var opts struct {
|
||||
Test1 []bool `short:"a" long:"testing" description:"Test 1"`
|
||||
Test2 []string `short:"b" long:"testing" description:"Test 2."`
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"--testing",
|
||||
}
|
||||
|
||||
_, err := ParseArgs(&opts, args)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error with type ErrDuplicatedFlag")
|
||||
} else {
|
||||
err2 := err.(*Error)
|
||||
if err2.Type != ErrDuplicatedFlag {
|
||||
t.Errorf("Expected an error with type ErrDuplicatedFlag")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindOptionByLongFlag(t *testing.T) {
|
||||
var opts struct {
|
||||
Testing bool `long:"testing" description:"Testing"`
|
||||
}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
opt := p.FindOptionByLongName("testing")
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
assertString(t, opt.LongName, "testing")
|
||||
}
|
||||
|
||||
func TestFindOptionByShortFlag(t *testing.T) {
|
||||
var opts struct {
|
||||
Testing bool `short:"t" description:"Testing"`
|
||||
}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
opt := p.FindOptionByShortName('t')
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
if opt.ShortName != 't' {
|
||||
t.Errorf("Expected 't', but got %v", opt.ShortName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindOptionByLongFlagInSubGroup(t *testing.T) {
|
||||
var opts struct {
|
||||
Group struct {
|
||||
Testing bool `long:"testing" description:"Testing"`
|
||||
} `group:"sub-group"`
|
||||
}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
opt := p.FindOptionByLongName("testing")
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
assertString(t, opt.LongName, "testing")
|
||||
}
|
||||
|
||||
func TestFindOptionByShortFlagInSubGroup(t *testing.T) {
|
||||
var opts struct {
|
||||
Group struct {
|
||||
Testing bool `short:"t" description:"Testing"`
|
||||
} `group:"sub-group"`
|
||||
}
|
||||
|
||||
p := NewParser(&opts, Default)
|
||||
opt := p.FindOptionByShortName('t')
|
||||
|
||||
if opt == nil {
|
||||
t.Errorf("Expected option, but found none")
|
||||
}
|
||||
|
||||
if opt.ShortName != 't' {
|
||||
t.Errorf("Expected 't', but got %v", opt.ShortName)
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -107,6 +107,10 @@ func (p *Parser) getAlignmentInfo() alignmentInfo {
|
||||
func wrapText(s string, l int, prefix string) string {
|
||||
var ret string
|
||||
|
||||
if l < 10 {
|
||||
l = 10
|
||||
}
|
||||
|
||||
// Basic text wrapping of s at spaces to fit in l
|
||||
lines := strings.Split(s, "\n")
|
||||
|
||||
@@ -212,8 +216,10 @@ func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alig
|
||||
|
||||
var def string
|
||||
|
||||
if len(option.DefaultMask) != 0 && option.DefaultMask != "-" {
|
||||
def = option.DefaultMask
|
||||
if len(option.DefaultMask) != 0 {
|
||||
if option.DefaultMask != "-" {
|
||||
def = option.DefaultMask
|
||||
}
|
||||
} else {
|
||||
def = option.defaultLiteral
|
||||
}
|
||||
|
||||
-468
@@ -1,468 +0,0 @@
|
||||
package flags
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type helpOptions struct {
|
||||
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information" ini-name:"verbose"`
|
||||
Call func(string) `short:"c" description:"Call phone number" ini-name:"call"`
|
||||
PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"`
|
||||
EmptyDescription bool `long:"empty-description"`
|
||||
|
||||
Default string `long:"default" default:"Some\nvalue" description:"Test default value"`
|
||||
DefaultArray []string `long:"default-array" default:"Some value" default:"Other\tvalue" description:"Test default array value"`
|
||||
DefaultMap map[string]string `long:"default-map" default:"some:value" default:"another:value" description:"Testdefault map value"`
|
||||
EnvDefault1 string `long:"env-default1" default:"Some value" env:"ENV_DEFAULT" description:"Test env-default1 value"`
|
||||
EnvDefault2 string `long:"env-default2" env:"ENV_DEFAULT" description:"Test env-default2 value"`
|
||||
OptionWithArgName string `long:"opt-with-arg-name" value-name:"something" description:"Option with named argument"`
|
||||
OptionWithChoices string `long:"opt-with-choices" value-name:"choice" choice:"dog" choice:"cat" description:"Option with choices"`
|
||||
Hidden string `long:"hidden" description:"Hidden option" hidden:"yes"`
|
||||
|
||||
OnlyIni string `ini-name:"only-ini" description:"Option only available in ini"`
|
||||
|
||||
Other struct {
|
||||
StringSlice []string `short:"s" default:"some" default:"value" description:"A slice of strings"`
|
||||
IntMap map[string]int `long:"intmap" default:"a:1" description:"A map from string to int" ini-name:"int-map"`
|
||||
} `group:"Other Options"`
|
||||
|
||||
HiddenGroup struct {
|
||||
InsideHiddenGroup string `long:"inside-hidden-group" description:"Inside hidden group"`
|
||||
} `group:"Hidden group" hidden:"yes"`
|
||||
|
||||
Group struct {
|
||||
Opt string `long:"opt" description:"This is a subgroup option"`
|
||||
HiddenInsideGroup string `long:"hidden-inside-group" description:"Hidden inside group" hidden:"yes"`
|
||||
|
||||
Group struct {
|
||||
Opt string `long:"opt" description:"This is a subsubgroup option"`
|
||||
} `group:"Subsubgroup" namespace:"sap"`
|
||||
} `group:"Subgroup" namespace:"sip"`
|
||||
|
||||
Command struct {
|
||||
ExtraVerbose []bool `long:"extra-verbose" description:"Use for extra verbosity"`
|
||||
} `command:"command" alias:"cm" alias:"cmd" description:"A command"`
|
||||
|
||||
HiddenCommand struct {
|
||||
ExtraVerbose []bool `long:"extra-verbose" description:"Use for extra verbosity"`
|
||||
} `command:"hidden-command" description:"A hidden command" hidden:"yes"`
|
||||
|
||||
Args struct {
|
||||
Filename string `positional-arg-name:"filename" description:"A filename with a long description to trigger line wrapping"`
|
||||
Number int `positional-arg-name:"num" description:"A number"`
|
||||
HiddenInHelp float32 `positional-arg-name:"hidden-in-help" required:"yes"`
|
||||
} `positional-args:"yes"`
|
||||
}
|
||||
|
||||
func TestHelp(t *testing.T) {
|
||||
oldEnv := EnvSnapshot()
|
||||
defer oldEnv.Restore()
|
||||
os.Setenv("ENV_DEFAULT", "env-def")
|
||||
|
||||
var opts helpOptions
|
||||
p := NewNamedParser("TestHelp", HelpFlag)
|
||||
p.AddGroup("Application Options", "The application options", &opts)
|
||||
|
||||
_, err := p.ParseArgs([]string{"--help"})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Expected help error")
|
||||
}
|
||||
|
||||
if e, ok := err.(*Error); !ok {
|
||||
t.Fatalf("Expected flags.Error, but got %T", err)
|
||||
} else {
|
||||
if e.Type != ErrHelp {
|
||||
t.Errorf("Expected flags.ErrHelp type, but got %s", e.Type)
|
||||
}
|
||||
|
||||
var expected string
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
expected = `Usage:
|
||||
TestHelp [OPTIONS] [filename] [num] [hidden-in-help] <command>
|
||||
|
||||
|
||||
Application Options:
|
||||
/v, /verbose Show verbose debug information
|
||||
/c: Call phone number
|
||||
/ptrslice: A slice of pointers to string
|
||||
/empty-description
|
||||
/default: Test default value (default:
|
||||
"Some\nvalue")
|
||||
/default-array: Test default array value (default:
|
||||
Some value, "Other\tvalue")
|
||||
/default-map: Testdefault map value (default:
|
||||
some:value, another:value)
|
||||
/env-default1: Test env-default1 value (default:
|
||||
Some value) [%ENV_DEFAULT%]
|
||||
/env-default2: Test env-default2 value
|
||||
[%ENV_DEFAULT%]
|
||||
/opt-with-arg-name:something Option with named argument
|
||||
/opt-with-choices:choice[dog|cat] Option with choices
|
||||
|
||||
Other Options:
|
||||
/s: A slice of strings (default: some,
|
||||
value)
|
||||
/intmap: A map from string to int (default:
|
||||
a:1)
|
||||
|
||||
Subgroup:
|
||||
/sip.opt: This is a subgroup option
|
||||
|
||||
Subsubgroup:
|
||||
/sip.sap.opt: This is a subsubgroup option
|
||||
|
||||
Help Options:
|
||||
/? Show this help message
|
||||
/h, /help Show this help message
|
||||
|
||||
Arguments:
|
||||
filename: A filename
|
||||
num: A number
|
||||
|
||||
Available commands:
|
||||
command A command (aliases: cm, cmd)
|
||||
`
|
||||
} else {
|
||||
expected = `Usage:
|
||||
TestHelp [OPTIONS] [filename] [num] [hidden-in-help] <command>
|
||||
|
||||
Application Options:
|
||||
-v, --verbose Show verbose debug information
|
||||
-c= Call phone number
|
||||
--ptrslice= A slice of pointers to string
|
||||
--empty-description
|
||||
--default= Test default value (default:
|
||||
"Some\nvalue")
|
||||
--default-array= Test default array value (default:
|
||||
Some value, "Other\tvalue")
|
||||
--default-map= Testdefault map value (default:
|
||||
some:value, another:value)
|
||||
--env-default1= Test env-default1 value (default:
|
||||
Some value) [$ENV_DEFAULT]
|
||||
--env-default2= Test env-default2 value
|
||||
[$ENV_DEFAULT]
|
||||
--opt-with-arg-name=something Option with named argument
|
||||
--opt-with-choices=choice[dog|cat] Option with choices
|
||||
|
||||
Other Options:
|
||||
-s= A slice of strings (default: some,
|
||||
value)
|
||||
--intmap= A map from string to int (default:
|
||||
a:1)
|
||||
|
||||
Subgroup:
|
||||
--sip.opt= This is a subgroup option
|
||||
|
||||
Subsubgroup:
|
||||
--sip.sap.opt= This is a subsubgroup option
|
||||
|
||||
Help Options:
|
||||
-h, --help Show this help message
|
||||
|
||||
Arguments:
|
||||
filename: A filename with a long description
|
||||
to trigger line wrapping
|
||||
num: A number
|
||||
|
||||
Available commands:
|
||||
command A command (aliases: cm, cmd)
|
||||
`
|
||||
}
|
||||
|
||||
assertDiff(t, e.Message, expected, "help message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMan(t *testing.T) {
|
||||
oldEnv := EnvSnapshot()
|
||||
defer oldEnv.Restore()
|
||||
os.Setenv("ENV_DEFAULT", "env-def")
|
||||
|
||||
var opts helpOptions
|
||||
p := NewNamedParser("TestMan", HelpFlag)
|
||||
p.ShortDescription = "Test manpage generation"
|
||||
p.LongDescription = "This is a somewhat `longer' description of what this does"
|
||||
p.AddGroup("Application Options", "The application options", &opts)
|
||||
|
||||
p.Commands()[0].LongDescription = "Longer `command' description"
|
||||
|
||||
var buf bytes.Buffer
|
||||
p.WriteManPage(&buf)
|
||||
|
||||
got := buf.String()
|
||||
|
||||
tt := time.Now()
|
||||
|
||||
var envDefaultName string
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
envDefaultName = "%ENV_DEFAULT%"
|
||||
} else {
|
||||
envDefaultName = "$ENV_DEFAULT"
|
||||
}
|
||||
|
||||
expected := fmt.Sprintf(`.TH TestMan 1 "%s"
|
||||
.SH NAME
|
||||
TestMan \- Test manpage generation
|
||||
.SH SYNOPSIS
|
||||
\fBTestMan\fP [OPTIONS]
|
||||
.SH DESCRIPTION
|
||||
This is a somewhat \fBlonger\fP description of what this does
|
||||
.SH OPTIONS
|
||||
.SS Application Options
|
||||
The application options
|
||||
.TP
|
||||
\fB\fB\-v\fR, \fB\-\-verbose\fR\fP
|
||||
Show verbose debug information
|
||||
.TP
|
||||
\fB\fB\-c\fR\fP
|
||||
Call phone number
|
||||
.TP
|
||||
\fB\fB\-\-ptrslice\fR\fP
|
||||
A slice of pointers to string
|
||||
.TP
|
||||
\fB\fB\-\-empty-description\fR\fP
|
||||
.TP
|
||||
\fB\fB\-\-default\fR <default: \fI"Some\\nvalue"\fR>\fP
|
||||
Test default value
|
||||
.TP
|
||||
\fB\fB\-\-default-array\fR <default: \fI"Some value", "Other\\tvalue"\fR>\fP
|
||||
Test default array value
|
||||
.TP
|
||||
\fB\fB\-\-default-map\fR <default: \fI"some:value", "another:value"\fR>\fP
|
||||
Testdefault map value
|
||||
.TP
|
||||
\fB\fB\-\-env-default1\fR <default: \fI"Some value"\fR>\fP
|
||||
Test env-default1 value
|
||||
.TP
|
||||
\fB\fB\-\-env-default2\fR <default: \fI%s\fR>\fP
|
||||
Test env-default2 value
|
||||
.TP
|
||||
\fB\fB\-\-opt-with-arg-name\fR \fIsomething\fR\fP
|
||||
Option with named argument
|
||||
.TP
|
||||
\fB\fB\-\-opt-with-choices\fR \fIchoice\fR\fP
|
||||
Option with choices
|
||||
.SS Other Options
|
||||
.TP
|
||||
\fB\fB\-s\fR <default: \fI"some", "value"\fR>\fP
|
||||
A slice of strings
|
||||
.TP
|
||||
\fB\fB\-\-intmap\fR <default: \fI"a:1"\fR>\fP
|
||||
A map from string to int
|
||||
.SS Subgroup
|
||||
.TP
|
||||
\fB\fB\-\-sip.opt\fR\fP
|
||||
This is a subgroup option
|
||||
.SS Subsubgroup
|
||||
.TP
|
||||
\fB\fB\-\-sip.sap.opt\fR\fP
|
||||
This is a subsubgroup option
|
||||
.SH COMMANDS
|
||||
.SS command
|
||||
A command
|
||||
|
||||
Longer \fBcommand\fP description
|
||||
|
||||
\fBUsage\fP: TestMan [OPTIONS] command [command-OPTIONS]
|
||||
.TP
|
||||
|
||||
\fBAliases\fP: cm, cmd
|
||||
|
||||
.TP
|
||||
\fB\fB\-\-extra-verbose\fR\fP
|
||||
Use for extra verbosity
|
||||
`, tt.Format("2 January 2006"), envDefaultName)
|
||||
|
||||
assertDiff(t, got, expected, "man page")
|
||||
}
|
||||
|
||||
type helpCommandNoOptions struct {
|
||||
Command struct {
|
||||
} `command:"command" description:"A command"`
|
||||
}
|
||||
|
||||
func TestHelpCommand(t *testing.T) {
|
||||
oldEnv := EnvSnapshot()
|
||||
defer oldEnv.Restore()
|
||||
os.Setenv("ENV_DEFAULT", "env-def")
|
||||
|
||||
var opts helpCommandNoOptions
|
||||
p := NewNamedParser("TestHelpCommand", HelpFlag)
|
||||
p.AddGroup("Application Options", "The application options", &opts)
|
||||
|
||||
_, err := p.ParseArgs([]string{"command", "--help"})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Expected help error")
|
||||
}
|
||||
|
||||
if e, ok := err.(*Error); !ok {
|
||||
t.Fatalf("Expected flags.Error, but got %T", err)
|
||||
} else {
|
||||
if e.Type != ErrHelp {
|
||||
t.Errorf("Expected flags.ErrHelp type, but got %s", e.Type)
|
||||
}
|
||||
|
||||
var expected string
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
expected = `Usage:
|
||||
TestHelpCommand [OPTIONS] command
|
||||
|
||||
Help Options:
|
||||
/? Show this help message
|
||||
/h, /help Show this help message
|
||||
`
|
||||
} else {
|
||||
expected = `Usage:
|
||||
TestHelpCommand [OPTIONS] command
|
||||
|
||||
Help Options:
|
||||
-h, --help Show this help message
|
||||
`
|
||||
}
|
||||
|
||||
assertDiff(t, e.Message, expected, "help message")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelpDefaults(t *testing.T) {
|
||||
var expected string
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
expected = `Usage:
|
||||
TestHelpDefaults [OPTIONS]
|
||||
|
||||
Application Options:
|
||||
/with-default: With default (default: default-value)
|
||||
/without-default: Without default
|
||||
/with-programmatic-default: With programmatic default (default:
|
||||
default-value)
|
||||
|
||||
Help Options:
|
||||
/? Show this help message
|
||||
/h, /help Show this help message
|
||||
`
|
||||
} else {
|
||||
expected = `Usage:
|
||||
TestHelpDefaults [OPTIONS]
|
||||
|
||||
Application Options:
|
||||
--with-default= With default (default: default-value)
|
||||
--without-default= Without default
|
||||
--with-programmatic-default= With programmatic default (default:
|
||||
default-value)
|
||||
|
||||
Help Options:
|
||||
-h, --help Show this help message
|
||||
`
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
Args []string
|
||||
Output string
|
||||
}{
|
||||
{
|
||||
Args: []string{"-h"},
|
||||
Output: expected,
|
||||
},
|
||||
{
|
||||
Args: []string{"--with-default", "other-value", "--with-programmatic-default", "other-value", "-h"},
|
||||
Output: expected,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
var opts struct {
|
||||
WithDefault string `long:"with-default" default:"default-value" description:"With default"`
|
||||
WithoutDefault string `long:"without-default" description:"Without default"`
|
||||
WithProgrammaticDefault string `long:"with-programmatic-default" description:"With programmatic default"`
|
||||
}
|
||||
|
||||
opts.WithProgrammaticDefault = "default-value"
|
||||
|
||||
p := NewNamedParser("TestHelpDefaults", HelpFlag)
|
||||
p.AddGroup("Application Options", "The application options", &opts)
|
||||
|
||||
_, err := p.ParseArgs(test.Args)
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Expected help error")
|
||||
}
|
||||
|
||||
if e, ok := err.(*Error); !ok {
|
||||
t.Fatalf("Expected flags.Error, but got %T", err)
|
||||
} else {
|
||||
if e.Type != ErrHelp {
|
||||
t.Errorf("Expected flags.ErrHelp type, but got %s", e.Type)
|
||||
}
|
||||
|
||||
assertDiff(t, e.Message, test.Output, "help message")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelpRestArgs(t *testing.T) {
|
||||
opts := struct {
|
||||
Verbose bool `short:"v"`
|
||||
}{}
|
||||
|
||||
p := NewNamedParser("TestHelpDefaults", HelpFlag)
|
||||
p.AddGroup("Application Options", "The application options", &opts)
|
||||
|
||||
retargs, err := p.ParseArgs([]string{"-h", "-v", "rest"})
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("Expected help error")
|
||||
}
|
||||
|
||||
assertStringArray(t, retargs, []string{"-v", "rest"})
|
||||
}
|
||||
|
||||
func TestWrapText(t *testing.T) {
|
||||
s := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
|
||||
got := wrapText(s, 60, " ")
|
||||
expected := `Lorem ipsum dolor sit amet, consectetur adipisicing elit,
|
||||
sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
||||
ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit
|
||||
esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
|
||||
occaecat cupidatat non proident, sunt in culpa qui officia
|
||||
deserunt mollit anim id est laborum.`
|
||||
|
||||
assertDiff(t, got, expected, "wrapped text")
|
||||
}
|
||||
|
||||
func TestWrapParagraph(t *testing.T) {
|
||||
s := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n\n"
|
||||
s += "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n\n"
|
||||
s += "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n\n"
|
||||
s += "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n"
|
||||
|
||||
got := wrapText(s, 60, " ")
|
||||
expected := `Lorem ipsum dolor sit amet, consectetur adipisicing elit,
|
||||
sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||
aliqua.
|
||||
|
||||
Ut enim ad minim veniam, quis nostrud exercitation ullamco
|
||||
laboris nisi ut aliquip ex ea commodo consequat.
|
||||
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit
|
||||
esse cillum dolore eu fugiat nulla pariatur.
|
||||
|
||||
Excepteur sint occaecat cupidatat non proident, sunt in
|
||||
culpa qui officia deserunt mollit anim id est laborum.
|
||||
`
|
||||
|
||||
assertDiff(t, got, expected, "wrapped paragraph")
|
||||
}
|
||||
+10
-6
@@ -11,7 +11,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IniError contains location information on where an error occured.
|
||||
// IniError contains location information on where an error occurred.
|
||||
type IniError struct {
|
||||
// The error message.
|
||||
Message string
|
||||
@@ -58,6 +58,8 @@ const (
|
||||
// IniParser is a utility to read and write flags options from and to ini
|
||||
// formatted strings.
|
||||
type IniParser struct {
|
||||
ParseAsDefaults bool // override default flags
|
||||
|
||||
parser *Parser
|
||||
}
|
||||
|
||||
@@ -96,8 +98,6 @@ func IniParse(filename string, data interface{}) error {
|
||||
// information on the ini file format. The returned errors can be of the type
|
||||
// flags.Error or flags.IniError.
|
||||
func (i *IniParser) ParseFile(filename string) error {
|
||||
i.parser.clearIsSet()
|
||||
|
||||
ini, err := readIniFromFile(filename)
|
||||
|
||||
if err != nil {
|
||||
@@ -132,8 +132,6 @@ func (i *IniParser) ParseFile(filename string) error {
|
||||
//
|
||||
// The returned errors can be of the type flags.Error or flags.IniError.
|
||||
func (i *IniParser) Parse(reader io.Reader) error {
|
||||
i.parser.clearIsSet()
|
||||
|
||||
ini, err := readIni(reader, "")
|
||||
|
||||
if err != nil {
|
||||
@@ -143,7 +141,7 @@ func (i *IniParser) Parse(reader io.Reader) error {
|
||||
return i.parse(ini)
|
||||
}
|
||||
|
||||
// WriteFile writes the flags as ini format into a file. See WriteIni
|
||||
// WriteFile writes the flags as ini format into a file. See Write
|
||||
// for more information. The returned error occurs when the specified file
|
||||
// could not be opened for writing.
|
||||
func (i *IniParser) WriteFile(filename string, options IniOptions) error {
|
||||
@@ -539,6 +537,12 @@ func (i *IniParser) parse(ini *ini) error {
|
||||
continue
|
||||
}
|
||||
|
||||
// ini value is ignored if override is set and
|
||||
// value was previously set from non default
|
||||
if i.ParseAsDefaults && !opt.isSetDefault {
|
||||
continue
|
||||
}
|
||||
|
||||
pval := &inival.Value
|
||||
|
||||
if !opt.canArgument() && len(inival.Value) == 0 {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user