1// Copyright 2016 Google LLC.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package iterator_test
6
7import (
8	"context"
9	"encoding/json"
10	"math"
11	"reflect"
12	"testing"
13
14	"google.golang.org/api/iterator"
15	itest "google.golang.org/api/iterator/testing"
16)
17
18// Service represents the implementation of a Google API's List method.
19// We want to test against a large range of possible valid behaviors.
20// All the behaviors this can generate are valid under the spec for
21// Google API paging.
22type service struct {
23	// End of the sequence. end-1 is the last value returned.
24	end int
25	// Maximum number of items to return in one RPC. Also the default page size.
26	// If zero, max is unlimited.
27	max int
28	// If true, return two empty pages before each RPC that returns items, and
29	// two zero pages at the end. E.g. if end = 5, max = 2 and the pageSize
30	// parameter to List is zero, then the number of items returned in
31	// successive RPCS is:
32	//    0 0 2 0 0 2 0 0 1 0 0
33	// Note that this implies that the RPC returning the last items will have a
34	// non-empty page token.
35	zeroes bool
36}
37
38// List simulates an API List RPC. It returns integers in the range [0, s.end).
39func (s *service) List(pageSize int, pageToken string) ([]int, string, error) {
40	max := s.max
41	if max == 0 {
42		max = math.MaxInt64
43	}
44	// Never give back any more than s.max.
45	if pageSize <= 0 || pageSize > max {
46		pageSize = max
47	}
48	state := &listState{}
49	if pageToken != "" {
50		if err := json.Unmarshal([]byte(pageToken), state); err != nil {
51			return nil, "", err
52		}
53	}
54	ints := state.advance(pageSize, s.end, s.zeroes)
55	if state.Start == s.end && (!s.zeroes || state.NumZeroes == 2) {
56		pageToken = ""
57	} else {
58		bytes, err := json.Marshal(state)
59		if err != nil {
60			return nil, "", err
61		}
62		pageToken = string(bytes)
63	}
64	return ints, pageToken, nil
65}
66
67type listState struct {
68	Start     int // where to start this page
69	NumZeroes int // number of consecutive empty pages before this
70}
71
72func (s *listState) advance(pageSize, end int, zeroes bool) []int {
73	var page []int
74	if zeroes && s.NumZeroes != 2 {
75		// Return a zero page.
76	} else {
77		for i := s.Start; i < end && len(page) < pageSize; i++ {
78			page = append(page, i)
79		}
80	}
81	s.Start += len(page)
82	if len(page) == 0 {
83		s.NumZeroes++
84	} else {
85		s.NumZeroes = 0
86	}
87	return page
88}
89
90func TestServiceList(t *testing.T) {
91	for _, test := range []struct {
92		svc      service
93		pageSize int
94		want     [][]int
95	}{
96		{service{end: 0}, 0, [][]int{nil}},
97		{service{end: 5}, 0, [][]int{{0, 1, 2, 3, 4}}},
98		{service{end: 5}, 8, [][]int{{0, 1, 2, 3, 4}}},
99		{service{end: 5}, 2, [][]int{{0, 1}, {2, 3}, {4}}},
100		{service{end: 5, max: 2}, 0, [][]int{{0, 1}, {2, 3}, {4}}},
101		{service{end: 5, max: 2}, 1, [][]int{{0}, {1}, {2}, {3}, {4}}},
102		{service{end: 5, max: 2}, 10, [][]int{{0, 1}, {2, 3}, {4}}},
103		{service{end: 5, zeroes: true}, 0, [][]int{nil, nil, {0, 1, 2, 3, 4}, nil, nil}},
104		{service{end: 5, max: 3, zeroes: true}, 0, [][]int{nil, nil, {0, 1, 2}, nil, nil, {3, 4}, nil, nil}},
105	} {
106		var got [][]int
107		token := ""
108		for {
109			items, nextToken, err := test.svc.List(test.pageSize, token)
110			if err != nil {
111				t.Fatalf("%v, %d: %v", test.svc, test.pageSize, err)
112			}
113			got = append(got, items)
114			if nextToken == "" {
115				break
116			}
117			token = nextToken
118		}
119		if !reflect.DeepEqual(got, test.want) {
120			t.Errorf("%v, %d: got %v, want %v", test.svc, test.pageSize, got, test.want)
121		}
122	}
123}
124
125type Client struct{ s *service }
126
127// ItemIterator is a sample implementation of a standard iterator.
128type ItemIterator struct {
129	pageInfo *iterator.PageInfo
130	nextFunc func() error
131	s        *service
132	items    []int
133}
134
135// PageInfo returns a PageInfo, which supports pagination.
136func (it *ItemIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
137
138// Items is a sample implementation of an iterator-creating method.
139func (c *Client) Items(ctx context.Context) *ItemIterator {
140	it := &ItemIterator{s: c.s}
141	it.pageInfo, it.nextFunc = iterator.NewPageInfo(
142		it.fetch,
143		func() int { return len(it.items) },
144		func() interface{} { b := it.items; it.items = nil; return b })
145	return it
146}
147
148func (it *ItemIterator) fetch(pageSize int, pageToken string) (string, error) {
149	items, tok, err := it.s.List(pageSize, pageToken)
150	it.items = append(it.items, items...)
151	return tok, err
152}
153
154func (it *ItemIterator) Next() (int, error) {
155	if err := it.nextFunc(); err != nil {
156		return 0, err
157	}
158	item := it.items[0]
159	it.items = it.items[1:]
160	return item, nil
161}
162
163func TestNext(t *testing.T) {
164	// Test the iterator's Next method with a variety of different service behaviors.
165	// This is primarily a test of PageInfo.next.
166	for _, svc := range []service{
167		{end: 0},
168		{end: 5},
169		{end: 5, max: 1},
170		{end: 5, max: 2},
171		{end: 5, zeroes: true},
172		{end: 5, max: 2, zeroes: true},
173	} {
174		client := &Client{&svc}
175
176		msg, ok := itest.TestIterator(
177			seq(0, svc.end),
178			func() interface{} { return client.Items(ctx) },
179			func(it interface{}) (interface{}, error) { return it.(*ItemIterator).Next() })
180		if !ok {
181			t.Errorf("%+v: %s", svc, msg)
182		}
183	}
184}
185
186// TODO(jba): test setting PageInfo.MaxSize
187// TODO(jba): test setting PageInfo.Token
188
189// Verify that, for an iterator that uses PageInfo.next to implement its Next
190// method, using Next and NextPage together result in an error.
191func TestNextWithNextPage(t *testing.T) {
192	client := &Client{&service{end: 11}}
193	var items []int
194
195	// Calling Next before NextPage.
196	it := client.Items(ctx)
197	it.Next()
198	_, err := iterator.NewPager(it, 1, "").NextPage(&items)
199	if err == nil {
200		t.Error("NextPage after Next: got nil, want error")
201	}
202	_, err = it.Next()
203	if err == nil {
204		t.Error("Next after NextPage: got nil, want error")
205	}
206
207	// Next between two calls to NextPage.
208	it = client.Items(ctx)
209	p := iterator.NewPager(it, 1, "")
210	p.NextPage(&items)
211	_, err = it.Next()
212	if err == nil {
213		t.Error("Next after NextPage: got nil, want error")
214	}
215	_, err = p.NextPage(&items)
216	if err == nil {
217		t.Error("second NextPage after Next: got nil, want error")
218	}
219}
220
221// Verify that we turn various potential reflection panics into errors.
222func TestNextPageReflectionErrors(t *testing.T) {
223	client := &Client{&service{end: 1}}
224	p := iterator.NewPager(client.Items(ctx), 1, "")
225
226	// Passing the nil interface value.
227	_, err := p.NextPage(nil)
228	if err == nil {
229		t.Error("nil: got nil, want error")
230	}
231
232	// Passing a non-slice.
233	_, err = p.NextPage(17)
234	if err == nil {
235		t.Error("non-slice: got nil, want error")
236	}
237
238	// Passing a slice of the wrong type.
239	var bools []bool
240	_, err = p.NextPage(&bools)
241	if err == nil {
242		t.Error("wrong type: got nil, want error")
243	}
244
245	// Using a slice of the right type, but not passing a pointer to it.
246	var ints []int
247	_, err = p.NextPage(ints)
248	if err == nil {
249		t.Error("not a pointer: got nil, want error")
250	}
251}
252
253// seq returns a slice containing the values in [from, to).
254func seq(from, to int) []int {
255	var r []int
256	for i := from; i < to; i++ {
257		r = append(r, i)
258	}
259	return r
260}
261