...

Source file src/google.golang.org/api/iterator/iterator_test.go

Documentation: google.golang.org/api/iterator

     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  
     5  package iterator_test
     6  
     7  import (
     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.
    22  type 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).
    39  func (s *service) List(pageSize int, pageToken string) ([]int, string, error) {
    40  	max := s.max
    41  	if max == 0 {
    42  		max = math.MaxInt
    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  
    67  type listState struct {
    68  	Start     int // where to start this page
    69  	NumZeroes int // number of consecutive empty pages before this
    70  }
    71  
    72  func (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  
    90  func 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  
   125  type Client struct{ s *service }
   126  
   127  // ItemIterator is a sample implementation of a standard iterator.
   128  type 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.
   136  func (it *ItemIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
   137  
   138  // Items is a sample implementation of an iterator-creating method.
   139  func (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  
   148  func (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  
   154  func (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  
   163  func 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.
   191  func 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.
   222  func 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).
   254  func 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  

View as plain text