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