initial commit

Signed-off-by: Derek McQuay <derekmcquay@gmail.com>
This commit is contained in:
dm
2018-04-10 19:17:26 -07:00
commit 59af8fc84f
2025 changed files with 456739 additions and 0 deletions
+54
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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]
}
File diff suppressed because it is too large Load Diff
+422
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
Binary file not shown.
+108
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,2 @@
[snip]:# (unknown)
+1
View File
@@ -0,0 +1 @@
[snip]:# missing-parens
+21
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
}
}