|
- // 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
- }
|