+54
@@ -0,0 +1,54 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// Annotate prepends msg to the error message in err, attempting
|
||||
// to preserve other information in err, like an error code.
|
||||
//
|
||||
// Annotate panics if err is nil.
|
||||
//
|
||||
// Annotate knows about these error types:
|
||||
// - "google.golang.org/grpc/status".Status
|
||||
// - "google.golang.org/api/googleapi".Error
|
||||
// If the error is not one of these types, Annotate behaves
|
||||
// like
|
||||
// fmt.Errorf("%s: %v", msg, err)
|
||||
func Annotate(err error, msg string) error {
|
||||
if err == nil {
|
||||
panic("Annotate called with nil")
|
||||
}
|
||||
if s, ok := status.FromError(err); ok {
|
||||
p := s.Proto()
|
||||
p.Message = msg + ": " + p.Message
|
||||
return status.ErrorProto(p)
|
||||
}
|
||||
if g, ok := err.(*googleapi.Error); ok {
|
||||
g.Message = msg + ": " + g.Message
|
||||
return g
|
||||
}
|
||||
return fmt.Errorf("%s: %v", msg, err)
|
||||
}
|
||||
|
||||
// Annotatef uses format and args to format a string, then calls Annotate.
|
||||
func Annotatef(err error, format string, args ...interface{}) error {
|
||||
return Annotate(err, fmt.Sprintf(format, args...))
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
const wantMessage = "prefix: msg"
|
||||
|
||||
func TestAnnotateGRPC(t *testing.T) {
|
||||
// grpc Status error
|
||||
err := status.Error(codes.NotFound, "msg")
|
||||
err = Annotate(err, "prefix")
|
||||
got, ok := status.FromError(err)
|
||||
if !ok {
|
||||
t.Fatalf("got %T, wanted a status", got)
|
||||
}
|
||||
if g, w := got.Code(), codes.NotFound; g != w {
|
||||
t.Errorf("got code %v, want %v", g, w)
|
||||
}
|
||||
if g, w := got.Message(), wantMessage; g != w {
|
||||
t.Errorf("got message %q, want %q", g, w)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotateGoogleapi(t *testing.T) {
|
||||
// googleapi error
|
||||
var err error = &googleapi.Error{Code: 403, Message: "msg"}
|
||||
err = Annotate(err, "prefix")
|
||||
got2, ok := err.(*googleapi.Error)
|
||||
if !ok {
|
||||
t.Fatalf("got %T, wanted a googleapi.Error", got2)
|
||||
}
|
||||
if g, w := got2.Code, 403; g != w {
|
||||
t.Errorf("got code %d, want %d", g, w)
|
||||
}
|
||||
if g, w := got2.Message, wantMessage; g != w {
|
||||
t.Errorf("got message %q, want %q", g, w)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotateUnknownError(t *testing.T) {
|
||||
err := Annotate(errors.New("msg"), "prefix")
|
||||
if g, w := err.Error(), wantMessage; g != w {
|
||||
t.Errorf("got message %q, want %q", g, w)
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package atomiccache provides a map-based cache that supports very fast
|
||||
// reads.
|
||||
package atomiccache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type mapType map[interface{}]interface{}
|
||||
|
||||
// Cache is a map-based cache that supports fast reads via use of atomics.
|
||||
// Writes are slow, requiring a copy of the entire cache.
|
||||
// The zero Cache is an empty cache, ready for use.
|
||||
type Cache struct {
|
||||
val atomic.Value // mapType
|
||||
mu sync.Mutex // used only by writers
|
||||
}
|
||||
|
||||
// Get returns the value of the cache at key. If there is no value,
|
||||
// getter is called to provide one, and the cache is updated.
|
||||
// The getter function may be called concurrently. It should be pure,
|
||||
// returning the same value for every call.
|
||||
func (c *Cache) Get(key interface{}, getter func() interface{}) interface{} {
|
||||
mp, _ := c.val.Load().(mapType)
|
||||
if v, ok := mp[key]; ok {
|
||||
return v
|
||||
}
|
||||
|
||||
// Compute value without lock.
|
||||
// Might duplicate effort but won't hold other computations back.
|
||||
newV := getter()
|
||||
|
||||
c.mu.Lock()
|
||||
mp, _ = c.val.Load().(mapType)
|
||||
newM := make(mapType, len(mp)+1)
|
||||
for k, v := range mp {
|
||||
newM[k] = v
|
||||
}
|
||||
newM[key] = newV
|
||||
c.val.Store(newM)
|
||||
c.mu.Unlock()
|
||||
return newV
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package atomiccache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
var c Cache
|
||||
called := false
|
||||
get := func(k interface{}) interface{} {
|
||||
return c.Get(k, func() interface{} {
|
||||
called = true
|
||||
return fmt.Sprintf("v%d", k)
|
||||
})
|
||||
}
|
||||
got := get(1)
|
||||
if want := "v1"; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
if !called {
|
||||
t.Error("getter not called, expected a call")
|
||||
}
|
||||
called = false
|
||||
got = get(1)
|
||||
if want := "v1"; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
if called {
|
||||
t.Error("getter unexpectedly called")
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
This package is a fork of github.com/jba/btree at commit
|
||||
d4edd57f39b8425fc2c631047ff4dc6024d82a4f, which itself was a fork of
|
||||
github.com/google/btree at 316fb6d3f031ae8f4d457c6c5186b9e3ded70435.
|
||||
|
||||
This directory makes the following modifications:
|
||||
|
||||
- Updated copyright notice.
|
||||
- removed LICENSE (it is the same as the repo-wide license, Apache 2.0)
|
||||
- Removed examples_test.go and .travis.yml.
|
||||
- Added this file.
|
||||
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package btree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const benchmarkTreeSize = 10000
|
||||
|
||||
var degrees = []int{2, 8, 32, 64}
|
||||
|
||||
func BenchmarkInsert(b *testing.B) {
|
||||
insertP := perm(benchmarkTreeSize)
|
||||
for _, d := range degrees {
|
||||
b.Run(fmt.Sprintf("degree=%d", d), func(b *testing.B) {
|
||||
i := 0
|
||||
for i < b.N {
|
||||
tr := New(d, less)
|
||||
for _, m := range insertP {
|
||||
tr.Set(m.Key, m.Value)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDeleteInsert(b *testing.B) {
|
||||
insertP := perm(benchmarkTreeSize)
|
||||
for _, d := range degrees {
|
||||
b.Run(fmt.Sprintf("degree=%d", d), func(b *testing.B) {
|
||||
tr := New(d, less)
|
||||
for _, m := range insertP {
|
||||
tr.Set(m.Key, m.Value)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m := insertP[i%benchmarkTreeSize]
|
||||
tr.Delete(m.Key)
|
||||
tr.Set(m.Key, m.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDeleteInsertCloneOnce(b *testing.B) {
|
||||
insertP := perm(benchmarkTreeSize)
|
||||
for _, d := range degrees {
|
||||
b.Run(fmt.Sprintf("degree=%d", d), func(b *testing.B) {
|
||||
tr := New(d, less)
|
||||
for _, m := range insertP {
|
||||
tr.Set(m.Key, m.Value)
|
||||
}
|
||||
tr = tr.Clone()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
m := insertP[i%benchmarkTreeSize]
|
||||
tr.Delete(m.Key)
|
||||
tr.Set(m.Key, m.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDeleteInsertCloneEachTime(b *testing.B) {
|
||||
insertP := perm(benchmarkTreeSize)
|
||||
for _, d := range degrees {
|
||||
b.Run(fmt.Sprintf("degree=%d", d), func(b *testing.B) {
|
||||
tr := New(d, less)
|
||||
for _, m := range insertP {
|
||||
tr.Set(m.Key, m.Value)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr = tr.Clone()
|
||||
m := insertP[i%benchmarkTreeSize]
|
||||
tr.Delete(m.Key)
|
||||
tr.Set(m.Key, m.Value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDelete(b *testing.B) {
|
||||
insertP := perm(benchmarkTreeSize)
|
||||
removeP := perm(benchmarkTreeSize)
|
||||
for _, d := range degrees {
|
||||
b.Run(fmt.Sprintf("degree=%d", d), func(b *testing.B) {
|
||||
i := 0
|
||||
for i < b.N {
|
||||
b.StopTimer()
|
||||
tr := New(d, less)
|
||||
for _, v := range insertP {
|
||||
tr.Set(v.Key, v.Value)
|
||||
}
|
||||
b.StartTimer()
|
||||
for _, m := range removeP {
|
||||
tr.Delete(m.Key)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
if tr.Len() > 0 {
|
||||
panic(tr.Len())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGet(b *testing.B) {
|
||||
insertP := perm(benchmarkTreeSize)
|
||||
getP := perm(benchmarkTreeSize)
|
||||
for _, d := range degrees {
|
||||
b.Run(fmt.Sprintf("degree=%d", d), func(b *testing.B) {
|
||||
i := 0
|
||||
for i < b.N {
|
||||
b.StopTimer()
|
||||
tr := New(d, less)
|
||||
for _, v := range insertP {
|
||||
tr.Set(v.Key, v.Value)
|
||||
}
|
||||
b.StartTimer()
|
||||
for _, m := range getP {
|
||||
tr.Get(m.Key)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetWithIndex(b *testing.B) {
|
||||
insertP := perm(benchmarkTreeSize)
|
||||
getP := perm(benchmarkTreeSize)
|
||||
for _, d := range degrees {
|
||||
b.Run(fmt.Sprintf("degree=%d", d), func(b *testing.B) {
|
||||
i := 0
|
||||
for i < b.N {
|
||||
b.StopTimer()
|
||||
tr := New(d, less)
|
||||
for _, v := range insertP {
|
||||
tr.Set(v.Key, v.Value)
|
||||
}
|
||||
b.StartTimer()
|
||||
for _, m := range getP {
|
||||
tr.GetWithIndex(m.Key)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetCloneEachTime(b *testing.B) {
|
||||
insertP := perm(benchmarkTreeSize)
|
||||
getP := perm(benchmarkTreeSize)
|
||||
for _, d := range degrees {
|
||||
b.Run(fmt.Sprintf("degree=%d", d), func(b *testing.B) {
|
||||
i := 0
|
||||
for i < b.N {
|
||||
b.StopTimer()
|
||||
tr := New(d, less)
|
||||
for _, m := range insertP {
|
||||
tr.Set(m.Key, m.Value)
|
||||
}
|
||||
b.StartTimer()
|
||||
for _, m := range getP {
|
||||
tr = tr.Clone()
|
||||
tr.Get(m.Key)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFind(b *testing.B) {
|
||||
for _, d := range degrees {
|
||||
var items []item
|
||||
for i := 0; i < 2*d; i++ {
|
||||
items = append(items, item{i, i})
|
||||
}
|
||||
b.Run(fmt.Sprintf("size=%d", len(items)), func(b *testing.B) {
|
||||
for _, alg := range []struct {
|
||||
name string
|
||||
fun func(Key, []item) (int, bool)
|
||||
}{
|
||||
{"binary", findBinary},
|
||||
{"linear", findLinear},
|
||||
} {
|
||||
b.Run(alg.name, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < len(items); j++ {
|
||||
alg.fun(items[j].key, items)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func findBinary(k Key, s []item) (int, bool) {
|
||||
i := sort.Search(len(s), func(i int) bool { return less(k, s[i].key) })
|
||||
// i is the smallest index of s for which key.Less(s[i].Key), or len(s).
|
||||
if i > 0 && !less(s[i-1], k) {
|
||||
return i - 1, true
|
||||
}
|
||||
return i, false
|
||||
}
|
||||
|
||||
func findLinear(k Key, s []item) (int, bool) {
|
||||
var i int
|
||||
for i = 0; i < len(s); i++ {
|
||||
if less(k, s[i].key) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i > 0 && !less(s[i-1].key, k) {
|
||||
return i - 1, true
|
||||
}
|
||||
return i, false
|
||||
}
|
||||
|
||||
type byInts []item
|
||||
|
||||
func (a byInts) Len() int {
|
||||
return len(a)
|
||||
}
|
||||
|
||||
func (a byInts) Less(i, j int) bool {
|
||||
return a[i].key.(int) < a[j].key.(int)
|
||||
}
|
||||
|
||||
func (a byInts) Swap(i, j int) {
|
||||
a[i], a[j] = a[j], a[i]
|
||||
}
|
||||
+1018
File diff suppressed because it is too large
Load Diff
+422
@@ -0,0 +1,422 @@
|
||||
// Copyright 2014 Google Inc.
|
||||
// Modified 2018 by Jonathan Amsterdam (jbamsterdam@gmail.com)
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package btree
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
seed := time.Now().Unix()
|
||||
fmt.Println(seed)
|
||||
rand.Seed(seed)
|
||||
}
|
||||
|
||||
type itemWithIndex struct {
|
||||
Key Key
|
||||
Value Value
|
||||
Index int
|
||||
}
|
||||
|
||||
// perm returns a random permutation of n Int items in the range [0, n).
|
||||
func perm(n int) []itemWithIndex {
|
||||
var out []itemWithIndex
|
||||
for _, v := range rand.Perm(n) {
|
||||
out = append(out, itemWithIndex{v, v, v})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// rang returns an ordered list of Int items in the range [0, n).
|
||||
func rang(n int) []itemWithIndex {
|
||||
var out []itemWithIndex
|
||||
for i := 0; i < n; i++ {
|
||||
out = append(out, itemWithIndex{i, i, i})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// all extracts all items from an iterator.
|
||||
func all(it *Iterator) []itemWithIndex {
|
||||
var out []itemWithIndex
|
||||
for it.Next() {
|
||||
out = append(out, itemWithIndex{it.Key, it.Value, it.Index})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// rangerev returns a reversed ordered list of Int items in the range [0, n).
|
||||
func rangrev(n int) []itemWithIndex {
|
||||
var out []itemWithIndex
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
out = append(out, itemWithIndex{i, i, i})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func reverse(s []itemWithIndex) {
|
||||
for i := 0; i < len(s)/2; i++ {
|
||||
s[i], s[len(s)-i-1] = s[len(s)-i-1], s[i]
|
||||
}
|
||||
}
|
||||
|
||||
var btreeDegree = flag.Int("degree", 32, "B-Tree degree")
|
||||
|
||||
func TestBTree(t *testing.T) {
|
||||
tr := New(*btreeDegree, less)
|
||||
const treeSize = 10000
|
||||
for i := 0; i < 10; i++ {
|
||||
if min, _ := tr.Min(); min != nil {
|
||||
t.Fatalf("empty min, got %+v", min)
|
||||
}
|
||||
if max, _ := tr.Max(); max != nil {
|
||||
t.Fatalf("empty max, got %+v", max)
|
||||
}
|
||||
for _, m := range perm(treeSize) {
|
||||
if _, ok := tr.Set(m.Key, m.Value); ok {
|
||||
t.Fatal("set found item", m)
|
||||
}
|
||||
}
|
||||
for _, m := range perm(treeSize) {
|
||||
_, ok, idx := tr.SetWithIndex(m.Key, m.Value)
|
||||
if !ok {
|
||||
t.Fatal("set didn't find item", m)
|
||||
}
|
||||
if idx != m.Index {
|
||||
t.Fatalf("got index %d, want %d", idx, m.Index)
|
||||
}
|
||||
}
|
||||
mink, minv := tr.Min()
|
||||
if want := 0; mink != want || minv != want {
|
||||
t.Fatalf("min: want %+v, got %+v, %+v", want, mink, minv)
|
||||
}
|
||||
maxk, maxv := tr.Max()
|
||||
if want := treeSize - 1; maxk != want || maxv != want {
|
||||
t.Fatalf("max: want %+v, got %+v, %+v", want, maxk, maxv)
|
||||
}
|
||||
got := all(tr.BeforeIndex(0))
|
||||
want := rang(treeSize)
|
||||
if !cmp.Equal(got, want) {
|
||||
t.Fatalf("mismatch:\n got: %v\nwant: %v", got, want)
|
||||
}
|
||||
|
||||
for _, m := range perm(treeSize) {
|
||||
if _, removed := tr.Delete(m.Key); !removed {
|
||||
t.Fatalf("didn't find %v", m)
|
||||
}
|
||||
}
|
||||
if got = all(tr.BeforeIndex(0)); len(got) > 0 {
|
||||
t.Fatalf("some left!: %v", got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAt(t *testing.T) {
|
||||
tr := New(*btreeDegree, less)
|
||||
for _, m := range perm(100) {
|
||||
tr.Set(m.Key, m.Value)
|
||||
}
|
||||
for i := 0; i < tr.Len(); i++ {
|
||||
gotk, gotv := tr.At(i)
|
||||
if want := i; gotk != want || gotv != want {
|
||||
t.Fatalf("At(%d) = (%v, %v), want (%v, %v)", i, gotk, gotv, want, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWithIndex(t *testing.T) {
|
||||
tr := New(*btreeDegree, less)
|
||||
for _, m := range perm(100) {
|
||||
tr.Set(m.Key, m.Value)
|
||||
}
|
||||
for i := 0; i < tr.Len(); i++ {
|
||||
gotv, goti := tr.GetWithIndex(i)
|
||||
wantv, wanti := i, i
|
||||
if gotv != wantv || goti != wanti {
|
||||
t.Errorf("GetWithIndex(%d) = (%v, %v), want (%v, %v)",
|
||||
i, gotv, goti, wantv, wanti)
|
||||
}
|
||||
}
|
||||
_, got := tr.GetWithIndex(100)
|
||||
if want := -1; got != want {
|
||||
t.Errorf("got %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetWithIndex(t *testing.T) {
|
||||
tr := New(4, less) // use a small degree to cover more cases
|
||||
var contents []int
|
||||
for _, m := range perm(100) {
|
||||
_, _, idx := tr.SetWithIndex(m.Key, m.Value)
|
||||
contents = append(contents, m.Index)
|
||||
sort.Ints(contents)
|
||||
want := -1
|
||||
for i, c := range contents {
|
||||
if c == m.Index {
|
||||
want = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx != want {
|
||||
t.Fatalf("got %d, want %d", idx, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteMin(t *testing.T) {
|
||||
tr := New(3, less)
|
||||
for _, m := range perm(100) {
|
||||
tr.Set(m.Key, m.Value)
|
||||
}
|
||||
var got []itemWithIndex
|
||||
for i := 0; tr.Len() > 0; i++ {
|
||||
k, v := tr.DeleteMin()
|
||||
got = append(got, itemWithIndex{k, v, i})
|
||||
}
|
||||
if want := rang(100); !cmp.Equal(got, want) {
|
||||
t.Fatalf("got: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteMax(t *testing.T) {
|
||||
tr := New(3, less)
|
||||
for _, m := range perm(100) {
|
||||
tr.Set(m.Key, m.Value)
|
||||
}
|
||||
var got []itemWithIndex
|
||||
for tr.Len() > 0 {
|
||||
k, v := tr.DeleteMax()
|
||||
got = append(got, itemWithIndex{k, v, tr.Len()})
|
||||
}
|
||||
reverse(got)
|
||||
if want := rang(100); !cmp.Equal(got, want) {
|
||||
t.Fatalf("got: %v\nwant: %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIterator(t *testing.T) {
|
||||
const size = 10
|
||||
|
||||
tr := New(2, less)
|
||||
// Empty tree.
|
||||
for i, it := range []*Iterator{
|
||||
tr.BeforeIndex(0),
|
||||
tr.Before(3),
|
||||
tr.After(3),
|
||||
} {
|
||||
if got, want := it.Next(), false; got != want {
|
||||
t.Errorf("empty, #%d: got %t, want %t", i, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Root with zero children.
|
||||
tr.Set(1, nil)
|
||||
tr.Delete(1)
|
||||
if !(tr.root != nil && len(tr.root.children) == 0 && len(tr.root.items) == 0) {
|
||||
t.Fatal("wrong shape tree")
|
||||
}
|
||||
for i, it := range []*Iterator{
|
||||
tr.BeforeIndex(0),
|
||||
tr.Before(3),
|
||||
tr.After(3),
|
||||
} {
|
||||
if got, want := it.Next(), false; got != want {
|
||||
t.Errorf("zero root, #%d: got %t, want %t", i, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Tree with size elements.
|
||||
p := perm(size)
|
||||
for _, v := range p {
|
||||
tr.Set(v.Key, v.Value)
|
||||
}
|
||||
|
||||
it := tr.BeforeIndex(0)
|
||||
got := all(it)
|
||||
want := rang(size)
|
||||
if !cmp.Equal(got, want) {
|
||||
t.Fatalf("got %+v\nwant %+v\n", got, want)
|
||||
}
|
||||
|
||||
for i, w := range want {
|
||||
it := tr.Before(w.Key)
|
||||
got = all(it)
|
||||
wn := want[w.Key.(int):]
|
||||
if !cmp.Equal(got, wn) {
|
||||
t.Fatalf("got %+v\nwant %+v\n", got, wn)
|
||||
}
|
||||
|
||||
it = tr.BeforeIndex(i)
|
||||
got = all(it)
|
||||
if !cmp.Equal(got, wn) {
|
||||
t.Fatalf("got %+v\nwant %+v\n", got, wn)
|
||||
}
|
||||
|
||||
it = tr.After(w.Key)
|
||||
got = all(it)
|
||||
wn = append([]itemWithIndex(nil), want[:w.Key.(int)+1]...)
|
||||
reverse(wn)
|
||||
if !cmp.Equal(got, wn) {
|
||||
t.Fatalf("got %+v\nwant %+v\n", got, wn)
|
||||
}
|
||||
|
||||
it = tr.AfterIndex(i)
|
||||
got = all(it)
|
||||
if !cmp.Equal(got, wn) {
|
||||
t.Fatalf("got %+v\nwant %+v\n", got, wn)
|
||||
}
|
||||
}
|
||||
|
||||
// Non-existent keys.
|
||||
tr = New(2, less)
|
||||
for _, v := range p {
|
||||
tr.Set(v.Key.(int)*2, v.Value)
|
||||
}
|
||||
// tr has only even keys: 0, 2, 4, ... Iterate from odd keys.
|
||||
for i := -1; i <= size+1; i += 2 {
|
||||
it := tr.Before(i)
|
||||
got := all(it)
|
||||
var want []itemWithIndex
|
||||
for j := (i + 1) / 2; j < size; j++ {
|
||||
want = append(want, itemWithIndex{j * 2, j, j})
|
||||
}
|
||||
if !cmp.Equal(got, want) {
|
||||
tr.print(os.Stdout)
|
||||
t.Fatalf("%d: got %+v\nwant %+v\n", i, got, want)
|
||||
}
|
||||
|
||||
it = tr.After(i)
|
||||
got = all(it)
|
||||
want = nil
|
||||
for j := (i - 1) / 2; j >= 0; j-- {
|
||||
want = append(want, itemWithIndex{j * 2, j, j})
|
||||
}
|
||||
if !cmp.Equal(got, want) {
|
||||
t.Fatalf("%d: got %+v\nwant %+v\n", i, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMixed(t *testing.T) {
|
||||
// Test random, mixed insertions and deletions.
|
||||
const maxSize = 1000
|
||||
tr := New(3, less)
|
||||
has := map[int]bool{}
|
||||
for i := 0; i < 10000; i++ {
|
||||
r := rand.Intn(maxSize)
|
||||
if r >= tr.Len() {
|
||||
old, ok := tr.Set(r, r)
|
||||
if has[r] != ok {
|
||||
t.Fatalf("%d: has=%t, ok=%t", r, has[r], ok)
|
||||
}
|
||||
if ok && old.(int) != r {
|
||||
t.Fatalf("%d: bad old", r)
|
||||
}
|
||||
has[r] = true
|
||||
if got, want := tr.Get(r), r; got != want {
|
||||
t.Fatalf("Get(%d) = %d, want %d", r, got, want)
|
||||
}
|
||||
} else {
|
||||
// Expoit random map iteration order.
|
||||
var d int
|
||||
for d = range has {
|
||||
break
|
||||
}
|
||||
old, removed := tr.Delete(d)
|
||||
if !removed {
|
||||
t.Fatalf("%d not found", d)
|
||||
}
|
||||
if old.(int) != d {
|
||||
t.Fatalf("%d: bad old", d)
|
||||
}
|
||||
delete(has, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cloneTestSize = 10000
|
||||
|
||||
func cloneTest(t *testing.T, b *BTree, start int, p []itemWithIndex, wg *sync.WaitGroup, treec chan<- *BTree) {
|
||||
treec <- b
|
||||
for i := start; i < cloneTestSize; i++ {
|
||||
b.Set(p[i].Key, p[i].Value)
|
||||
if i%(cloneTestSize/5) == 0 {
|
||||
wg.Add(1)
|
||||
go cloneTest(t, b.Clone(), i+1, p, wg, treec)
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func TestCloneConcurrentOperations(t *testing.T) {
|
||||
b := New(*btreeDegree, less)
|
||||
treec := make(chan *BTree)
|
||||
p := perm(cloneTestSize)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go cloneTest(t, b, 0, p, &wg, treec)
|
||||
var trees []*BTree
|
||||
donec := make(chan struct{})
|
||||
go func() {
|
||||
for t := range treec {
|
||||
trees = append(trees, t)
|
||||
}
|
||||
close(donec)
|
||||
}()
|
||||
wg.Wait()
|
||||
close(treec)
|
||||
<-donec
|
||||
want := rang(cloneTestSize)
|
||||
for i, tree := range trees {
|
||||
if !cmp.Equal(want, all(tree.BeforeIndex(0))) {
|
||||
t.Errorf("tree %v mismatch", i)
|
||||
}
|
||||
}
|
||||
toRemove := rang(cloneTestSize)[cloneTestSize/2:]
|
||||
for i := 0; i < len(trees)/2; i++ {
|
||||
tree := trees[i]
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
for _, m := range toRemove {
|
||||
tree.Delete(m.Key)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
for i, tree := range trees {
|
||||
var wantpart []itemWithIndex
|
||||
if i < len(trees)/2 {
|
||||
wantpart = want[:cloneTestSize/2]
|
||||
} else {
|
||||
wantpart = want
|
||||
}
|
||||
if got := all(tree.BeforeIndex(0)); !cmp.Equal(wantpart, got) {
|
||||
t.Errorf("tree %v mismatch, want %v got %v", i, len(want), len(got))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func less(a, b interface{}) bool { return a.(int) < b.(int) }
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package btree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (t *BTree) print(w io.Writer) {
|
||||
t.root.print(w, 0)
|
||||
}
|
||||
|
||||
func (n *node) print(w io.Writer, level int) {
|
||||
indent := strings.Repeat(" ", level)
|
||||
if n == nil {
|
||||
fmt.Fprintf(w, "%s<nil>\n", indent)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "%s%v\n", indent, n.items)
|
||||
for _, c := range n.children {
|
||||
c.print(w, level+1)
|
||||
}
|
||||
}
|
||||
+468
@@ -0,0 +1,468 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package fields provides a view of the fields of a struct that follows the Go
|
||||
// rules, amended to consider tags and case insensitivity.
|
||||
//
|
||||
// Usage
|
||||
//
|
||||
// First define a function that interprets tags:
|
||||
//
|
||||
// func parseTag(st reflect.StructTag) (name string, keep bool, other interface{}, err error) { ... }
|
||||
//
|
||||
// The function's return values describe whether to ignore the field
|
||||
// completely or provide an alternate name, as well as other data from the
|
||||
// parse that is stored to avoid re-parsing.
|
||||
//
|
||||
// Then define a function to validate the type:
|
||||
//
|
||||
// func validate(t reflect.Type) error { ... }
|
||||
//
|
||||
// Then, if necessary, define a function to specify leaf types - types
|
||||
// which should be considered one field and not be recursed into:
|
||||
//
|
||||
// func isLeafType(t reflect.Type) bool { ... }
|
||||
//
|
||||
// eg:
|
||||
//
|
||||
// func isLeafType(t reflect.Type) bool {
|
||||
// return t == reflect.TypeOf(time.Time{})
|
||||
// }
|
||||
//
|
||||
// Next, construct a Cache, passing your functions. As its name suggests, a
|
||||
// Cache remembers validation and field information for a type, so subsequent
|
||||
// calls with the same type are very fast.
|
||||
//
|
||||
// cache := fields.NewCache(parseTag, validate, isLeafType)
|
||||
//
|
||||
// To get the fields of a struct type as determined by the above rules, call
|
||||
// the Fields method:
|
||||
//
|
||||
// fields, err := cache.Fields(reflect.TypeOf(MyStruct{}))
|
||||
//
|
||||
// The return value can be treated as a slice of Fields.
|
||||
//
|
||||
// Given a string, such as a key or column name obtained during unmarshalling,
|
||||
// call Match on the list of fields to find a field whose name is the best
|
||||
// match:
|
||||
//
|
||||
// field := fields.Match(name)
|
||||
//
|
||||
// Match looks for an exact match first, then falls back to a case-insensitive
|
||||
// comparison.
|
||||
package fields
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"cloud.google.com/go/internal/atomiccache"
|
||||
)
|
||||
|
||||
// A Field records information about a struct field.
|
||||
type Field struct {
|
||||
Name string // effective field name
|
||||
NameFromTag bool // did Name come from a tag?
|
||||
Type reflect.Type // field type
|
||||
Index []int // index sequence, for reflect.Value.FieldByIndex
|
||||
ParsedTag interface{} // third return value of the parseTag function
|
||||
|
||||
nameBytes []byte
|
||||
equalFold func(s, t []byte) bool
|
||||
}
|
||||
|
||||
type ParseTagFunc func(reflect.StructTag) (name string, keep bool, other interface{}, err error)
|
||||
|
||||
type ValidateFunc func(reflect.Type) error
|
||||
|
||||
type LeafTypesFunc func(reflect.Type) bool
|
||||
|
||||
// A Cache records information about the fields of struct types.
|
||||
//
|
||||
// A Cache is safe for use by multiple goroutines.
|
||||
type Cache struct {
|
||||
parseTag ParseTagFunc
|
||||
validate ValidateFunc
|
||||
leafTypes LeafTypesFunc
|
||||
cache atomiccache.Cache // from reflect.Type to cacheValue
|
||||
}
|
||||
|
||||
// NewCache constructs a Cache.
|
||||
//
|
||||
// Its first argument should be a function that accepts
|
||||
// a struct tag and returns four values: an alternative name for the field
|
||||
// extracted from the tag, a boolean saying whether to keep the field or ignore
|
||||
// it, additional data that is stored with the field information to avoid
|
||||
// having to parse the tag again, and an error.
|
||||
//
|
||||
// Its second argument should be a function that accepts a reflect.Type and
|
||||
// returns an error if the struct type is invalid in any way. For example, it
|
||||
// may check that all of the struct field tags are valid, or that all fields
|
||||
// are of an appropriate type.
|
||||
func NewCache(parseTag ParseTagFunc, validate ValidateFunc, leafTypes LeafTypesFunc) *Cache {
|
||||
if parseTag == nil {
|
||||
parseTag = func(reflect.StructTag) (string, bool, interface{}, error) {
|
||||
return "", true, nil, nil
|
||||
}
|
||||
}
|
||||
if validate == nil {
|
||||
validate = func(reflect.Type) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if leafTypes == nil {
|
||||
leafTypes = func(reflect.Type) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return &Cache{
|
||||
parseTag: parseTag,
|
||||
validate: validate,
|
||||
leafTypes: leafTypes,
|
||||
}
|
||||
}
|
||||
|
||||
// A fieldScan represents an item on the fieldByNameFunc scan work list.
|
||||
type fieldScan struct {
|
||||
typ reflect.Type
|
||||
index []int
|
||||
}
|
||||
|
||||
// Fields returns all the exported fields of t, which must be a struct type. It
|
||||
// follows the standard Go rules for embedded fields, modified by the presence
|
||||
// of tags. The result is sorted lexicographically by index.
|
||||
//
|
||||
// These rules apply in the absence of tags:
|
||||
// Anonymous struct fields are treated as if their inner exported fields were
|
||||
// fields in the outer struct (embedding). The result includes all fields that
|
||||
// aren't shadowed by fields at higher level of embedding. If more than one
|
||||
// field with the same name exists at the same level of embedding, it is
|
||||
// excluded. An anonymous field that is not of struct type is treated as having
|
||||
// its type as its name.
|
||||
//
|
||||
// Tags modify these rules as follows:
|
||||
// A field's tag is used as its name.
|
||||
// An anonymous struct field with a name given in its tag is treated as
|
||||
// a field having that name, rather than an embedded struct (the struct's
|
||||
// fields will not be returned).
|
||||
// If more than one field with the same name exists at the same level of embedding,
|
||||
// but exactly one of them is tagged, then the tagged field is reported and the others
|
||||
// are ignored.
|
||||
func (c *Cache) Fields(t reflect.Type) (List, error) {
|
||||
if t.Kind() != reflect.Struct {
|
||||
panic("fields: Fields of non-struct type")
|
||||
}
|
||||
return c.cachedTypeFields(t)
|
||||
}
|
||||
|
||||
// A List is a list of Fields.
|
||||
type List []Field
|
||||
|
||||
// Match returns the field in the list whose name best matches the supplied
|
||||
// name, nor nil if no field does. If there is a field with the exact name, it
|
||||
// is returned. Otherwise the first field (sorted by index) whose name matches
|
||||
// case-insensitively is returned.
|
||||
func (l List) Match(name string) *Field {
|
||||
return l.MatchBytes([]byte(name))
|
||||
}
|
||||
|
||||
// MatchBytes is identical to Match, except that the argument is a byte slice.
|
||||
func (l List) MatchBytes(name []byte) *Field {
|
||||
var f *Field
|
||||
for i := range l {
|
||||
ff := &l[i]
|
||||
if bytes.Equal(ff.nameBytes, name) {
|
||||
return ff
|
||||
}
|
||||
if f == nil && ff.equalFold(ff.nameBytes, name) {
|
||||
f = ff
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
type cacheValue struct {
|
||||
fields List
|
||||
err error
|
||||
}
|
||||
|
||||
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
|
||||
// This code has been copied and modified from
|
||||
// https://go.googlesource.com/go/+/go1.7.3/src/encoding/json/encode.go.
|
||||
func (c *Cache) cachedTypeFields(t reflect.Type) (List, error) {
|
||||
cv := c.cache.Get(t, func() interface{} {
|
||||
if err := c.validate(t); err != nil {
|
||||
return cacheValue{nil, err}
|
||||
}
|
||||
f, err := c.typeFields(t)
|
||||
return cacheValue{List(f), err}
|
||||
}).(cacheValue)
|
||||
return cv.fields, cv.err
|
||||
}
|
||||
|
||||
func (c *Cache) typeFields(t reflect.Type) ([]Field, error) {
|
||||
fields, err := c.listFields(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Sort(byName(fields))
|
||||
// Delete all fields that are hidden by the Go rules for embedded fields.
|
||||
|
||||
// The fields are sorted in primary order of name, secondary order of field
|
||||
// index length. So the first field with a given name is the dominant one.
|
||||
var out []Field
|
||||
for advance, i := 0, 0; i < len(fields); i += advance {
|
||||
// One iteration per name.
|
||||
// Find the sequence of fields with the name of this first field.
|
||||
fi := fields[i]
|
||||
name := fi.Name
|
||||
for advance = 1; i+advance < len(fields); advance++ {
|
||||
fj := fields[i+advance]
|
||||
if fj.Name != name {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Find the dominant field, if any, out of all fields that have the same name.
|
||||
dominant, ok := dominantField(fields[i : i+advance])
|
||||
if ok {
|
||||
out = append(out, dominant)
|
||||
}
|
||||
}
|
||||
sort.Sort(byIndex(out))
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *Cache) listFields(t reflect.Type) ([]Field, error) {
|
||||
// This uses the same condition that the Go language does: there must be a unique instance
|
||||
// of the match at a given depth level. If there are multiple instances of a match at the
|
||||
// same depth, they annihilate each other and inhibit any possible match at a lower level.
|
||||
// The algorithm is breadth first search, one depth level at a time.
|
||||
|
||||
// The current and next slices are work queues:
|
||||
// current lists the fields to visit on this depth level,
|
||||
// and next lists the fields on the next lower level.
|
||||
current := []fieldScan{}
|
||||
next := []fieldScan{{typ: t}}
|
||||
|
||||
// nextCount records the number of times an embedded type has been
|
||||
// encountered and considered for queueing in the 'next' slice.
|
||||
// We only queue the first one, but we increment the count on each.
|
||||
// If a struct type T can be reached more than once at a given depth level,
|
||||
// then it annihilates itself and need not be considered at all when we
|
||||
// process that next depth level.
|
||||
var nextCount map[reflect.Type]int
|
||||
|
||||
// visited records the structs that have been considered already.
|
||||
// Embedded pointer fields can create cycles in the graph of
|
||||
// reachable embedded types; visited avoids following those cycles.
|
||||
// It also avoids duplicated effort: if we didn't find the field in an
|
||||
// embedded type T at level 2, we won't find it in one at level 4 either.
|
||||
visited := map[reflect.Type]bool{}
|
||||
|
||||
var fields []Field // Fields found.
|
||||
|
||||
for len(next) > 0 {
|
||||
current, next = next, current[:0]
|
||||
count := nextCount
|
||||
nextCount = nil
|
||||
|
||||
// Process all the fields at this depth, now listed in 'current'.
|
||||
// The loop queues embedded fields found in 'next', for processing during the next
|
||||
// iteration. The multiplicity of the 'current' field counts is recorded
|
||||
// in 'count'; the multiplicity of the 'next' field counts is recorded in 'nextCount'.
|
||||
for _, scan := range current {
|
||||
t := scan.typ
|
||||
if visited[t] {
|
||||
// We've looked through this type before, at a higher level.
|
||||
// That higher level would shadow the lower level we're now at,
|
||||
// so this one can't be useful to us. Ignore it.
|
||||
continue
|
||||
}
|
||||
visited[t] = true
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
|
||||
exported := (f.PkgPath == "")
|
||||
|
||||
// If a named field is unexported, ignore it. An anonymous
|
||||
// unexported field is processed, because it may contain
|
||||
// exported fields, which are visible.
|
||||
if !exported && !f.Anonymous {
|
||||
continue
|
||||
}
|
||||
|
||||
// Examine the tag.
|
||||
tagName, keep, other, err := c.parseTag(f.Tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !keep {
|
||||
continue
|
||||
}
|
||||
if c.leafTypes(f.Type) {
|
||||
fields = append(fields, newField(f, tagName, other, scan.index, i))
|
||||
continue
|
||||
}
|
||||
|
||||
var ntyp reflect.Type
|
||||
if f.Anonymous {
|
||||
// Anonymous field of type T or *T.
|
||||
ntyp = f.Type
|
||||
if ntyp.Kind() == reflect.Ptr {
|
||||
ntyp = ntyp.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Record fields with a tag name, non-anonymous fields, or
|
||||
// anonymous non-struct fields.
|
||||
if tagName != "" || ntyp == nil || ntyp.Kind() != reflect.Struct {
|
||||
if !exported {
|
||||
continue
|
||||
}
|
||||
fields = append(fields, newField(f, tagName, other, scan.index, i))
|
||||
if count[t] > 1 {
|
||||
// If there were multiple instances, add a second,
|
||||
// so that the annihilation code will see a duplicate.
|
||||
fields = append(fields, fields[len(fields)-1])
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Queue embedded struct fields for processing with next level,
|
||||
// but only if the embedded types haven't already been queued.
|
||||
if nextCount[ntyp] > 0 {
|
||||
nextCount[ntyp] = 2 // exact multiple doesn't matter
|
||||
continue
|
||||
}
|
||||
if nextCount == nil {
|
||||
nextCount = map[reflect.Type]int{}
|
||||
}
|
||||
nextCount[ntyp] = 1
|
||||
if count[t] > 1 {
|
||||
nextCount[ntyp] = 2 // exact multiple doesn't matter
|
||||
}
|
||||
var index []int
|
||||
index = append(index, scan.index...)
|
||||
index = append(index, i)
|
||||
next = append(next, fieldScan{ntyp, index})
|
||||
}
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func newField(f reflect.StructField, tagName string, other interface{}, index []int, i int) Field {
|
||||
name := tagName
|
||||
if name == "" {
|
||||
name = f.Name
|
||||
}
|
||||
sf := Field{
|
||||
Name: name,
|
||||
NameFromTag: tagName != "",
|
||||
Type: f.Type,
|
||||
ParsedTag: other,
|
||||
nameBytes: []byte(name),
|
||||
}
|
||||
sf.equalFold = foldFunc(sf.nameBytes)
|
||||
sf.Index = append(sf.Index, index...)
|
||||
sf.Index = append(sf.Index, i)
|
||||
return sf
|
||||
}
|
||||
|
||||
// byName sorts fields using the following criteria, in order:
|
||||
// 1. name
|
||||
// 2. embedding depth
|
||||
// 3. tag presence (preferring a tagged field)
|
||||
// 4. index sequence.
|
||||
type byName []Field
|
||||
|
||||
func (x byName) Len() int { return len(x) }
|
||||
|
||||
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
func (x byName) Less(i, j int) bool {
|
||||
if x[i].Name != x[j].Name {
|
||||
return x[i].Name < x[j].Name
|
||||
}
|
||||
if len(x[i].Index) != len(x[j].Index) {
|
||||
return len(x[i].Index) < len(x[j].Index)
|
||||
}
|
||||
if x[i].NameFromTag != x[j].NameFromTag {
|
||||
return x[i].NameFromTag
|
||||
}
|
||||
return byIndex(x).Less(i, j)
|
||||
}
|
||||
|
||||
// byIndex sorts field by index sequence.
|
||||
type byIndex []Field
|
||||
|
||||
func (x byIndex) Len() int { return len(x) }
|
||||
|
||||
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
func (x byIndex) Less(i, j int) bool {
|
||||
xi := x[i].Index
|
||||
xj := x[j].Index
|
||||
ln := len(xi)
|
||||
if l := len(xj); l < ln {
|
||||
ln = l
|
||||
}
|
||||
for k := 0; k < ln; k++ {
|
||||
if xi[k] != xj[k] {
|
||||
return xi[k] < xj[k]
|
||||
}
|
||||
}
|
||||
return len(xi) < len(xj)
|
||||
}
|
||||
|
||||
// dominantField looks through the fields, all of which are known to have the
|
||||
// same name, to find the single field that dominates the others using Go's
|
||||
// embedding rules, modified by the presence of tags. If there are multiple
|
||||
// top-level fields, the boolean will be false: This condition is an error in
|
||||
// Go and we skip all the fields.
|
||||
func dominantField(fs []Field) (Field, bool) {
|
||||
// The fields are sorted in increasing index-length order, then by presence of tag.
|
||||
// That means that the first field is the dominant one. We need only check
|
||||
// for error cases: two fields at top level, either both tagged or neither tagged.
|
||||
if len(fs) > 1 && len(fs[0].Index) == len(fs[1].Index) && fs[0].NameFromTag == fs[1].NameFromTag {
|
||||
return Field{}, false
|
||||
}
|
||||
return fs[0], true
|
||||
}
|
||||
|
||||
// ParseStandardTag extracts the sub-tag named by key, then parses it using the
|
||||
// de facto standard format introduced in encoding/json:
|
||||
// "-" means "ignore this tag". It must occur by itself. (parseStandardTag returns an error
|
||||
// in this case, whereas encoding/json accepts the "-" even if it is not alone.)
|
||||
// "<name>" provides an alternative name for the field
|
||||
// "<name>,opt1,opt2,..." specifies options after the name.
|
||||
// The options are returned as a []string.
|
||||
func ParseStandardTag(key string, t reflect.StructTag) (name string, keep bool, options []string, err error) {
|
||||
s := t.Get(key)
|
||||
parts := strings.Split(s, ",")
|
||||
if parts[0] == "-" {
|
||||
if len(parts) > 1 {
|
||||
return "", false, nil, errors.New(`"-" field tag with options`)
|
||||
}
|
||||
return "", false, nil, nil
|
||||
}
|
||||
if len(parts) > 1 {
|
||||
options = parts[1:]
|
||||
}
|
||||
return parts[0], true, options, nil
|
||||
}
|
||||
+563
@@ -0,0 +1,563 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fields
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
)
|
||||
|
||||
type embed1 struct {
|
||||
Em1 int
|
||||
Dup int // annihilates with embed2.Dup
|
||||
Shadow int
|
||||
embed3
|
||||
}
|
||||
|
||||
type embed2 struct {
|
||||
Dup int
|
||||
embed3
|
||||
embed4
|
||||
}
|
||||
|
||||
type embed3 struct {
|
||||
Em3 int // annihilated because embed3 is in both embed1 and embed2
|
||||
embed5
|
||||
}
|
||||
|
||||
type embed4 struct {
|
||||
Em4 int
|
||||
Dup int // annihilation of Dup in embed1, embed2 hides this Dup
|
||||
*embed1 // ignored because it occurs at a higher level
|
||||
}
|
||||
|
||||
type embed5 struct {
|
||||
x int
|
||||
}
|
||||
|
||||
type Anonymous int
|
||||
|
||||
type S1 struct {
|
||||
Exported int
|
||||
unexported int
|
||||
Shadow int // shadows S1.Shadow
|
||||
embed1
|
||||
*embed2
|
||||
Anonymous
|
||||
}
|
||||
|
||||
type Time struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
var intType = reflect.TypeOf(int(0))
|
||||
|
||||
func field(name string, tval interface{}, index ...int) *Field {
|
||||
return &Field{
|
||||
Name: name,
|
||||
Type: reflect.TypeOf(tval),
|
||||
Index: index,
|
||||
ParsedTag: []string(nil),
|
||||
}
|
||||
}
|
||||
|
||||
func tfield(name string, tval interface{}, index ...int) *Field {
|
||||
return &Field{
|
||||
Name: name,
|
||||
Type: reflect.TypeOf(tval),
|
||||
Index: index,
|
||||
NameFromTag: true,
|
||||
ParsedTag: []string(nil),
|
||||
}
|
||||
}
|
||||
|
||||
func TestFieldsNoTags(t *testing.T) {
|
||||
c := NewCache(nil, nil, nil)
|
||||
got, err := c.Fields(reflect.TypeOf(S1{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []*Field{
|
||||
field("Exported", int(0), 0),
|
||||
field("Shadow", int(0), 2),
|
||||
field("Em1", int(0), 3, 0),
|
||||
field("Em4", int(0), 4, 2, 0),
|
||||
field("Anonymous", Anonymous(0), 5),
|
||||
}
|
||||
for _, f := range want {
|
||||
f.ParsedTag = nil
|
||||
}
|
||||
if msg, ok := compareFields(got, want); !ok {
|
||||
t.Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgainstJSONEncodingNoTags(t *testing.T) {
|
||||
// Demonstrates that this package produces the same set of fields as encoding/json.
|
||||
s1 := S1{
|
||||
Exported: 1,
|
||||
unexported: 2,
|
||||
Shadow: 3,
|
||||
embed1: embed1{
|
||||
Em1: 4,
|
||||
Dup: 5,
|
||||
Shadow: 6,
|
||||
embed3: embed3{
|
||||
Em3: 7,
|
||||
embed5: embed5{x: 8},
|
||||
},
|
||||
},
|
||||
embed2: &embed2{
|
||||
Dup: 9,
|
||||
embed3: embed3{
|
||||
Em3: 10,
|
||||
embed5: embed5{x: 11},
|
||||
},
|
||||
embed4: embed4{
|
||||
Em4: 12,
|
||||
Dup: 13,
|
||||
embed1: &embed1{Em1: 14},
|
||||
},
|
||||
},
|
||||
Anonymous: Anonymous(15),
|
||||
}
|
||||
var want S1
|
||||
want.embed2 = &embed2{} // need this because reflection won't create it
|
||||
jsonRoundTrip(t, s1, &want)
|
||||
var got S1
|
||||
got.embed2 = &embed2{}
|
||||
fields, err := NewCache(nil, nil, nil).Fields(reflect.TypeOf(got))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
setFields(fields, &got, s1)
|
||||
if !testutil.Equal(got, want,
|
||||
cmp.AllowUnexported(S1{}, embed1{}, embed2{}, embed3{}, embed4{}, embed5{})) {
|
||||
t.Errorf("got\n%+v\nwant\n%+v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests use of LeafTypes parameter to NewCache
|
||||
func TestAgainstJSONEncodingEmbeddedTime(t *testing.T) {
|
||||
timeLeafFn := func(t reflect.Type) bool {
|
||||
return t == reflect.TypeOf(time.Time{})
|
||||
}
|
||||
// Demonstrates that this package can produce the same set of
|
||||
// fields as encoding/json for a struct with an embedded time.Time.
|
||||
now := time.Now().UTC()
|
||||
myt := Time{
|
||||
now,
|
||||
}
|
||||
var want Time
|
||||
jsonRoundTrip(t, myt, &want)
|
||||
var got Time
|
||||
fields, err := NewCache(nil, nil, timeLeafFn).Fields(reflect.TypeOf(got))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
setFields(fields, &got, myt)
|
||||
if !testutil.Equal(got, want) {
|
||||
t.Errorf("got\n%+v\nwant\n%+v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
type S2 struct {
|
||||
NoTag int
|
||||
XXX int `json:"tag"` // tag name takes precedence
|
||||
Anonymous `json:"anon"` // anonymous non-structs also get their name from the tag
|
||||
unexported int `json:"tag"`
|
||||
Embed `json:"em"` // embedded structs with tags become fields
|
||||
Tag int
|
||||
YYY int `json:"Tag"` // tag takes precedence over untagged field of the same name
|
||||
Empty int `json:""` // empty tag is noop
|
||||
tEmbed1
|
||||
tEmbed2
|
||||
}
|
||||
|
||||
type Embed struct {
|
||||
Em int
|
||||
}
|
||||
|
||||
type tEmbed1 struct {
|
||||
Dup int
|
||||
X int `json:"Dup2"`
|
||||
}
|
||||
|
||||
type tEmbed2 struct {
|
||||
Y int `json:"Dup"` // takes precedence over tEmbed1.Dup because it is tagged
|
||||
Z int `json:"Dup2"` // same name as tEmbed1.X and both tagged, so ignored
|
||||
}
|
||||
|
||||
func jsonTagParser(t reflect.StructTag) (name string, keep bool, other interface{}, err error) {
|
||||
return ParseStandardTag("json", t)
|
||||
}
|
||||
|
||||
func validateFunc(t reflect.Type) (err error) {
|
||||
if t.Kind() != reflect.Struct {
|
||||
return errors.New("non-struct type used")
|
||||
}
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
if t.Field(i).Type.Kind() == reflect.Slice {
|
||||
return fmt.Errorf("slice field found at field %s on struct %s", t.Field(i).Name, t.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestFieldsWithTags(t *testing.T) {
|
||||
got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S2{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []*Field{
|
||||
field("NoTag", int(0), 0),
|
||||
tfield("tag", int(0), 1),
|
||||
tfield("anon", Anonymous(0), 2),
|
||||
tfield("em", Embed{}, 4),
|
||||
tfield("Tag", int(0), 6),
|
||||
field("Empty", int(0), 7),
|
||||
tfield("Dup", int(0), 8, 0),
|
||||
}
|
||||
if msg, ok := compareFields(got, want); !ok {
|
||||
t.Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgainstJSONEncodingWithTags(t *testing.T) {
|
||||
// Demonstrates that this package produces the same set of fields as encoding/json.
|
||||
s2 := S2{
|
||||
NoTag: 1,
|
||||
XXX: 2,
|
||||
Anonymous: 3,
|
||||
Embed: Embed{
|
||||
Em: 4,
|
||||
},
|
||||
tEmbed1: tEmbed1{
|
||||
Dup: 5,
|
||||
X: 6,
|
||||
},
|
||||
tEmbed2: tEmbed2{
|
||||
Y: 7,
|
||||
Z: 8,
|
||||
},
|
||||
}
|
||||
var want S2
|
||||
jsonRoundTrip(t, s2, &want)
|
||||
var got S2
|
||||
fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(got))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
setFields(fields, &got, s2)
|
||||
if !testutil.Equal(got, want, cmp.AllowUnexported(S2{})) {
|
||||
t.Errorf("got\n%+v\nwant\n%+v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnexportedAnonymousNonStruct(t *testing.T) {
|
||||
// An unexported anonymous non-struct field should not be recorded.
|
||||
// This is currently a bug in encoding/json.
|
||||
// https://github.com/golang/go/issues/18009
|
||||
type (
|
||||
u int
|
||||
v int
|
||||
S struct {
|
||||
u
|
||||
v `json:"x"`
|
||||
int
|
||||
}
|
||||
)
|
||||
|
||||
got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) != 0 {
|
||||
t.Errorf("got %d fields, want 0", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnexportedAnonymousStruct(t *testing.T) {
|
||||
// An unexported anonymous struct with a tag is ignored.
|
||||
// This is currently a bug in encoding/json.
|
||||
// https://github.com/golang/go/issues/18009
|
||||
type (
|
||||
s1 struct{ X int }
|
||||
S2 struct {
|
||||
s1 `json:"Y"`
|
||||
}
|
||||
)
|
||||
got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S2{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) != 0 {
|
||||
t.Errorf("got %d fields, want 0", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDominantField(t *testing.T) {
|
||||
// With fields sorted by index length and then by tag presence,
|
||||
// the dominant field is always the first. Make sure all error
|
||||
// cases are caught.
|
||||
for _, test := range []struct {
|
||||
fields []Field
|
||||
wantOK bool
|
||||
}{
|
||||
// A single field is OK.
|
||||
{[]Field{{Index: []int{0}}}, true},
|
||||
{[]Field{{Index: []int{0}, NameFromTag: true}}, true},
|
||||
// A single field at top level is OK.
|
||||
{[]Field{{Index: []int{0}}, {Index: []int{1, 0}}}, true},
|
||||
{[]Field{{Index: []int{0}}, {Index: []int{1, 0}, NameFromTag: true}}, true},
|
||||
{[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1, 0}, NameFromTag: true}}, true},
|
||||
// A single tagged field is OK.
|
||||
{[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1}}}, true},
|
||||
// Two untagged fields at the same level is an error.
|
||||
{[]Field{{Index: []int{0}}, {Index: []int{1}}}, false},
|
||||
// Two tagged fields at the same level is an error.
|
||||
{[]Field{{Index: []int{0}, NameFromTag: true}, {Index: []int{1}, NameFromTag: true}}, false},
|
||||
} {
|
||||
_, gotOK := dominantField(test.fields)
|
||||
if gotOK != test.wantOK {
|
||||
t.Errorf("%v: got %t, want %t", test.fields, gotOK, test.wantOK)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnore(t *testing.T) {
|
||||
type S struct {
|
||||
X int `json:"-"`
|
||||
}
|
||||
got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) != 0 {
|
||||
t.Errorf("got %d fields, want 0", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsedTag(t *testing.T) {
|
||||
type S struct {
|
||||
X int `json:"name,omitempty"`
|
||||
}
|
||||
got, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := []*Field{
|
||||
{Name: "name", NameFromTag: true, Type: intType,
|
||||
Index: []int{0}, ParsedTag: []string{"omitempty"}},
|
||||
}
|
||||
if msg, ok := compareFields(got, want); !ok {
|
||||
t.Error(msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateFunc(t *testing.T) {
|
||||
type MyInvalidStruct struct {
|
||||
A string
|
||||
B []int
|
||||
}
|
||||
|
||||
_, err := NewCache(nil, validateFunc, nil).Fields(reflect.TypeOf(MyInvalidStruct{}))
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
|
||||
type MyValidStruct struct {
|
||||
A string
|
||||
B int
|
||||
}
|
||||
_, err = NewCache(nil, validateFunc, nil).Fields(reflect.TypeOf(MyValidStruct{}))
|
||||
if err != nil {
|
||||
t.Fatalf("expected nil, got error: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func compareFields(got []Field, want []*Field) (msg string, ok bool) {
|
||||
if len(got) != len(want) {
|
||||
return fmt.Sprintf("got %d fields, want %d", len(got), len(want)), false
|
||||
}
|
||||
for i, g := range got {
|
||||
w := *want[i]
|
||||
if !fieldsEqual(&g, &w) {
|
||||
return fmt.Sprintf("got\n%+v\nwant\n%+v", g, w), false
|
||||
}
|
||||
}
|
||||
return "", true
|
||||
}
|
||||
|
||||
// Need this because Field contains a function, which cannot be compared even
|
||||
// by testutil.Equal.
|
||||
func fieldsEqual(f1, f2 *Field) bool {
|
||||
if f1 == nil || f2 == nil {
|
||||
return f1 == f2
|
||||
}
|
||||
return f1.Name == f2.Name &&
|
||||
f1.NameFromTag == f2.NameFromTag &&
|
||||
f1.Type == f2.Type &&
|
||||
testutil.Equal(f1.ParsedTag, f2.ParsedTag)
|
||||
}
|
||||
|
||||
// Set the fields of dst from those of src.
|
||||
// dst must be a pointer to a struct value.
|
||||
// src must be a struct value.
|
||||
func setFields(fields []Field, dst, src interface{}) {
|
||||
vsrc := reflect.ValueOf(src)
|
||||
vdst := reflect.ValueOf(dst).Elem()
|
||||
for _, f := range fields {
|
||||
fdst := vdst.FieldByIndex(f.Index)
|
||||
fsrc := vsrc.FieldByIndex(f.Index)
|
||||
fdst.Set(fsrc)
|
||||
}
|
||||
}
|
||||
|
||||
func jsonRoundTrip(t *testing.T, in, out interface{}) {
|
||||
bytes, err := json.Marshal(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := json.Unmarshal(bytes, out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
type S3 struct {
|
||||
S4
|
||||
Abc int
|
||||
AbC int
|
||||
Tag int
|
||||
X int `json:"Tag"`
|
||||
unexported int
|
||||
}
|
||||
|
||||
type S4 struct {
|
||||
ABc int
|
||||
Y int `json:"Abc"` // ignored because of top-level Abc
|
||||
}
|
||||
|
||||
func TestMatchingField(t *testing.T) {
|
||||
fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S3{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
want *Field
|
||||
}{
|
||||
// Exact match wins.
|
||||
{"Abc", field("Abc", int(0), 1)},
|
||||
{"AbC", field("AbC", int(0), 2)},
|
||||
{"ABc", field("ABc", int(0), 0, 0)},
|
||||
// If there are multiple matches but no exact match or tag,
|
||||
// the first field wins, lexicographically by index.
|
||||
// Here, "ABc" is at a deeper embedding level, but since S4 appears
|
||||
// first in S3, its index precedes the other fields of S3.
|
||||
{"abc", field("ABc", int(0), 0, 0)},
|
||||
// Tag name takes precedence over untagged field of the same name.
|
||||
{"Tag", tfield("Tag", int(0), 4)},
|
||||
// Unexported fields disappear.
|
||||
{"unexported", nil},
|
||||
// Untagged embedded structs disappear.
|
||||
{"S4", nil},
|
||||
} {
|
||||
if got := fields.Match(test.name); !fieldsEqual(got, test.want) {
|
||||
t.Errorf("match %q:\ngot %+v\nwant %+v", test.name, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAgainstJSONMatchingField(t *testing.T) {
|
||||
s3 := S3{
|
||||
S4: S4{ABc: 1, Y: 2},
|
||||
Abc: 3,
|
||||
AbC: 4,
|
||||
Tag: 5,
|
||||
X: 6,
|
||||
unexported: 7,
|
||||
}
|
||||
var want S3
|
||||
jsonRoundTrip(t, s3, &want)
|
||||
v := reflect.ValueOf(want)
|
||||
fields, err := NewCache(jsonTagParser, nil, nil).Fields(reflect.TypeOf(S3{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
got int
|
||||
}{
|
||||
{"Abc", 3},
|
||||
{"AbC", 4},
|
||||
{"ABc", 1},
|
||||
{"abc", 1},
|
||||
{"Tag", 6},
|
||||
} {
|
||||
f := fields.Match(test.name)
|
||||
if f == nil {
|
||||
t.Fatalf("%s: no match", test.name)
|
||||
}
|
||||
w := v.FieldByIndex(f.Index).Interface()
|
||||
if test.got != w {
|
||||
t.Errorf("%s: got %d, want %d", test.name, test.got, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagErrors(t *testing.T) {
|
||||
called := false
|
||||
c := NewCache(func(t reflect.StructTag) (string, bool, interface{}, error) {
|
||||
called = true
|
||||
s := t.Get("f")
|
||||
if s == "bad" {
|
||||
return "", false, nil, errors.New("error")
|
||||
}
|
||||
return s, true, nil, nil
|
||||
}, nil, nil)
|
||||
|
||||
type T struct {
|
||||
X int `f:"ok"`
|
||||
Y int `f:"bad"`
|
||||
}
|
||||
|
||||
_, err := c.Fields(reflect.TypeOf(T{}))
|
||||
if !called {
|
||||
t.Fatal("tag parser not called")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("want error, got nil")
|
||||
}
|
||||
// Second time, we should cache the error.
|
||||
called = false
|
||||
_, err = c.Fields(reflect.TypeOf(T{}))
|
||||
if called {
|
||||
t.Fatal("tag parser called on second time")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("want error, got nil")
|
||||
}
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fields
|
||||
|
||||
// This file was copied from https://go.googlesource.com/go/+/go1.7.3/src/encoding/json/fold.go.
|
||||
// Only the license and package were changed.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
caseMask = ^byte(0x20) // Mask to ignore case in ASCII.
|
||||
kelvin = '\u212a'
|
||||
smallLongEss = '\u017f'
|
||||
)
|
||||
|
||||
// foldFunc returns one of four different case folding equivalence
|
||||
// functions, from most general (and slow) to fastest:
|
||||
//
|
||||
// 1) bytes.EqualFold, if the key s contains any non-ASCII UTF-8
|
||||
// 2) equalFoldRight, if s contains special folding ASCII ('k', 'K', 's', 'S')
|
||||
// 3) asciiEqualFold, no special, but includes non-letters (including _)
|
||||
// 4) simpleLetterEqualFold, no specials, no non-letters.
|
||||
//
|
||||
// The letters S and K are special because they map to 3 runes, not just 2:
|
||||
// * S maps to s and to U+017F 'ſ' Latin small letter long s
|
||||
// * k maps to K and to U+212A 'K' Kelvin sign
|
||||
// See https://play.golang.org/p/tTxjOc0OGo
|
||||
//
|
||||
// The returned function is specialized for matching against s and
|
||||
// should only be given s. It's not curried for performance reasons.
|
||||
func foldFunc(s []byte) func(s, t []byte) bool {
|
||||
nonLetter := false
|
||||
special := false // special letter
|
||||
for _, b := range s {
|
||||
if b >= utf8.RuneSelf {
|
||||
return bytes.EqualFold
|
||||
}
|
||||
upper := b & caseMask
|
||||
if upper < 'A' || upper > 'Z' {
|
||||
nonLetter = true
|
||||
} else if upper == 'K' || upper == 'S' {
|
||||
// See above for why these letters are special.
|
||||
special = true
|
||||
}
|
||||
}
|
||||
if special {
|
||||
return equalFoldRight
|
||||
}
|
||||
if nonLetter {
|
||||
return asciiEqualFold
|
||||
}
|
||||
return simpleLetterEqualFold
|
||||
}
|
||||
|
||||
// equalFoldRight is a specialization of bytes.EqualFold when s is
|
||||
// known to be all ASCII (including punctuation), but contains an 's',
|
||||
// 'S', 'k', or 'K', requiring a Unicode fold on the bytes in t.
|
||||
// See comments on foldFunc.
|
||||
func equalFoldRight(s, t []byte) bool {
|
||||
for _, sb := range s {
|
||||
if len(t) == 0 {
|
||||
return false
|
||||
}
|
||||
tb := t[0]
|
||||
if tb < utf8.RuneSelf {
|
||||
if sb != tb {
|
||||
sbUpper := sb & caseMask
|
||||
if 'A' <= sbUpper && sbUpper <= 'Z' {
|
||||
if sbUpper != tb&caseMask {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
t = t[1:]
|
||||
continue
|
||||
}
|
||||
// sb is ASCII and t is not. t must be either kelvin
|
||||
// sign or long s; sb must be s, S, k, or K.
|
||||
tr, size := utf8.DecodeRune(t)
|
||||
switch sb {
|
||||
case 's', 'S':
|
||||
if tr != smallLongEss {
|
||||
return false
|
||||
}
|
||||
case 'k', 'K':
|
||||
if tr != kelvin {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
t = t[size:]
|
||||
|
||||
}
|
||||
if len(t) > 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// asciiEqualFold is a specialization of bytes.EqualFold for use when
|
||||
// s is all ASCII (but may contain non-letters) and contains no
|
||||
// special-folding letters.
|
||||
// See comments on foldFunc.
|
||||
func asciiEqualFold(s, t []byte) bool {
|
||||
if len(s) != len(t) {
|
||||
return false
|
||||
}
|
||||
for i, sb := range s {
|
||||
tb := t[i]
|
||||
if sb == tb {
|
||||
continue
|
||||
}
|
||||
if ('a' <= sb && sb <= 'z') || ('A' <= sb && sb <= 'Z') {
|
||||
if sb&caseMask != tb&caseMask {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// simpleLetterEqualFold is a specialization of bytes.EqualFold for
|
||||
// use when s is all ASCII letters (no underscores, etc) and also
|
||||
// doesn't contain 'k', 'K', 's', or 'S'.
|
||||
// See comments on foldFunc.
|
||||
func simpleLetterEqualFold(s, t []byte) bool {
|
||||
if len(s) != len(t) {
|
||||
return false
|
||||
}
|
||||
for i, b := range s {
|
||||
if b&caseMask != t[i]&caseMask {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fields
|
||||
|
||||
// This file was copied from https://go.googlesource.com/go/+/go1.7.3/src/encoding/json/fold_test.go.
|
||||
// Only the license and package were changed.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var foldTests = []struct {
|
||||
fn func(s, t []byte) bool
|
||||
s, t string
|
||||
want bool
|
||||
}{
|
||||
{equalFoldRight, "", "", true},
|
||||
{equalFoldRight, "a", "a", true},
|
||||
{equalFoldRight, "", "a", false},
|
||||
{equalFoldRight, "a", "", false},
|
||||
{equalFoldRight, "a", "A", true},
|
||||
{equalFoldRight, "AB", "ab", true},
|
||||
{equalFoldRight, "AB", "ac", false},
|
||||
{equalFoldRight, "sbkKc", "ſbKKc", true},
|
||||
{equalFoldRight, "SbKkc", "ſbKKc", true},
|
||||
{equalFoldRight, "SbKkc", "ſbKK", false},
|
||||
{equalFoldRight, "e", "é", false},
|
||||
{equalFoldRight, "s", "S", true},
|
||||
|
||||
{simpleLetterEqualFold, "", "", true},
|
||||
{simpleLetterEqualFold, "abc", "abc", true},
|
||||
{simpleLetterEqualFold, "abc", "ABC", true},
|
||||
{simpleLetterEqualFold, "abc", "ABCD", false},
|
||||
{simpleLetterEqualFold, "abc", "xxx", false},
|
||||
|
||||
{asciiEqualFold, "a_B", "A_b", true},
|
||||
{asciiEqualFold, "aa@", "aa`", false}, // verify 0x40 and 0x60 aren't case-equivalent
|
||||
}
|
||||
|
||||
func TestFold(t *testing.T) {
|
||||
for i, tt := range foldTests {
|
||||
if got := tt.fn([]byte(tt.s), []byte(tt.t)); got != tt.want {
|
||||
t.Errorf("%d. %q, %q = %v; want %v", i, tt.s, tt.t, got, tt.want)
|
||||
}
|
||||
truth := strings.EqualFold(tt.s, tt.t)
|
||||
if truth != tt.want {
|
||||
t.Errorf("strings.EqualFold doesn't agree with case %d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFoldAgainstUnicode(t *testing.T) {
|
||||
const bufSize = 5
|
||||
buf1 := make([]byte, 0, bufSize)
|
||||
buf2 := make([]byte, 0, bufSize)
|
||||
var runes []rune
|
||||
for i := 0x20; i <= 0x7f; i++ {
|
||||
runes = append(runes, rune(i))
|
||||
}
|
||||
runes = append(runes, kelvin, smallLongEss)
|
||||
|
||||
funcs := []struct {
|
||||
name string
|
||||
fold func(s, t []byte) bool
|
||||
letter bool // must be ASCII letter
|
||||
simple bool // must be simple ASCII letter (not 'S' or 'K')
|
||||
}{
|
||||
{
|
||||
name: "equalFoldRight",
|
||||
fold: equalFoldRight,
|
||||
},
|
||||
{
|
||||
name: "asciiEqualFold",
|
||||
fold: asciiEqualFold,
|
||||
simple: true,
|
||||
},
|
||||
{
|
||||
name: "simpleLetterEqualFold",
|
||||
fold: simpleLetterEqualFold,
|
||||
simple: true,
|
||||
letter: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, ff := range funcs {
|
||||
for _, r := range runes {
|
||||
if r >= utf8.RuneSelf {
|
||||
continue
|
||||
}
|
||||
if ff.letter && !isASCIILetter(byte(r)) {
|
||||
continue
|
||||
}
|
||||
if ff.simple && (r == 's' || r == 'S' || r == 'k' || r == 'K') {
|
||||
continue
|
||||
}
|
||||
for _, r2 := range runes {
|
||||
buf1 := append(buf1[:0], 'x')
|
||||
buf2 := append(buf2[:0], 'x')
|
||||
buf1 = buf1[:1+utf8.EncodeRune(buf1[1:bufSize], r)]
|
||||
buf2 = buf2[:1+utf8.EncodeRune(buf2[1:bufSize], r2)]
|
||||
buf1 = append(buf1, 'x')
|
||||
buf2 = append(buf2, 'x')
|
||||
want := bytes.EqualFold(buf1, buf2)
|
||||
if got := ff.fold(buf1, buf2); got != want {
|
||||
t.Errorf("%s(%q, %q) = %v; want %v", ff.name, buf1, buf2, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isASCIILetter(b byte) bool {
|
||||
return ('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z')
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Fail on any error
|
||||
set -eo pipefail
|
||||
|
||||
# Display commands being run
|
||||
set -x
|
||||
|
||||
# cd to project dir on Kokoro instance
|
||||
cd git/gocloud
|
||||
|
||||
go version
|
||||
|
||||
# Set $GOPATH
|
||||
export GOPATH="$HOME/go"
|
||||
GOCLOUD_HOME=$GOPATH/src/cloud.google.com/go
|
||||
mkdir -p $GOCLOUD_HOME
|
||||
|
||||
# Move code into $GOPATH and get dependencies
|
||||
cp -R ./* $GOCLOUD_HOME
|
||||
cd $GOCLOUD_HOME
|
||||
go get -v ./...
|
||||
|
||||
# # Don't run integration tests until we can protect against code from
|
||||
# # untrusted forks reading and storing our service account key.
|
||||
# cd internal/kokoro
|
||||
# # Don't print out encryption keys, etc
|
||||
# set +x
|
||||
# key=$(cat $KOKORO_ARTIFACTS_DIR/keystore/*_encrypted_ba2d6f7723ed_key)
|
||||
# iv=$(cat $KOKORO_ARTIFACTS_DIR/keystore/*_encrypted_ba2d6f7723ed_iv)
|
||||
# pass=$(cat $KOKORO_ARTIFACTS_DIR/keystore/*_encrypted_ba2d6f7723ed_pass)
|
||||
|
||||
# openssl aes-256-cbc -K $key -iv $iv -pass pass:$pass -in kokoro-key.json.enc -out key.json -d
|
||||
# set -x
|
||||
|
||||
# export GCLOUD_TESTS_GOLANG_KEY="$(pwd)/key.json"
|
||||
# export GCLOUD_TESTS_GOLANG_PROJECT_ID="dulcet-port-762"
|
||||
# cd $GOCLOUD_HOME
|
||||
|
||||
# Run tests and tee output to log file, to be pushed to GCS as artifact.
|
||||
go test -race -v -short ./... 2>&1 | tee $KOKORO_ARTIFACTS_DIR/$KOKORO_GERRIT_CHANGE_NUMBER.txt
|
||||
|
||||
# Make sure README.md is up to date.
|
||||
make -C internal/readme test diff
|
||||
BIN
Binary file not shown.
+108
@@ -0,0 +1,108 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package optional provides versions of primitive types that can
|
||||
// be nil. These are useful in methods that update some of an API object's
|
||||
// fields.
|
||||
package optional
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// Bool is either a bool or nil.
|
||||
Bool interface{}
|
||||
|
||||
// String is either a string or nil.
|
||||
String interface{}
|
||||
|
||||
// Int is either an int or nil.
|
||||
Int interface{}
|
||||
|
||||
// Uint is either a uint or nil.
|
||||
Uint interface{}
|
||||
|
||||
// Float64 is either a float64 or nil.
|
||||
Float64 interface{}
|
||||
|
||||
// Duration is either a time.Duration or nil.
|
||||
Duration interface{}
|
||||
)
|
||||
|
||||
// ToBool returns its argument as a bool.
|
||||
// It panics if its argument is nil or not a bool.
|
||||
func ToBool(v Bool) bool {
|
||||
x, ok := v.(bool)
|
||||
if !ok {
|
||||
doPanic("Bool", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToString returns its argument as a string.
|
||||
// It panics if its argument is nil or not a string.
|
||||
func ToString(v String) string {
|
||||
x, ok := v.(string)
|
||||
if !ok {
|
||||
doPanic("String", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToInt returns its argument as an int.
|
||||
// It panics if its argument is nil or not an int.
|
||||
func ToInt(v Int) int {
|
||||
x, ok := v.(int)
|
||||
if !ok {
|
||||
doPanic("Int", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToUint returns its argument as a uint.
|
||||
// It panics if its argument is nil or not a uint.
|
||||
func ToUint(v Uint) uint {
|
||||
x, ok := v.(uint)
|
||||
if !ok {
|
||||
doPanic("Uint", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToFloat64 returns its argument as a float64.
|
||||
// It panics if its argument is nil or not a float64.
|
||||
func ToFloat64(v Float64) float64 {
|
||||
x, ok := v.(float64)
|
||||
if !ok {
|
||||
doPanic("Float64", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToDuration returns its argument as a time.Duration.
|
||||
// It panics if its argument is nil or not a time.Duration.
|
||||
func ToDuration(v Duration) time.Duration {
|
||||
x, ok := v.(time.Duration)
|
||||
if !ok {
|
||||
doPanic("Duration", v)
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func doPanic(capType string, v interface{}) {
|
||||
panic(fmt.Sprintf("optional.%s value should be %s, got %T", capType, strings.ToLower(capType), v))
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package optional
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestConvertSuccess(t *testing.T) {
|
||||
if got, want := ToBool(false), false; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
if got, want := ToString(""), ""; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
if got, want := ToInt(0), 0; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
if got, want := ToUint(uint(0)), uint(0); got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
if got, want := ToFloat64(0.0), 0.0; got != want {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertFailure(t *testing.T) {
|
||||
for _, f := range []func(){
|
||||
func() { ToBool(nil) },
|
||||
func() { ToBool(3) },
|
||||
func() { ToString(nil) },
|
||||
func() { ToString(3) },
|
||||
func() { ToInt(nil) },
|
||||
func() { ToInt("") },
|
||||
func() { ToUint(nil) },
|
||||
func() { ToUint("") },
|
||||
func() { ToFloat64(nil) },
|
||||
func() { ToFloat64("") },
|
||||
} {
|
||||
if !panics(f) {
|
||||
t.Error("got no panic, want panic")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func panics(f func()) (b bool) {
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
b = true
|
||||
}
|
||||
}()
|
||||
f()
|
||||
return false
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pretty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Diff compares the pretty-printed representation of two values. The second
|
||||
// return value reports whether the two values' representations are identical.
|
||||
// If it is false, the first return value contains the diffs.
|
||||
//
|
||||
// The output labels the first value "want" and the second "got".
|
||||
//
|
||||
// Diff works by invoking the "diff" command. It will only succeed in
|
||||
// environments where "diff" is on the shell path.
|
||||
func Diff(want, got interface{}) (string, bool, error) {
|
||||
fname1, err := writeToTemp(want)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
defer os.Remove(fname1)
|
||||
|
||||
fname2, err := writeToTemp(got)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
defer os.Remove(fname2)
|
||||
|
||||
cmd := exec.Command("diff", "-u", "--label=want", "--label=got", fname1, fname2)
|
||||
out, err := cmd.Output()
|
||||
if err == nil {
|
||||
return string(out), true, nil
|
||||
}
|
||||
eerr, ok := err.(*exec.ExitError)
|
||||
if !ok {
|
||||
return "", false, err
|
||||
}
|
||||
ws, ok := eerr.Sys().(syscall.WaitStatus)
|
||||
if !ok {
|
||||
return "", false, err
|
||||
}
|
||||
if ws.ExitStatus() != 1 {
|
||||
return "", false, err
|
||||
}
|
||||
// Exit status of 1 means no error, but diffs were found.
|
||||
return string(out), false, nil
|
||||
}
|
||||
|
||||
func writeToTemp(v interface{}) (string, error) {
|
||||
f, err := ioutil.TempFile("", "prettyDiff")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := fmt.Fprintf(f, "%+v\n", Value(v)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return f.Name(), nil
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pretty
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestDiff(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
v1, v2 interface{}
|
||||
ok bool
|
||||
want string
|
||||
}{
|
||||
{5, 5, true, ""},
|
||||
{"foo", "foo", true, ""},
|
||||
{[]int{1, 2, 3}, []int{1, 0, 3}, false, `--- want
|
||||
+++ got
|
||||
@@ -1,5 +1,5 @@
|
||||
[]int{
|
||||
1,
|
||||
- 2,
|
||||
+ 0,
|
||||
3,
|
||||
}
|
||||
`},
|
||||
} {
|
||||
got, ok, err := Diff(test.v1, test.v2)
|
||||
if err != nil {
|
||||
t.Errorf("%v vs. %v: %v", test.v1, test.v2, err)
|
||||
continue
|
||||
}
|
||||
if ok != test.ok {
|
||||
t.Errorf("%v vs. %v: got %t, want %t", test.v1, test.v2, ok, test.ok)
|
||||
}
|
||||
if got != test.want {
|
||||
t.Errorf("%v vs. %v: got:\n%q\nwant:\n%q", test.v1, test.v2, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package pretty implements a simple pretty-printer. It is intended for
|
||||
// debugging the output of tests.
|
||||
//
|
||||
// It follows pointers and produces multi-line output for complex values like
|
||||
// slices, maps and structs.
|
||||
package pretty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Indent is the string output at each level of indentation.
|
||||
var Indent = " "
|
||||
|
||||
// Value returns a value that will print prettily when used as an
|
||||
// argument for the %v or %s format specifiers.
|
||||
// With no flags, struct fields and map keys with default values are omitted.
|
||||
// With the '+' or '#' flags, all values are displayed.
|
||||
//
|
||||
// This package does not detect cycles. Attempting to print a Value that
|
||||
// contains cycles will result in unbounded recursion.
|
||||
func Value(v interface{}) val { return val{v: v} }
|
||||
|
||||
type val struct{ v interface{} }
|
||||
|
||||
// Format implements the fmt.Formatter interface.
|
||||
func (v val) Format(s fmt.State, c rune) {
|
||||
if c == 'v' || c == 's' {
|
||||
fprint(s, reflect.ValueOf(v.v), state{
|
||||
defaults: s.Flag('+') || s.Flag('#'),
|
||||
})
|
||||
} else {
|
||||
fmt.Fprintf(s, "%%!%c(pretty.Val)", c)
|
||||
}
|
||||
}
|
||||
|
||||
type state struct {
|
||||
level int
|
||||
prefix, suffix string
|
||||
defaults bool
|
||||
}
|
||||
|
||||
const maxLevel = 100
|
||||
|
||||
var typeOfTime = reflect.TypeOf(time.Time{})
|
||||
|
||||
func fprint(w io.Writer, v reflect.Value, s state) {
|
||||
if s.level > maxLevel {
|
||||
fmt.Fprintln(w, "pretty: max nested depth exceeded")
|
||||
return
|
||||
}
|
||||
indent := strings.Repeat(Indent, s.level)
|
||||
fmt.Fprintf(w, "%s%s", indent, s.prefix)
|
||||
if isNil(v) {
|
||||
fmt.Fprintf(w, "nil%s", s.suffix)
|
||||
return
|
||||
}
|
||||
if v.Type().Kind() == reflect.Interface {
|
||||
v = v.Elem()
|
||||
}
|
||||
if v.Type() == typeOfTime {
|
||||
fmt.Fprintf(w, "%s%s", v.Interface(), s.suffix)
|
||||
return
|
||||
}
|
||||
for v.Type().Kind() == reflect.Ptr {
|
||||
fmt.Fprintf(w, "&")
|
||||
v = v.Elem()
|
||||
}
|
||||
switch v.Type().Kind() {
|
||||
default:
|
||||
fmt.Fprintf(w, "%s%s", short(v), s.suffix)
|
||||
|
||||
case reflect.Array:
|
||||
fmt.Fprintf(w, "%s{\n", v.Type())
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
fprint(w, v.Index(i), state{
|
||||
level: s.level + 1,
|
||||
prefix: "",
|
||||
suffix: ",",
|
||||
defaults: s.defaults,
|
||||
})
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
fmt.Fprintf(w, "%s}", indent)
|
||||
|
||||
case reflect.Slice:
|
||||
fmt.Fprintf(w, "%s{", v.Type())
|
||||
if v.Len() > 0 {
|
||||
fmt.Fprintln(w)
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
fprint(w, v.Index(i), state{
|
||||
level: s.level + 1,
|
||||
prefix: "",
|
||||
suffix: ",",
|
||||
defaults: s.defaults,
|
||||
})
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "%s}%s", indent, s.suffix)
|
||||
|
||||
case reflect.Map:
|
||||
fmt.Fprintf(w, "%s{", v.Type())
|
||||
if v.Len() > 0 {
|
||||
fmt.Fprintln(w)
|
||||
keys := v.MapKeys()
|
||||
maybeSort(keys, v.Type().Key())
|
||||
for _, key := range keys {
|
||||
val := v.MapIndex(key)
|
||||
if s.defaults || !isDefault(val) {
|
||||
fprint(w, val, state{
|
||||
level: s.level + 1,
|
||||
prefix: short(key) + ": ",
|
||||
suffix: ",",
|
||||
defaults: s.defaults,
|
||||
})
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "%s}%s", indent, s.suffix)
|
||||
|
||||
case reflect.Struct:
|
||||
t := v.Type()
|
||||
fmt.Fprintf(w, "%s{\n", t)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := v.Field(i)
|
||||
if s.defaults || !isDefault(f) {
|
||||
fprint(w, f, state{
|
||||
level: s.level + 1,
|
||||
prefix: t.Field(i).Name + ": ",
|
||||
suffix: ",",
|
||||
defaults: s.defaults,
|
||||
})
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(w, "%s}%s", indent, s.suffix)
|
||||
}
|
||||
}
|
||||
|
||||
func isNil(v reflect.Value) bool {
|
||||
if !v.IsValid() {
|
||||
return true
|
||||
}
|
||||
switch v.Type().Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return v.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isDefault(v reflect.Value) bool {
|
||||
if !v.IsValid() {
|
||||
return true
|
||||
}
|
||||
t := v.Type()
|
||||
switch t.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return v.IsNil()
|
||||
default:
|
||||
if !v.CanInterface() {
|
||||
return false
|
||||
}
|
||||
return t.Comparable() && v.Interface() == reflect.Zero(t).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
// short returns a short, one-line string for v.
|
||||
func short(v reflect.Value) string {
|
||||
if !v.IsValid() {
|
||||
return "nil"
|
||||
}
|
||||
if v.Type().Kind() == reflect.String {
|
||||
return fmt.Sprintf("%q", v)
|
||||
}
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
func indent(w io.Writer, level int) {
|
||||
for i := 0; i < level; i++ {
|
||||
io.WriteString(w, Indent) // ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
func maybeSort(vs []reflect.Value, t reflect.Type) {
|
||||
if less := lessFunc(t); less != nil {
|
||||
sort.Sort(&sorter{vs, less})
|
||||
}
|
||||
}
|
||||
|
||||
// lessFunc returns a function that implements the "<" operator
|
||||
// for the given type, or nil if the type doesn't support "<" .
|
||||
func lessFunc(t reflect.Type) func(v1, v2 interface{}) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
return func(v1, v2 interface{}) bool { return v1.(string) < v2.(string) }
|
||||
case reflect.Int:
|
||||
return func(v1, v2 interface{}) bool { return v1.(int) < v2.(int) }
|
||||
case reflect.Int8:
|
||||
return func(v1, v2 interface{}) bool { return v1.(int8) < v2.(int8) }
|
||||
case reflect.Int16:
|
||||
return func(v1, v2 interface{}) bool { return v1.(int16) < v2.(int16) }
|
||||
case reflect.Int32:
|
||||
return func(v1, v2 interface{}) bool { return v1.(int32) < v2.(int32) }
|
||||
case reflect.Int64:
|
||||
return func(v1, v2 interface{}) bool { return v1.(int64) < v2.(int64) }
|
||||
case reflect.Uint:
|
||||
return func(v1, v2 interface{}) bool { return v1.(uint) < v2.(uint) }
|
||||
case reflect.Uint8:
|
||||
return func(v1, v2 interface{}) bool { return v1.(uint8) < v2.(uint8) }
|
||||
case reflect.Uint16:
|
||||
return func(v1, v2 interface{}) bool { return v1.(uint16) < v2.(uint16) }
|
||||
case reflect.Uint32:
|
||||
return func(v1, v2 interface{}) bool { return v1.(uint32) < v2.(uint32) }
|
||||
case reflect.Uint64:
|
||||
return func(v1, v2 interface{}) bool { return v1.(uint64) < v2.(uint64) }
|
||||
case reflect.Float32:
|
||||
return func(v1, v2 interface{}) bool { return v1.(float32) < v2.(float32) }
|
||||
case reflect.Float64:
|
||||
return func(v1, v2 interface{}) bool { return v1.(float64) < v2.(float64) }
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type sorter struct {
|
||||
vs []reflect.Value
|
||||
less func(v1, v2 interface{}) bool
|
||||
}
|
||||
|
||||
func (s *sorter) Len() int { return len(s.vs) }
|
||||
func (s *sorter) Swap(i, j int) { s.vs[i], s.vs[j] = s.vs[j], s.vs[i] }
|
||||
func (s *sorter) Less(i, j int) bool { return s.less(s.vs[i].Interface(), s.vs[j].Interface()) }
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pretty
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type S struct {
|
||||
X int
|
||||
Y bool
|
||||
z *string
|
||||
}
|
||||
|
||||
func TestSprint(t *testing.T) {
|
||||
Indent = "~"
|
||||
i := 17
|
||||
|
||||
for _, test := range []struct {
|
||||
value interface{}
|
||||
want string
|
||||
}{
|
||||
// primitives and pointer
|
||||
{nil, "nil"},
|
||||
{3, "3"},
|
||||
{9.8, "9.8"},
|
||||
{true, "true"},
|
||||
{"foo", `"foo"`},
|
||||
{&i, "&17"},
|
||||
// array and slice
|
||||
{[3]int{1, 2, 3}, "[3]int{\n~1,\n~2,\n~3,\n}"},
|
||||
{[]int{1, 2, 3}, "[]int{\n~1,\n~2,\n~3,\n}"},
|
||||
{[]int{}, "[]int{}"},
|
||||
{[]string{"foo"}, "[]string{\n~\"foo\",\n}"},
|
||||
// map
|
||||
{map[int]bool{}, "map[int]bool{}"},
|
||||
{map[int]bool{1: true, 2: false, 3: true},
|
||||
"map[int]bool{\n~1: true,\n~3: true,\n}"},
|
||||
// struct
|
||||
{S{}, "pretty.S{\n}"},
|
||||
{S{3, true, ptr("foo")},
|
||||
"pretty.S{\n~X: 3,\n~Y: true,\n~z: &\"foo\",\n}"},
|
||||
// interface
|
||||
{[]interface{}{&i}, "[]interface {}{\n~&17,\n}"},
|
||||
// nesting
|
||||
{[]S{{1, false, ptr("a")}, {2, true, ptr("b")}},
|
||||
`[]pretty.S{
|
||||
~pretty.S{
|
||||
~~X: 1,
|
||||
~~z: &"a",
|
||||
~},
|
||||
~pretty.S{
|
||||
~~X: 2,
|
||||
~~Y: true,
|
||||
~~z: &"b",
|
||||
~},
|
||||
}`},
|
||||
} {
|
||||
got := fmt.Sprintf("%v", Value(test.value))
|
||||
if got != test.want {
|
||||
t.Errorf("%v: got:\n%q\nwant:\n%q", test.value, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithDefaults(t *testing.T) {
|
||||
Indent = "~"
|
||||
for _, test := range []struct {
|
||||
value interface{}
|
||||
want string
|
||||
}{
|
||||
{map[int]bool{1: true, 2: false, 3: true},
|
||||
"map[int]bool{\n~1: true,\n~2: false,\n~3: true,\n}"},
|
||||
{S{}, "pretty.S{\n~X: 0,\n~Y: false,\n~z: nil,\n}"},
|
||||
} {
|
||||
got := fmt.Sprintf("%+v", Value(test.value))
|
||||
if got != test.want {
|
||||
t.Errorf("%v: got:\n%q\nwant:\n%q", test.value, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadVerb(t *testing.T) {
|
||||
got := fmt.Sprintf("%d", Value(8))
|
||||
want := "%!d("
|
||||
if !strings.HasPrefix(got, want) {
|
||||
t.Errorf("got %q, want prefix %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func ptr(s string) *string { return &s }
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package protostruct supports operations on the protocol buffer Struct message.
|
||||
package protostruct
|
||||
|
||||
import (
|
||||
pb "github.com/golang/protobuf/ptypes/struct"
|
||||
)
|
||||
|
||||
// DecodeToMap converts a pb.Struct to a map from strings to Go types.
|
||||
// DecodeToMap panics if s is invalid.
|
||||
func DecodeToMap(s *pb.Struct) map[string]interface{} {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
m := map[string]interface{}{}
|
||||
for k, v := range s.Fields {
|
||||
m[k] = decodeValue(v)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func decodeValue(v *pb.Value) interface{} {
|
||||
switch k := v.Kind.(type) {
|
||||
case *pb.Value_NullValue:
|
||||
return nil
|
||||
case *pb.Value_NumberValue:
|
||||
return k.NumberValue
|
||||
case *pb.Value_StringValue:
|
||||
return k.StringValue
|
||||
case *pb.Value_BoolValue:
|
||||
return k.BoolValue
|
||||
case *pb.Value_StructValue:
|
||||
return DecodeToMap(k.StructValue)
|
||||
case *pb.Value_ListValue:
|
||||
s := make([]interface{}, len(k.ListValue.Values))
|
||||
for i, e := range k.ListValue.Values {
|
||||
s[i] = decodeValue(e)
|
||||
}
|
||||
return s
|
||||
default:
|
||||
panic("protostruct: unknown kind")
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package protostruct supports operations on the protocol buffer Struct message.
|
||||
package protostruct
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
pb "github.com/golang/protobuf/ptypes/struct"
|
||||
)
|
||||
|
||||
func TestDecodeToMap(t *testing.T) {
|
||||
if got := DecodeToMap(nil); !testutil.Equal(got, map[string]interface{}(nil)) {
|
||||
t.Errorf("DecodeToMap(nil) = %v, want nil", got)
|
||||
}
|
||||
nullv := &pb.Value{&pb.Value_NullValue{}}
|
||||
stringv := &pb.Value{&pb.Value_StringValue{"x"}}
|
||||
boolv := &pb.Value{&pb.Value_BoolValue{true}}
|
||||
numberv := &pb.Value{&pb.Value_NumberValue{2.7}}
|
||||
in := &pb.Struct{Fields: map[string]*pb.Value{
|
||||
"n": nullv,
|
||||
"s": stringv,
|
||||
"b": boolv,
|
||||
"f": numberv,
|
||||
"l": &pb.Value{&pb.Value_ListValue{&pb.ListValue{
|
||||
[]*pb.Value{nullv, stringv, boolv, numberv},
|
||||
}}},
|
||||
"S": &pb.Value{&pb.Value_StructValue{&pb.Struct{Fields: map[string]*pb.Value{
|
||||
"n1": nullv,
|
||||
"b1": boolv,
|
||||
}}}},
|
||||
}}
|
||||
want := map[string]interface{}{
|
||||
"n": nil,
|
||||
"s": "x",
|
||||
"b": true,
|
||||
"f": 2.7,
|
||||
"l": []interface{}{nil, "x", true, 2.7},
|
||||
"S": map[string]interface{}{"n1": nil, "b1": true},
|
||||
}
|
||||
got := DecodeToMap(in)
|
||||
if diff := testutil.Diff(got, want); diff != "" {
|
||||
t.Error(diff)
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
# Rebuild the README.md file at repo root by inserting code samples
|
||||
# from compilable go files.
|
||||
|
||||
SHELL=/bin/bash
|
||||
|
||||
GOCLOUD_HOME=$(GOPATH)/src/cloud.google.com/go
|
||||
README=$(GOCLOUD_HOME)/README.md
|
||||
|
||||
.PHONY: readme test test-good test-bad-go test-bad-md
|
||||
|
||||
readme:
|
||||
@tmp=$$(mktemp); \
|
||||
awk -f snipmd.awk snippets.go $(README) > $$tmp; \
|
||||
mv $$tmp $(README)
|
||||
|
||||
diff:
|
||||
diff $(README) <(awk -f snipmd.awk snippets.go $(README))
|
||||
|
||||
test: test-good test-bad-go test-bad-md
|
||||
@echo PASS
|
||||
|
||||
test-good:
|
||||
@echo testdata/good.md
|
||||
@cd testdata >& /dev/null; \
|
||||
diff -u want.md <(awk -f ../snipmd.awk snips.go good.md)
|
||||
@echo "testdata/want.md (round trip)"
|
||||
@cd testdata >& /dev/null; \
|
||||
diff -u want.md <(awk -f ../snipmd.awk snips.go want.md)
|
||||
|
||||
test-bad-go:
|
||||
@for f in testdata/bad-*.go; do \
|
||||
echo $$f; \
|
||||
if awk -f snipmd.awk $$f >& /dev/null; then \
|
||||
echo "$f succeeded, want failure"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
done
|
||||
|
||||
test-bad-md:
|
||||
@for f in testdata/bad-*.md; do \
|
||||
echo $$f; \
|
||||
if awk -f snipmd.awk testdata/snips.go $$f >& /dev/null; then \
|
||||
echo "$f succeeded, want failure"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
done
|
||||
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
# Copyright 2017 Google Inc. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# snipmd inserts code snippets from Go source files into a markdown file.
|
||||
#
|
||||
# Call with one or more .go files and a .md file:
|
||||
#
|
||||
# awk -f snipmd.awk foo.go bar.go template.md
|
||||
#
|
||||
# In the Go files, start a snippet with
|
||||
# //[ NAME
|
||||
# and end it with
|
||||
# //]
|
||||
#
|
||||
# In the markdown, write
|
||||
# [snip]:# NAME
|
||||
# to insert the snippet NAME just below that line.
|
||||
# If there is already a code block after the [snip]:# line, it will be
|
||||
# replaced, so a previous output can be used as input.
|
||||
#
|
||||
# The following transformations are made to the Go code:
|
||||
# - The first tab of each line is removed.
|
||||
# - Trailing blank lines are removed.
|
||||
# - `ELLIPSIS` and `_ = ELLIPSIS` are replaced by `...`
|
||||
|
||||
|
||||
/^[ \t]*\/\/\[/ { # start snippet in Go file
|
||||
if (inGo()) {
|
||||
if ($2 == "") {
|
||||
die("missing snippet name")
|
||||
}
|
||||
curSnip = $2
|
||||
next
|
||||
}
|
||||
}
|
||||
|
||||
/^[ \t]*\/\/]/ { # end snippet in Go file
|
||||
if (inGo()) {
|
||||
if (curSnip != "") {
|
||||
# Remove all but one trailing newline.
|
||||
gsub(/\n+$/, "\n", snips[curSnip])
|
||||
curSnip = ""
|
||||
next
|
||||
} else {
|
||||
die("//] without corresponding //[")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ENDFILE {
|
||||
if (curSnip != "") {
|
||||
die("unclosed snippet: " curSnip)
|
||||
}
|
||||
}
|
||||
|
||||
# Skip code blocks in the input that immediately follow [snip]:# lines,
|
||||
# because we just inserted the snippet. Supports round-tripping.
|
||||
/^```go$/,/^```$/ {
|
||||
if (inMarkdown() && afterSnip) {
|
||||
next
|
||||
}
|
||||
}
|
||||
|
||||
# Matches every line.
|
||||
{
|
||||
if (curSnip != "") {
|
||||
line = $0
|
||||
# Remove initial tab, if any.
|
||||
if (line ~ /^\t/) {
|
||||
line = substr(line, 2)
|
||||
}
|
||||
# Replace ELLIPSIS.
|
||||
gsub(/_ = ELLIPSIS/, "...", line)
|
||||
gsub(/ELLIPSIS/, "...", line)
|
||||
|
||||
snips[curSnip] = snips[curSnip] line "\n"
|
||||
} else if (inMarkdown()) {
|
||||
afterSnip = 0
|
||||
# Copy .md to output.
|
||||
print
|
||||
}
|
||||
}
|
||||
|
||||
$1 ~ /\[snip\]:#/ { # Snippet marker in .md file.
|
||||
if (inMarkdown()) {
|
||||
# We expect '[snip]:#' to be followed by '(NAME)'
|
||||
if ($2 !~ /\(.*\)/) {
|
||||
die("bad snip spec: " $0)
|
||||
}
|
||||
name = substr($2, 2, length($2)-2)
|
||||
if (snips[name] == "") {
|
||||
die("no snippet named " name)
|
||||
}
|
||||
printf("```go\n%s```\n", snips[name])
|
||||
afterSnip = 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function inMarkdown() {
|
||||
return match(FILENAME, /\.md$/)
|
||||
}
|
||||
|
||||
function inGo() {
|
||||
return match(FILENAME, /\.go$/)
|
||||
}
|
||||
|
||||
|
||||
function die(msg) {
|
||||
printf("%s:%d: %s\n", FILENAME, FNR, msg) > "/dev/stderr"
|
||||
exit 1
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file holds samples that are embedded into README.md.
|
||||
|
||||
// This file has to compile, but need not execute.
|
||||
// If it fails to compile, fix it, then run `make` to regenerate README.md.
|
||||
|
||||
package readme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"cloud.google.com/go/bigquery"
|
||||
"cloud.google.com/go/datastore"
|
||||
"cloud.google.com/go/logging"
|
||||
"cloud.google.com/go/pubsub"
|
||||
"cloud.google.com/go/spanner"
|
||||
"cloud.google.com/go/storage"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
)
|
||||
|
||||
var ctx context.Context
|
||||
|
||||
const END = 0
|
||||
|
||||
func auth() {
|
||||
//[ auth
|
||||
client, err := storage.NewClient(ctx)
|
||||
//]
|
||||
_ = client
|
||||
_ = err
|
||||
}
|
||||
|
||||
func auth2() {
|
||||
//[ auth-JSON
|
||||
client, err := storage.NewClient(ctx, option.WithServiceAccountFile("path/to/keyfile.json"))
|
||||
//]
|
||||
_ = client
|
||||
_ = err
|
||||
}
|
||||
|
||||
func auth3() {
|
||||
var ELLIPSIS oauth2.TokenSource
|
||||
//[ auth-ts
|
||||
tokenSource := ELLIPSIS
|
||||
client, err := storage.NewClient(ctx, option.WithTokenSource(tokenSource))
|
||||
//]
|
||||
_ = client
|
||||
_ = err
|
||||
}
|
||||
|
||||
func datastoreSnippets() {
|
||||
//[ datastore-1
|
||||
client, err := datastore.NewClient(ctx, "my-project-id")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
//]
|
||||
|
||||
//[ datastore-2
|
||||
type Post struct {
|
||||
Title string
|
||||
Body string `datastore:",noindex"`
|
||||
PublishedAt time.Time
|
||||
}
|
||||
keys := []*datastore.Key{
|
||||
datastore.NameKey("Post", "post1", nil),
|
||||
datastore.NameKey("Post", "post2", nil),
|
||||
}
|
||||
posts := []*Post{
|
||||
{Title: "Post 1", Body: "...", PublishedAt: time.Now()},
|
||||
{Title: "Post 2", Body: "...", PublishedAt: time.Now()},
|
||||
}
|
||||
if _, err := client.PutMulti(ctx, keys, posts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
//]
|
||||
}
|
||||
|
||||
func storageSnippets() {
|
||||
//[ storage-1
|
||||
client, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
//]
|
||||
|
||||
//[ storage-2
|
||||
// Read the object1 from bucket.
|
||||
rc, err := client.Bucket("bucket").Object("object1").NewReader(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer rc.Close()
|
||||
body, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
//]
|
||||
_ = body
|
||||
}
|
||||
|
||||
func pubsubSnippets() {
|
||||
//[ pubsub-1
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
//]
|
||||
|
||||
const ELLIPSIS = 0
|
||||
|
||||
//[ pubsub-2
|
||||
// Publish "hello world" on topic1.
|
||||
topic := client.Topic("topic1")
|
||||
res := topic.Publish(ctx, &pubsub.Message{
|
||||
Data: []byte("hello world"),
|
||||
})
|
||||
// The publish happens asynchronously.
|
||||
// Later, you can get the result from res:
|
||||
_ = ELLIPSIS
|
||||
msgID, err := res.Get(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Use a callback to receive messages via subscription1.
|
||||
sub := client.Subscription("subscription1")
|
||||
err = sub.Receive(ctx, func(ctx context.Context, m *pubsub.Message) {
|
||||
fmt.Println(m.Data)
|
||||
m.Ack() // Acknowledge that we've consumed the message.
|
||||
})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
//]
|
||||
_ = msgID
|
||||
}
|
||||
|
||||
func bqSnippets() {
|
||||
//[ bq-1
|
||||
c, err := bigquery.NewClient(ctx, "my-project-ID")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
//]
|
||||
|
||||
//[ bq-2
|
||||
// Construct a query.
|
||||
q := c.Query(`
|
||||
SELECT year, SUM(number)
|
||||
FROM [bigquery-public-data:usa_names.usa_1910_2013]
|
||||
WHERE name = "William"
|
||||
GROUP BY year
|
||||
ORDER BY year
|
||||
`)
|
||||
// Execute the query.
|
||||
it, err := q.Read(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// Iterate through the results.
|
||||
for {
|
||||
var values []bigquery.Value
|
||||
err := it.Next(&values)
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Println(values)
|
||||
}
|
||||
//]
|
||||
}
|
||||
|
||||
func loggingSnippets() {
|
||||
//[ logging-1
|
||||
ctx := context.Background()
|
||||
client, err := logging.NewClient(ctx, "my-project")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
//]
|
||||
//[ logging-2
|
||||
logger := client.Logger("my-log")
|
||||
logger.Log(logging.Entry{Payload: "something happened!"})
|
||||
//]
|
||||
|
||||
//[ logging-3
|
||||
err = client.Close()
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
//]
|
||||
}
|
||||
|
||||
func spannerSnippets() {
|
||||
//[ spanner-1
|
||||
client, err := spanner.NewClient(ctx, "projects/P/instances/I/databases/D")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
//]
|
||||
|
||||
//[ spanner-2
|
||||
// Simple Reads And Writes
|
||||
_, err = client.Apply(ctx, []*spanner.Mutation{
|
||||
spanner.Insert("Users",
|
||||
[]string{"name", "email"},
|
||||
[]interface{}{"alice", "a@example.com"})})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
row, err := client.Single().ReadRow(ctx, "Users",
|
||||
spanner.Key{"alice"}, []string{"email"})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
//]
|
||||
_ = row
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package readme
|
||||
|
||||
import "fmt"
|
||||
|
||||
func f() {
|
||||
//[
|
||||
fmt.Println()
|
||||
//]
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package readme
|
||||
|
||||
func f() {
|
||||
//]
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
[snip]:# (unknown)
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
[snip]:# missing-parens
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package readme
|
||||
|
||||
// unclosed snippet
|
||||
|
||||
func f() {
|
||||
//[ X
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
This template is for testing snipmd.awk.
|
||||
|
||||
Put the first snippet here.
|
||||
|
||||
[snip]:# (first)
|
||||
|
||||
And now the second.
|
||||
[snip]:# (second)
|
||||
|
||||
A top-level snippet.
|
||||
|
||||
[snip]:# (top-level)
|
||||
|
||||
```go
|
||||
// A code block that is not included.
|
||||
```
|
||||
|
||||
And we're done.
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package readme
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func f() {
|
||||
ELLIPSIS := 3
|
||||
//[ first
|
||||
fmt.Println("hello")
|
||||
x := ELLIPSIS
|
||||
//]
|
||||
|
||||
//[ second
|
||||
if x > 2 {
|
||||
_ = ELLIPSIS
|
||||
}
|
||||
//]
|
||||
}
|
||||
|
||||
//[ top-level
|
||||
var ErrBad = errors.New("bad")
|
||||
|
||||
//]
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
This template is for testing snipmd.awk.
|
||||
|
||||
Put the first snippet here.
|
||||
|
||||
[snip]:# (first)
|
||||
```go
|
||||
fmt.Println("hello")
|
||||
x := ...
|
||||
```
|
||||
|
||||
And now the second.
|
||||
[snip]:# (second)
|
||||
```go
|
||||
if x > 2 {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
A top-level snippet.
|
||||
|
||||
[snip]:# (top-level)
|
||||
```go
|
||||
var ErrBad = errors.New("bad")
|
||||
```
|
||||
|
||||
```go
|
||||
// A code block that is not included.
|
||||
```
|
||||
|
||||
And we're done.
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
gax "github.com/googleapis/gax-go"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Retry calls the supplied function f repeatedly according to the provided
|
||||
// backoff parameters. It returns when one of the following occurs:
|
||||
// When f's first return value is true, Retry immediately returns with f's second
|
||||
// return value.
|
||||
// When the provided context is done, Retry returns with an error that
|
||||
// includes both ctx.Error() and the last error returned by f.
|
||||
func Retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error)) error {
|
||||
return retry(ctx, bo, f, gax.Sleep)
|
||||
}
|
||||
|
||||
func retry(ctx context.Context, bo gax.Backoff, f func() (stop bool, err error),
|
||||
sleep func(context.Context, time.Duration) error) error {
|
||||
var lastErr error
|
||||
for {
|
||||
stop, err := f()
|
||||
if stop {
|
||||
return err
|
||||
}
|
||||
// Remember the last "real" error from f.
|
||||
if err != nil && err != context.Canceled && err != context.DeadlineExceeded {
|
||||
lastErr = err
|
||||
}
|
||||
p := bo.Pause()
|
||||
if cerr := sleep(ctx, p); cerr != nil {
|
||||
if lastErr != nil {
|
||||
return Annotatef(lastErr, "retry failed with %v; last error", cerr)
|
||||
}
|
||||
return cerr
|
||||
}
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
gax "github.com/googleapis/gax-go"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func TestRetry(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// Without a context deadline, retry will run until the function
|
||||
// says not to retry any more.
|
||||
n := 0
|
||||
endRetry := errors.New("end retry")
|
||||
err := retry(ctx, gax.Backoff{},
|
||||
func() (bool, error) {
|
||||
n++
|
||||
if n < 10 {
|
||||
return false, nil
|
||||
}
|
||||
return true, endRetry
|
||||
},
|
||||
func(context.Context, time.Duration) error { return nil })
|
||||
if got, want := err, endRetry; got != want {
|
||||
t.Errorf("got %v, want %v", err, endRetry)
|
||||
}
|
||||
if n != 10 {
|
||||
t.Errorf("n: got %d, want %d", n, 10)
|
||||
}
|
||||
|
||||
// If the context has a deadline, sleep will return an error
|
||||
// and end the function.
|
||||
n = 0
|
||||
err = retry(ctx, gax.Backoff{},
|
||||
func() (bool, error) { return false, nil },
|
||||
func(context.Context, time.Duration) error {
|
||||
n++
|
||||
if n < 10 {
|
||||
return nil
|
||||
}
|
||||
return context.DeadlineExceeded
|
||||
})
|
||||
if err == nil {
|
||||
t.Error("got nil, want error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryPreserveError(t *testing.T) {
|
||||
// Retry tries to preserve the type and other information from
|
||||
// the last error returned by the function.
|
||||
err := retry(context.Background(), gax.Backoff{},
|
||||
func() (bool, error) {
|
||||
return false, status.Error(codes.NotFound, "not found")
|
||||
},
|
||||
func(context.Context, time.Duration) error {
|
||||
return context.DeadlineExceeded
|
||||
})
|
||||
got, ok := status.FromError(err)
|
||||
if !ok {
|
||||
t.Fatalf("got %T, wanted a status", got)
|
||||
}
|
||||
if g, w := got.Code(), codes.NotFound; g != w {
|
||||
t.Errorf("got code %v, want %v", g, w)
|
||||
}
|
||||
wantMessage := fmt.Sprintf("retry failed with %v; last error: not found", context.DeadlineExceeded)
|
||||
if g, w := got.Message(), wantMessage; g != w {
|
||||
t.Errorf("got message %q, want %q", g, w)
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Snipdoc
|
||||
|
||||
Snipdoc is a simple tool for maintaining package documentation that contains
|
||||
code samples.
|
||||
|
||||
1. Create a subdirectory of your package to hold the following files. "internal"
|
||||
is a good name.
|
||||
|
||||
2. Write a template file (for example, "doc.template") with the text of your package documentation. The file
|
||||
should look exactly like you want your doc.go file to look, except for code
|
||||
snippets.
|
||||
Instead of embedding a code snippet, write a line consisting solely of
|
||||
|
||||
[NAME]
|
||||
|
||||
for your choice of NAME.
|
||||
|
||||
3. Write a snippets file (for example, "doc-snippets.go") as a valid Go source
|
||||
file. Begin each snippet you'd like to appear in your package docs with
|
||||
`//[ NAME` and end it with `//]`.
|
||||
|
||||
4. Construct your doc.go file with the command
|
||||
```
|
||||
awk -f snipdoc.awk doc-snippets.go doc.template
|
||||
```
|
||||
The file "sample-makefile" in this directory verifies that the
|
||||
snippets file compiles and safely constructs a doc.go file.
|
||||
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# Build doc.go from template and snippets.
|
||||
|
||||
SHELL=/bin/bash
|
||||
|
||||
../doc.go: build doc-snippets.go doc.template snipdoc.awk
|
||||
@tmp=$$(mktemp) && \
|
||||
awk -f snipdoc.awk doc-snippets.go doc.template > $$tmp && \
|
||||
chmod +w $@ && \
|
||||
mv $$tmp $@ && \
|
||||
chmod -w $@
|
||||
@echo "wrote $@"
|
||||
|
||||
.PHONY: build
|
||||
|
||||
build:
|
||||
go build doc-snippets.go
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
# Copyright 2017 Google Inc. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# snipdoc merges code snippets from Go source files into a template to
|
||||
# produce another go file (typically doc.go).
|
||||
#
|
||||
# Call with one or more .go files and a template file.
|
||||
#
|
||||
# awk -f snipmd.awk foo.go bar.go doc.template
|
||||
#
|
||||
# In the Go files, start a snippet with
|
||||
# //[ NAME
|
||||
# and end it with
|
||||
# //]
|
||||
#
|
||||
# In the template, write
|
||||
# [NAME]
|
||||
# on a line by itself to insert the snippet NAME on that line.
|
||||
#
|
||||
# The following transformations are made to the Go code:
|
||||
# - Trailing blank lines are removed.
|
||||
# - `ELLIPSIS` and `_ = ELLIPSIS` are replaced by `...`
|
||||
|
||||
|
||||
/^[ \t]*\/\/\[/ { # start snippet in Go file
|
||||
if (inGo()) {
|
||||
if ($2 == "") {
|
||||
die("missing snippet name")
|
||||
}
|
||||
curSnip = $2
|
||||
next
|
||||
}
|
||||
}
|
||||
|
||||
/^[ \t]*\/\/]/ { # end snippet in Go file
|
||||
if (inGo()) {
|
||||
if (curSnip != "") {
|
||||
# Remove all trailing newlines.
|
||||
gsub(/[\t\n]+$/, "", snips[curSnip])
|
||||
curSnip = ""
|
||||
next
|
||||
} else {
|
||||
die("//] without corresponding //[")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ENDFILE {
|
||||
if (curSnip != "") {
|
||||
die("unclosed snippet: " curSnip)
|
||||
}
|
||||
}
|
||||
|
||||
/^\[.*\]$/ { # Snippet marker in template file.
|
||||
if (inTemplate()) {
|
||||
name = substr($1, 2, length($1)-2)
|
||||
if (snips[name] == "") {
|
||||
die("no snippet named " name)
|
||||
}
|
||||
printf("%s\n", snips[name])
|
||||
afterSnip = 1
|
||||
next
|
||||
}
|
||||
}
|
||||
|
||||
# Matches every line.
|
||||
{
|
||||
if (curSnip != "") {
|
||||
# If the first line in the snip has no indent, add the indent.
|
||||
if (snips[curSnip] == "") {
|
||||
if (index($0, "\t") == 1) {
|
||||
extraIndent = ""
|
||||
} else {
|
||||
extraIndent = "\t"
|
||||
}
|
||||
}
|
||||
|
||||
line = $0
|
||||
# Replace ELLIPSIS.
|
||||
gsub(/_ = ELLIPSIS/, "...", line)
|
||||
gsub(/ELLIPSIS/, "...", line)
|
||||
|
||||
snips[curSnip] = snips[curSnip] extraIndent line "\n"
|
||||
} else if (inTemplate()) {
|
||||
afterSnip = 0
|
||||
# Copy to output.
|
||||
print
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function inTemplate() {
|
||||
return match(FILENAME, /\.template$/)
|
||||
}
|
||||
|
||||
function inGo() {
|
||||
return match(FILENAME, /\.go$/)
|
||||
}
|
||||
|
||||
|
||||
function die(msg) {
|
||||
printf("%s:%d: %s\n", FILENAME, FNR, msg) > "/dev/stderr"
|
||||
exit 1
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
var (
|
||||
alwaysEqual = cmp.Comparer(func(_, _ interface{}) bool { return true })
|
||||
|
||||
defaultCmpOptions = []cmp.Option{
|
||||
// Use proto.Equal for protobufs
|
||||
cmp.Comparer(proto.Equal),
|
||||
// NaNs compare equal
|
||||
cmp.FilterValues(func(x, y float64) bool {
|
||||
return math.IsNaN(x) && math.IsNaN(y)
|
||||
}, alwaysEqual),
|
||||
cmp.FilterValues(func(x, y float32) bool {
|
||||
return math.IsNaN(float64(x)) && math.IsNaN(float64(y))
|
||||
}, alwaysEqual),
|
||||
}
|
||||
)
|
||||
|
||||
// Equal tests two values for equality.
|
||||
func Equal(x, y interface{}, opts ...cmp.Option) bool {
|
||||
// Put default options at the end. Order doesn't matter.
|
||||
opts = append(opts[:len(opts):len(opts)], defaultCmpOptions...)
|
||||
return cmp.Equal(x, y, opts...)
|
||||
}
|
||||
|
||||
// Diff reports the differences between two values.
|
||||
// Diff(x, y) == "" iff Equal(x, y).
|
||||
func Diff(x, y interface{}, opts ...cmp.Option) string {
|
||||
// Put default options at the end. Order doesn't matter.
|
||||
opts = append(opts[:len(opts):len(opts)], defaultCmpOptions...)
|
||||
return cmp.Diff(x, y, opts...)
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package testutil contains helper functions for writing tests.
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
)
|
||||
|
||||
const (
|
||||
envProjID = "GCLOUD_TESTS_GOLANG_PROJECT_ID"
|
||||
envPrivateKey = "GCLOUD_TESTS_GOLANG_KEY"
|
||||
)
|
||||
|
||||
// ProjID returns the project ID to use in integration tests, or the empty
|
||||
// string if none is configured.
|
||||
func ProjID() string {
|
||||
return os.Getenv(envProjID)
|
||||
}
|
||||
|
||||
// TokenSource returns the OAuth2 token source to use in integration tests,
|
||||
// or nil if none is configured. It uses the standard environment variable
|
||||
// for tests in this repo.
|
||||
func TokenSource(ctx context.Context, scopes ...string) oauth2.TokenSource {
|
||||
return TokenSourceEnv(ctx, envPrivateKey, scopes...)
|
||||
}
|
||||
|
||||
// TokenSourceEnv returns the OAuth2 token source to use in integration tests. or nil
|
||||
// if none is configured. It tries to get credentials from the filename in the
|
||||
// environment variable envVar. If the environment variable is unset, TokenSourceEnv
|
||||
// will try to find 'Application Default Credentials'. Else, TokenSourceEnv will
|
||||
// return nil. TokenSourceEnv will log.Fatal if the token source is specified but
|
||||
// missing or invalid.
|
||||
func TokenSourceEnv(ctx context.Context, envVar string, scopes ...string) oauth2.TokenSource {
|
||||
key := os.Getenv(envVar)
|
||||
if key == "" { // Try for application default credentials.
|
||||
ts, err := google.DefaultTokenSource(ctx, scopes...)
|
||||
if err != nil {
|
||||
log.Println("No 'Application Default Credentials' found.")
|
||||
return nil
|
||||
}
|
||||
return ts
|
||||
}
|
||||
conf, err := jwtConfigFromFile(key, scopes)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return conf.TokenSource(ctx)
|
||||
}
|
||||
|
||||
// JWTConfig reads the JSON private key file whose name is in the default
|
||||
// environment variable, and returns the jwt.Config it contains. It ignores
|
||||
// scopes.
|
||||
// If the environment variable is empty, it returns (nil, nil).
|
||||
func JWTConfig() (*jwt.Config, error) {
|
||||
return jwtConfigFromFile(os.Getenv(envPrivateKey), nil)
|
||||
}
|
||||
|
||||
// jwtConfigFromFile reads the given JSON private key file, and returns the
|
||||
// jwt.Config it contains.
|
||||
// If the filename is empty, it returns (nil, nil).
|
||||
func jwtConfigFromFile(filename string, scopes []string) (*jwt.Config, error) {
|
||||
if filename == "" {
|
||||
return nil, nil
|
||||
}
|
||||
jsonKey, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot read the JSON key file, err: %v", err)
|
||||
}
|
||||
conf, err := google.JWTConfigFromJSON(jsonKey, scopes...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("google.JWTConfigFromJSON: %v", err)
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
// Copyright 2018 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"go.opencensus.io/plugin/ocgrpc"
|
||||
"go.opencensus.io/stats/view"
|
||||
"go.opencensus.io/trace"
|
||||
)
|
||||
|
||||
type TestExporter struct {
|
||||
Spans []*trace.SpanData
|
||||
Stats chan *view.Data
|
||||
}
|
||||
|
||||
func NewTestExporter() *TestExporter {
|
||||
te := &TestExporter{Stats: make(chan *view.Data)}
|
||||
|
||||
view.RegisterExporter(te)
|
||||
view.SetReportingPeriod(time.Millisecond)
|
||||
if err := view.Register(ocgrpc.ClientRequestCountView); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
trace.RegisterExporter(te)
|
||||
trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
|
||||
|
||||
return te
|
||||
}
|
||||
|
||||
func (te *TestExporter) ExportSpan(s *trace.SpanData) {
|
||||
te.Spans = append(te.Spans, s)
|
||||
}
|
||||
|
||||
func (te *TestExporter) ExportView(vd *view.Data) {
|
||||
if len(vd.Rows) > 0 {
|
||||
select {
|
||||
case te.Stats <- vd:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (te *TestExporter) Unregister() {
|
||||
view.UnregisterExporter(te)
|
||||
trace.UnregisterExporter(te)
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
Copyright 2016 Google Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// A Server is an in-process gRPC server, listening on a system-chosen port on
|
||||
// the local loopback interface. Servers are for testing only and are not
|
||||
// intended to be used in production code.
|
||||
//
|
||||
// To create a server, make a new Server, register your handlers, then call
|
||||
// Start:
|
||||
//
|
||||
// srv, err := NewServer()
|
||||
// ...
|
||||
// mypb.RegisterMyServiceServer(srv.Gsrv, &myHandler)
|
||||
// ....
|
||||
// srv.Start()
|
||||
//
|
||||
// Clients should connect to the server with no security:
|
||||
//
|
||||
// conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure())
|
||||
// ...
|
||||
type Server struct {
|
||||
Addr string
|
||||
l net.Listener
|
||||
Gsrv *grpc.Server
|
||||
}
|
||||
|
||||
// NewServer creates a new Server. The Server will be listening for gRPC connections
|
||||
// at the address named by the Addr field, without TLS.
|
||||
func NewServer(opts ...grpc.ServerOption) (*Server, error) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := &Server{
|
||||
Addr: l.Addr().String(),
|
||||
l: l,
|
||||
Gsrv: grpc.NewServer(opts...),
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Start causes the server to start accepting incoming connections.
|
||||
// Call Start after registering handlers.
|
||||
func (s *Server) Start() {
|
||||
go s.Gsrv.Serve(s.l)
|
||||
}
|
||||
|
||||
// Close shuts down the server.
|
||||
func (s *Server) Close() {
|
||||
s.Gsrv.Stop()
|
||||
s.l.Close()
|
||||
}
|
||||
|
||||
// PageBounds converts an incoming page size and token from an RPC request into
|
||||
// slice bounds and the outgoing next-page token.
|
||||
//
|
||||
// PageBounds assumes that the complete, unpaginated list of items exists as a
|
||||
// single slice. In addition to the page size and token, PageBounds needs the
|
||||
// length of that slice.
|
||||
//
|
||||
// PageBounds's first two return values should be used to construct a sub-slice of
|
||||
// the complete, unpaginated slice. E.g. if the complete slice is s, then
|
||||
// s[from:to] is the desired page. Its third return value should be set as the
|
||||
// NextPageToken field of the RPC response.
|
||||
func PageBounds(pageSize int, pageToken string, length int) (from, to int, nextPageToken string, err error) {
|
||||
from, to = 0, length
|
||||
if pageToken != "" {
|
||||
from, err = strconv.Atoi(pageToken)
|
||||
if err != nil {
|
||||
return 0, 0, "", status.Errorf(codes.InvalidArgument, "bad page token: %v", err)
|
||||
}
|
||||
if from >= length {
|
||||
return length, length, "", nil
|
||||
}
|
||||
}
|
||||
if pageSize > 0 && from+pageSize < length {
|
||||
to = from + pageSize
|
||||
nextPageToken = strconv.Itoa(to)
|
||||
}
|
||||
return from, to, nextPageToken, nil
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
grpc "google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
func TestNewServer(t *testing.T) {
|
||||
srv, err := NewServer()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
srv.Start()
|
||||
conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conn.Close()
|
||||
srv.Close()
|
||||
}
|
||||
|
||||
func TestPageBounds(t *testing.T) {
|
||||
const length = 10
|
||||
for _, test := range []struct {
|
||||
size int
|
||||
tok string
|
||||
wantFrom int
|
||||
wantTo int
|
||||
wantTok string
|
||||
}{
|
||||
{5, "",
|
||||
0, 5, "5"},
|
||||
{11, "",
|
||||
0, 10, ""},
|
||||
{5, "2",
|
||||
2, 7, "7"},
|
||||
{5, "8",
|
||||
8, 10, ""},
|
||||
{11, "8",
|
||||
8, 10, ""},
|
||||
{1, "11",
|
||||
10, 10, ""},
|
||||
} {
|
||||
gotFrom, gotTo, gotTok, err := PageBounds(test.size, test.tok, length)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := gotFrom, test.wantFrom; got != want {
|
||||
t.Errorf("%+v: from: got %d, want %d", test, got, want)
|
||||
}
|
||||
if got, want := gotTo, test.wantTo; got != want {
|
||||
t.Errorf("%+v: to: got %d, want %d", test, got, want)
|
||||
}
|
||||
if got, want := gotTok, test.wantTok; got != want {
|
||||
t.Errorf("%+v: got %q, want %q", test, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
_, _, _, err := PageBounds(4, "xyz", 5)
|
||||
if grpc.Code(err) != codes.InvalidArgument {
|
||||
t.Errorf("want invalid argument, got <%v>", err)
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file supports generating unique IDs so that multiple test executions
|
||||
// don't interfere with each other, and cleaning up old entities that may
|
||||
// remain if tests exit early.
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var startTime = time.Now().UTC()
|
||||
|
||||
// A UIDSpace manages a set of unique IDs distinguished by a prefix.
|
||||
type UIDSpace struct {
|
||||
Prefix string
|
||||
Sep rune
|
||||
re *regexp.Regexp
|
||||
mu sync.Mutex
|
||||
count int
|
||||
}
|
||||
|
||||
func NewUIDSpace(prefix string) *UIDSpace {
|
||||
return NewUIDSpaceSep(prefix, '-')
|
||||
}
|
||||
|
||||
func NewUIDSpaceSep(prefix string, sep rune) *UIDSpace {
|
||||
re := fmt.Sprintf(`^%s%[2]c(\d{4})(\d{2})(\d{2})%[2]c(\d+)%[2]c\d+$`,
|
||||
regexp.QuoteMeta(prefix), sep)
|
||||
return &UIDSpace{
|
||||
Prefix: prefix,
|
||||
Sep: sep,
|
||||
re: regexp.MustCompile(re),
|
||||
}
|
||||
}
|
||||
|
||||
// New generates a new unique ID . The ID consists of the UIDSpace's prefix, a
|
||||
// timestamp, and a counter value. All unique IDs generated in the same test
|
||||
// execution will have the same timestamp.
|
||||
//
|
||||
// Aside from the characters in the prefix, IDs contain only letters, numbers
|
||||
// and sep.
|
||||
func (s *UIDSpace) New() string { return s.newID(startTime) }
|
||||
|
||||
func (s *UIDSpace) newID(t time.Time) string {
|
||||
s.mu.Lock()
|
||||
c := s.count
|
||||
s.count++
|
||||
s.mu.Unlock()
|
||||
// Write the time as a date followed by nanoseconds from midnight of that date.
|
||||
// That makes it easier to see the approximate time of the ID when it is displayed.
|
||||
y, m, d := t.Date()
|
||||
ns := t.Sub(time.Date(y, m, d, 0, 0, 0, 0, time.UTC))
|
||||
// Zero-pad the counter for lexical sort order for IDs with the same timestamp.
|
||||
return fmt.Sprintf("%s%c%04d%02d%02d%c%d%c%04d",
|
||||
s.Prefix, s.Sep, y, m, d, s.Sep, ns, s.Sep, c)
|
||||
}
|
||||
|
||||
// Timestamp extracts the timestamp of uid, which must have been generated by
|
||||
// s. The second return value is true on success, false if there was a problem.
|
||||
func (s *UIDSpace) Timestamp(uid string) (time.Time, bool) {
|
||||
subs := s.re.FindStringSubmatch(uid)
|
||||
if subs == nil {
|
||||
return time.Time{}, false
|
||||
}
|
||||
y, err1 := strconv.Atoi(subs[1])
|
||||
m, err2 := strconv.Atoi(subs[2])
|
||||
d, err3 := strconv.Atoi(subs[3])
|
||||
ns, err4 := strconv.Atoi(subs[4])
|
||||
if err1 != nil || err2 != nil || err3 != nil || err4 != nil {
|
||||
return time.Time{}, false
|
||||
}
|
||||
return time.Date(y, time.Month(m), d, 0, 0, 0, ns, time.UTC), true
|
||||
}
|
||||
|
||||
// Older reports whether uid was created by m and has a timestamp older than
|
||||
// the current time by at least d.
|
||||
func (s *UIDSpace) Older(uid string, d time.Duration) bool {
|
||||
ts, ok := s.Timestamp(uid)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return time.Since(ts) > d
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
s := NewUIDSpace("prefix")
|
||||
tm := time.Date(2017, 1, 6, 0, 0, 0, 21, time.UTC)
|
||||
got := s.newID(tm)
|
||||
want := "prefix-20170106-21-0000"
|
||||
if got != want {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
|
||||
s2 := NewUIDSpaceSep("prefix2", '_')
|
||||
got = s2.newID(tm)
|
||||
want = "prefix2_20170106_21_0000"
|
||||
if got != want {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimestamp(t *testing.T) {
|
||||
s := NewUIDSpace("unique-ID")
|
||||
uid := s.New()
|
||||
got, ok := s.Timestamp(uid)
|
||||
if !ok {
|
||||
t.Fatal("got ok = false, want true")
|
||||
}
|
||||
if !startTime.Equal(got) {
|
||||
t.Errorf("got %s, want %s", got, startTime)
|
||||
}
|
||||
|
||||
got, ok = s.Timestamp("unique-ID-20160308-123-8")
|
||||
if !ok {
|
||||
t.Fatal("got false, want true")
|
||||
}
|
||||
if want := time.Date(2016, 3, 8, 0, 0, 0, 123, time.UTC); !want.Equal(got) {
|
||||
t.Errorf("got %s, want %s", got, want)
|
||||
}
|
||||
if _, ok = s.Timestamp("invalid-time-1234"); ok {
|
||||
t.Error("got true, want false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOlder(t *testing.T) {
|
||||
s := NewUIDSpace("uid")
|
||||
// A non-matching ID returns false.
|
||||
id2 := NewUIDSpace("different-prefix").New()
|
||||
if got, want := s.Older(id2, time.Second), false; got != want {
|
||||
t.Errorf("got %t, want %t", got, want)
|
||||
}
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
// Copyright 2018 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"go.opencensus.io/trace"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/genproto/googleapis/rpc/code"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func StartSpan(ctx context.Context, name string) context.Context {
|
||||
ctx, _ = trace.StartSpan(ctx, name)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func EndSpan(ctx context.Context, err error) {
|
||||
span := trace.FromContext(ctx)
|
||||
if err != nil {
|
||||
span.SetStatus(toStatus(err))
|
||||
}
|
||||
span.End()
|
||||
}
|
||||
|
||||
// ToStatus interrogates an error and converts it to an appropriate
|
||||
// OpenCensus status.
|
||||
func toStatus(err error) trace.Status {
|
||||
if err2, ok := err.(*googleapi.Error); ok {
|
||||
return trace.Status{Code: httpStatusCodeToOCCode(err2.Code), Message: err2.Message}
|
||||
} else if s, ok := status.FromError(err); ok {
|
||||
return trace.Status{Code: int32(s.Code()), Message: s.Message()}
|
||||
} else {
|
||||
return trace.Status{Code: int32(code.Code_UNKNOWN), Message: err.Error()}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (deklerk): switch to using OpenCensus function when it becomes available.
|
||||
// Reference: https://github.com/googleapis/googleapis/blob/26b634d2724ac5dd30ae0b0cbfb01f07f2e4050e/google/rpc/code.proto
|
||||
func httpStatusCodeToOCCode(httpStatusCode int) int32 {
|
||||
switch httpStatusCode {
|
||||
case 200:
|
||||
return int32(code.Code_OK)
|
||||
case 499:
|
||||
return int32(code.Code_CANCELLED)
|
||||
case 500:
|
||||
return int32(code.Code_UNKNOWN) // Could also be Code_INTERNAL, Code_DATA_LOSS
|
||||
case 400:
|
||||
return int32(code.Code_INVALID_ARGUMENT) // Could also be Code_OUT_OF_RANGE
|
||||
case 504:
|
||||
return int32(code.Code_DEADLINE_EXCEEDED)
|
||||
case 404:
|
||||
return int32(code.Code_NOT_FOUND)
|
||||
case 409:
|
||||
return int32(code.Code_ALREADY_EXISTS) // Could also be Code_ABORTED
|
||||
case 403:
|
||||
return int32(code.Code_PERMISSION_DENIED)
|
||||
case 401:
|
||||
return int32(code.Code_UNAUTHENTICATED)
|
||||
case 429:
|
||||
return int32(code.Code_RESOURCE_EXHAUSTED)
|
||||
case 501:
|
||||
return int32(code.Code_UNIMPLEMENTED)
|
||||
case 503:
|
||||
return int32(code.Code_UNAVAILABLE)
|
||||
default:
|
||||
return int32(code.Code_UNKNOWN)
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
// Copyright 2018 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build go1.8
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
octrace "go.opencensus.io/trace"
|
||||
"google.golang.org/api/googleapi"
|
||||
"google.golang.org/genproto/googleapis/rpc/code"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func TestToStatus(t *testing.T) {
|
||||
for _, testcase := range []struct {
|
||||
input error
|
||||
want octrace.Status
|
||||
}{
|
||||
{
|
||||
errors.New("some random error"),
|
||||
octrace.Status{Code: int32(code.Code_UNKNOWN), Message: "some random error"},
|
||||
},
|
||||
{
|
||||
&googleapi.Error{Code: http.StatusConflict, Message: "some specific googleapi http error"},
|
||||
octrace.Status{Code: int32(code.Code_ALREADY_EXISTS), Message: "some specific googleapi http error"},
|
||||
},
|
||||
{
|
||||
status.Error(codes.DataLoss, "some specific grpc error"),
|
||||
octrace.Status{Code: int32(code.Code_DATA_LOSS), Message: "some specific grpc error"},
|
||||
},
|
||||
} {
|
||||
got := toStatus(testcase.input)
|
||||
if r := testutil.Diff(got, testcase.want); r != "" {
|
||||
t.Errorf("got -, want +:\n%s", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// Copyright 2018 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !go1.8
|
||||
|
||||
package trace
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// OpenCensus only supports go 1.8 and higher.
|
||||
|
||||
func StartSpan(ctx context.Context, _ string) context.Context {
|
||||
return ctx
|
||||
}
|
||||
|
||||
func EndSpan(context.Context, error) {
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
// Copyright 2017 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package tracecontext provides encoders and decoders for Stackdriver Trace contexts.
|
||||
package tracecontext
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
const (
|
||||
versionID = 0
|
||||
traceIDField = 0
|
||||
spanIDField = 1
|
||||
optsField = 2
|
||||
|
||||
traceIDLen = 16
|
||||
spanIDLen = 8
|
||||
optsLen = 1
|
||||
|
||||
// Len represents the length of trace context.
|
||||
Len = 1 + 1 + traceIDLen + 1 + spanIDLen + 1 + optsLen
|
||||
)
|
||||
|
||||
// Encode encodes trace ID, span ID and options into dst. The number of bytes
|
||||
// written will be returned. If len(dst) isn't big enough to fit the trace context,
|
||||
// a negative number is returned.
|
||||
func Encode(dst []byte, traceID []byte, spanID uint64, opts byte) (n int) {
|
||||
if len(dst) < Len {
|
||||
return -1
|
||||
}
|
||||
var offset = 0
|
||||
putByte := func(b byte) { dst[offset] = b; offset++ }
|
||||
putUint64 := func(u uint64) { binary.LittleEndian.PutUint64(dst[offset:], u); offset += 8 }
|
||||
|
||||
putByte(versionID)
|
||||
putByte(traceIDField)
|
||||
for _, b := range traceID {
|
||||
putByte(b)
|
||||
}
|
||||
putByte(spanIDField)
|
||||
putUint64(spanID)
|
||||
putByte(optsField)
|
||||
putByte(opts)
|
||||
|
||||
return offset
|
||||
}
|
||||
|
||||
// Decode decodes the src into a trace ID, span ID and options. If src doesn't
|
||||
// contain a valid trace context, ok = false is returned.
|
||||
func Decode(src []byte) (traceID []byte, spanID uint64, opts byte, ok bool) {
|
||||
if len(src) < Len {
|
||||
return traceID, spanID, 0, false
|
||||
}
|
||||
var offset = 0
|
||||
readByte := func() byte { b := src[offset]; offset++; return b }
|
||||
readUint64 := func() uint64 { v := binary.LittleEndian.Uint64(src[offset:]); offset += 8; return v }
|
||||
|
||||
if readByte() != versionID {
|
||||
return traceID, spanID, 0, false
|
||||
}
|
||||
for offset < len(src) {
|
||||
switch readByte() {
|
||||
case traceIDField:
|
||||
traceID = src[offset : offset+traceIDLen]
|
||||
offset += traceIDLen
|
||||
case spanIDField:
|
||||
spanID = readUint64()
|
||||
case optsField:
|
||||
opts = readByte()
|
||||
}
|
||||
}
|
||||
return traceID, spanID, opts, true
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package tracecontext
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"cloud.google.com/go/internal/testutil"
|
||||
)
|
||||
|
||||
var validData = []byte{0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 1, 97, 98, 99, 100, 101, 102, 103, 104, 2, 1}
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
wantTraceID []byte
|
||||
wantSpanID uint64
|
||||
wantOpts byte
|
||||
wantOk bool
|
||||
}{
|
||||
{
|
||||
name: "nil data",
|
||||
data: nil,
|
||||
wantTraceID: nil,
|
||||
wantSpanID: 0,
|
||||
wantOpts: 0,
|
||||
wantOk: false,
|
||||
},
|
||||
{
|
||||
name: "short data",
|
||||
data: []byte{0, 0, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77},
|
||||
wantTraceID: nil,
|
||||
wantSpanID: 0,
|
||||
wantOpts: 0,
|
||||
wantOk: false,
|
||||
},
|
||||
{
|
||||
name: "wrong field number",
|
||||
data: []byte{0, 1, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77},
|
||||
wantTraceID: nil,
|
||||
wantSpanID: 0,
|
||||
wantOpts: 0,
|
||||
wantOk: false,
|
||||
},
|
||||
{
|
||||
name: "valid data",
|
||||
data: validData,
|
||||
wantTraceID: []byte{64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79},
|
||||
wantSpanID: 0x6867666564636261,
|
||||
wantOpts: 1,
|
||||
wantOk: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
gotTraceID, gotSpanID, gotOpts, gotOk := Decode(tt.data)
|
||||
if !testutil.Equal(gotTraceID, tt.wantTraceID) {
|
||||
t.Errorf("%s: Decode() gotTraceID = %v, want %v", tt.name, gotTraceID, tt.wantTraceID)
|
||||
}
|
||||
if gotSpanID != tt.wantSpanID {
|
||||
t.Errorf("%s: Decode() gotSpanID = %v, want %v", tt.name, gotSpanID, tt.wantSpanID)
|
||||
}
|
||||
if gotOpts != tt.wantOpts {
|
||||
t.Errorf("%s: Decode() gotOpts = %v, want %v", tt.name, gotOpts, tt.wantOpts)
|
||||
}
|
||||
if gotOk != tt.wantOk {
|
||||
t.Errorf("%s: Decode() gotOk = %v, want %v", tt.name, gotOk, tt.wantOk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dst []byte
|
||||
traceID []byte
|
||||
spanID uint64
|
||||
opts byte
|
||||
wantN int
|
||||
wantData []byte
|
||||
}{
|
||||
{
|
||||
name: "short data",
|
||||
dst: make([]byte, 0),
|
||||
traceID: []byte("00112233445566"),
|
||||
spanID: 0x6867666564636261,
|
||||
opts: 1,
|
||||
wantN: -1,
|
||||
wantData: make([]byte, 0),
|
||||
},
|
||||
{
|
||||
name: "valid data",
|
||||
dst: make([]byte, Len),
|
||||
traceID: []byte{64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79},
|
||||
spanID: 0x6867666564636261,
|
||||
opts: 1,
|
||||
wantN: Len,
|
||||
wantData: validData,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
gotN := Encode(tt.dst, tt.traceID, tt.spanID, tt.opts)
|
||||
if gotN != tt.wantN {
|
||||
t.Errorf("%s: n = %v, want %v", tt.name, gotN, tt.wantN)
|
||||
}
|
||||
if gotData := tt.dst; !testutil.Equal(gotData, tt.wantData) {
|
||||
t.Errorf("%s: dst = %v, want %v", tt.name, gotData, tt.wantData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecode(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Decode(validData)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncode(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
traceID := make([]byte, 16)
|
||||
var opts byte
|
||||
Encode(validData, traceID, 0, opts)
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
today=$(date +%Y%m%d)
|
||||
|
||||
sed -i -r -e 's/const Repo = "([0-9]{8})"/const Repo = "'$today'"/' $GOFILE
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:generate ./update_version.sh
|
||||
|
||||
// Package version contains version information for Google Cloud Client
|
||||
// Libraries for Go, as reported in request headers.
|
||||
package version
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Repo is the current version of the client libraries in this
|
||||
// repo. It should be a date in YYYYMMDD format.
|
||||
const Repo = "20180226"
|
||||
|
||||
// Go returns the Go runtime version. The returned string
|
||||
// has no whitespace.
|
||||
func Go() string {
|
||||
return goVersion
|
||||
}
|
||||
|
||||
var goVersion = goVer(runtime.Version())
|
||||
|
||||
const develPrefix = "devel +"
|
||||
|
||||
func goVer(s string) string {
|
||||
if strings.HasPrefix(s, develPrefix) {
|
||||
s = s[len(develPrefix):]
|
||||
if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
|
||||
s = s[:p]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
if strings.HasPrefix(s, "go1") {
|
||||
s = s[2:]
|
||||
var prerelease string
|
||||
if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
|
||||
s, prerelease = s[:p], s[p:]
|
||||
}
|
||||
if strings.HasSuffix(s, ".") {
|
||||
s += "0"
|
||||
} else if strings.Count(s, ".") < 2 {
|
||||
s += ".0"
|
||||
}
|
||||
if prerelease != "" {
|
||||
s += "-" + prerelease
|
||||
}
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func notSemverRune(r rune) bool {
|
||||
return strings.IndexRune("0123456789.", r) < 0
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright 2016 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package version
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGoVer(t *testing.T) {
|
||||
for _, tst := range []struct {
|
||||
in, want string
|
||||
}{
|
||||
{"go1.8", "1.8.0"},
|
||||
{"go1.7.3", "1.7.3"},
|
||||
{"go1.8.typealias", "1.8.0-typealias"},
|
||||
{"go1.8beta1", "1.8.0-beta1"},
|
||||
{"go1.8rc2", "1.8.0-rc2"},
|
||||
{"devel +824f981dd4b7 Tue Apr 29 21:41:54 2014 -0400", "824f981dd4b7"},
|
||||
{"foo bar zipzap", ""},
|
||||
} {
|
||||
if got := goVer(tst.in); got != tst.want {
|
||||
t.Errorf("goVer(%q) = %q, want %q", tst.in, got, tst.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user