mirror of
https://github.com/restic/restic.git
synced 2024-09-07 20:09:40 +02:00
271 lines
7.3 KiB
Go
271 lines
7.3 KiB
Go
|
// 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 iterator_test
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"math"
|
||
|
"reflect"
|
||
|
"testing"
|
||
|
|
||
|
"golang.org/x/net/context"
|
||
|
"google.golang.org/api/iterator"
|
||
|
itest "google.golang.org/api/iterator/testing"
|
||
|
)
|
||
|
|
||
|
// Service represents the implementation of a Google API's List method.
|
||
|
// We want to test against a large range of possible valid behaviors.
|
||
|
// All the behaviors this can generate are valid under the spec for
|
||
|
// Google API paging.
|
||
|
type service struct {
|
||
|
// End of the sequence. end-1 is the last value returned.
|
||
|
end int
|
||
|
// Maximum number of items to return in one RPC. Also the default page size.
|
||
|
// If zero, max is unlimited.
|
||
|
max int
|
||
|
// If true, return two empty pages before each RPC that returns items, and
|
||
|
// two zero pages at the end. E.g. if end = 5, max = 2 and the pageSize
|
||
|
// parameter to List is zero, then the number of items returned in
|
||
|
// successive RPCS is:
|
||
|
// 0 0 2 0 0 2 0 0 1 0 0
|
||
|
// Note that this implies that the RPC returning the last items will have a
|
||
|
// non-empty page token.
|
||
|
zeroes bool
|
||
|
}
|
||
|
|
||
|
// List simulates an API List RPC. It returns integers in the range [0, s.end).
|
||
|
func (s *service) List(pageSize int, pageToken string) ([]int, string, error) {
|
||
|
max := s.max
|
||
|
if max == 0 {
|
||
|
max = math.MaxInt64
|
||
|
}
|
||
|
// Never give back any more than s.max.
|
||
|
if pageSize <= 0 || pageSize > max {
|
||
|
pageSize = max
|
||
|
}
|
||
|
state := &listState{}
|
||
|
if pageToken != "" {
|
||
|
if err := json.Unmarshal([]byte(pageToken), state); err != nil {
|
||
|
return nil, "", err
|
||
|
}
|
||
|
}
|
||
|
ints := state.advance(pageSize, s.end, s.zeroes)
|
||
|
if state.Start == s.end && (!s.zeroes || state.NumZeroes == 2) {
|
||
|
pageToken = ""
|
||
|
} else {
|
||
|
bytes, err := json.Marshal(state)
|
||
|
if err != nil {
|
||
|
return nil, "", err
|
||
|
}
|
||
|
pageToken = string(bytes)
|
||
|
}
|
||
|
return ints, pageToken, nil
|
||
|
}
|
||
|
|
||
|
type listState struct {
|
||
|
Start int // where to start this page
|
||
|
NumZeroes int // number of consecutive empty pages before this
|
||
|
}
|
||
|
|
||
|
func (s *listState) advance(pageSize, end int, zeroes bool) []int {
|
||
|
var page []int
|
||
|
if zeroes && s.NumZeroes != 2 {
|
||
|
// Return a zero page.
|
||
|
} else {
|
||
|
for i := s.Start; i < end && len(page) < pageSize; i++ {
|
||
|
page = append(page, i)
|
||
|
}
|
||
|
}
|
||
|
s.Start += len(page)
|
||
|
if len(page) == 0 {
|
||
|
s.NumZeroes++
|
||
|
} else {
|
||
|
s.NumZeroes = 0
|
||
|
}
|
||
|
return page
|
||
|
}
|
||
|
|
||
|
func TestServiceList(t *testing.T) {
|
||
|
for _, test := range []struct {
|
||
|
svc service
|
||
|
pageSize int
|
||
|
want [][]int
|
||
|
}{
|
||
|
{service{end: 0}, 0, [][]int{nil}},
|
||
|
{service{end: 5}, 0, [][]int{{0, 1, 2, 3, 4}}},
|
||
|
{service{end: 5}, 8, [][]int{{0, 1, 2, 3, 4}}},
|
||
|
{service{end: 5}, 2, [][]int{{0, 1}, {2, 3}, {4}}},
|
||
|
{service{end: 5, max: 2}, 0, [][]int{{0, 1}, {2, 3}, {4}}},
|
||
|
{service{end: 5, max: 2}, 1, [][]int{{0}, {1}, {2}, {3}, {4}}},
|
||
|
{service{end: 5, max: 2}, 10, [][]int{{0, 1}, {2, 3}, {4}}},
|
||
|
{service{end: 5, zeroes: true}, 0, [][]int{nil, nil, {0, 1, 2, 3, 4}, nil, nil}},
|
||
|
{service{end: 5, max: 3, zeroes: true}, 0, [][]int{nil, nil, {0, 1, 2}, nil, nil, {3, 4}, nil, nil}},
|
||
|
} {
|
||
|
var got [][]int
|
||
|
token := ""
|
||
|
for {
|
||
|
items, nextToken, err := test.svc.List(test.pageSize, token)
|
||
|
if err != nil {
|
||
|
t.Fatalf("%v, %d: %v", test.svc, test.pageSize, err)
|
||
|
}
|
||
|
got = append(got, items)
|
||
|
if nextToken == "" {
|
||
|
break
|
||
|
}
|
||
|
token = nextToken
|
||
|
}
|
||
|
if !reflect.DeepEqual(got, test.want) {
|
||
|
t.Errorf("%v, %d: got %v, want %v", test.svc, test.pageSize, got, test.want)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
type Client struct{ s *service }
|
||
|
|
||
|
// ItemIterator is a sample implementation of a standard iterator.
|
||
|
type ItemIterator struct {
|
||
|
pageInfo *iterator.PageInfo
|
||
|
nextFunc func() error
|
||
|
s *service
|
||
|
items []int
|
||
|
}
|
||
|
|
||
|
// PageInfo returns a PageInfo, which supports pagination.
|
||
|
func (it *ItemIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
|
||
|
|
||
|
// Items is a sample implementation of an iterator-creating method.
|
||
|
func (c *Client) Items(ctx context.Context) *ItemIterator {
|
||
|
it := &ItemIterator{s: c.s}
|
||
|
it.pageInfo, it.nextFunc = iterator.NewPageInfo(
|
||
|
it.fetch,
|
||
|
func() int { return len(it.items) },
|
||
|
func() interface{} { b := it.items; it.items = nil; return b })
|
||
|
return it
|
||
|
}
|
||
|
|
||
|
func (it *ItemIterator) fetch(pageSize int, pageToken string) (string, error) {
|
||
|
items, tok, err := it.s.List(pageSize, pageToken)
|
||
|
it.items = append(it.items, items...)
|
||
|
return tok, err
|
||
|
}
|
||
|
|
||
|
func (it *ItemIterator) Next() (int, error) {
|
||
|
if err := it.nextFunc(); err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
item := it.items[0]
|
||
|
it.items = it.items[1:]
|
||
|
return item, nil
|
||
|
}
|
||
|
|
||
|
func TestNext(t *testing.T) {
|
||
|
// Test the iterator's Next method with a variety of different service behaviors.
|
||
|
// This is primarily a test of PageInfo.next.
|
||
|
for _, svc := range []service{
|
||
|
{end: 0},
|
||
|
{end: 5},
|
||
|
{end: 5, max: 1},
|
||
|
{end: 5, max: 2},
|
||
|
{end: 5, zeroes: true},
|
||
|
{end: 5, max: 2, zeroes: true},
|
||
|
} {
|
||
|
client := &Client{&svc}
|
||
|
|
||
|
msg, ok := itest.TestIterator(
|
||
|
seq(0, svc.end),
|
||
|
func() interface{} { return client.Items(ctx) },
|
||
|
func(it interface{}) (interface{}, error) { return it.(*ItemIterator).Next() })
|
||
|
if !ok {
|
||
|
t.Errorf("%+v: %s", svc, msg)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TODO(jba): test setting PageInfo.MaxSize
|
||
|
// TODO(jba): test setting PageInfo.Token
|
||
|
|
||
|
// Verify that, for an iterator that uses PageInfo.next to implement its Next
|
||
|
// method, using Next and NextPage together result in an error.
|
||
|
func TestNextWithNextPage(t *testing.T) {
|
||
|
client := &Client{&service{end: 11}}
|
||
|
var items []int
|
||
|
|
||
|
// Calling Next before NextPage.
|
||
|
it := client.Items(ctx)
|
||
|
it.Next()
|
||
|
_, err := iterator.NewPager(it, 1, "").NextPage(&items)
|
||
|
if err == nil {
|
||
|
t.Error("NextPage after Next: got nil, want error")
|
||
|
}
|
||
|
_, err = it.Next()
|
||
|
if err == nil {
|
||
|
t.Error("Next after NextPage: got nil, want error")
|
||
|
}
|
||
|
|
||
|
// Next between two calls to NextPage.
|
||
|
it = client.Items(ctx)
|
||
|
p := iterator.NewPager(it, 1, "")
|
||
|
p.NextPage(&items)
|
||
|
_, err = it.Next()
|
||
|
if err == nil {
|
||
|
t.Error("Next after NextPage: got nil, want error")
|
||
|
}
|
||
|
_, err = p.NextPage(&items)
|
||
|
if err == nil {
|
||
|
t.Error("second NextPage after Next: got nil, want error")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Verify that we turn various potential reflection panics into errors.
|
||
|
func TestNextPageReflectionErrors(t *testing.T) {
|
||
|
client := &Client{&service{end: 1}}
|
||
|
p := iterator.NewPager(client.Items(ctx), 1, "")
|
||
|
|
||
|
// Passing the nil interface value.
|
||
|
_, err := p.NextPage(nil)
|
||
|
if err == nil {
|
||
|
t.Error("nil: got nil, want error")
|
||
|
}
|
||
|
|
||
|
// Passing a non-slice.
|
||
|
_, err = p.NextPage(17)
|
||
|
if err == nil {
|
||
|
t.Error("non-slice: got nil, want error")
|
||
|
}
|
||
|
|
||
|
// Passing a slice of the wrong type.
|
||
|
var bools []bool
|
||
|
_, err = p.NextPage(&bools)
|
||
|
if err == nil {
|
||
|
t.Error("wrong type: got nil, want error")
|
||
|
}
|
||
|
|
||
|
// Using a slice of the right type, but not passing a pointer to it.
|
||
|
var ints []int
|
||
|
_, err = p.NextPage(ints)
|
||
|
if err == nil {
|
||
|
t.Error("not a pointer: got nil, want error")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// seq returns a slice containing the values in [from, to).
|
||
|
func seq(from, to int) []int {
|
||
|
var r []int
|
||
|
for i := from; i < to; i++ {
|
||
|
r = append(r, i)
|
||
|
}
|
||
|
return r
|
||
|
}
|