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 testing provides support functions for testing iterators conforming
6// to the standard pattern.
7// See package google.golang.org/api/iterator and
8// https://github.com/GoogleCloudPlatform/gcloud-golang/wiki/Iterator-Guidelines.
9package testing
10
11import (
12	"fmt"
13	"reflect"
14
15	"google.golang.org/api/iterator"
16)
17
18// TestIterator tests the Next method of a standard iterator. It assumes that
19// the underlying sequence to be iterated over already exists.
20//
21// The want argument should be a slice that contains the elements of this
22// sequence. It may be an empty slice, but it must not be the nil interface
23// value. The elements must be comparable with reflect.DeepEqual.
24//
25// The create function should create and return a new iterator.
26// It will typically look like
27//    func() interface{} { return client.Items(ctx) }
28//
29// The next function takes the return value of create and should return the
30// result of calling Next on the iterator. It can usually be defined as
31//     func(it interface{}) (interface{}, error) { return it.(*ItemIterator).Next() }
32//
33// TestIterator checks that the iterator returns all the elements of want
34// in order, followed by (zero, done). It also confirms that subsequent calls
35// to next also return (zero, done).
36//
37// If the iterator implements the method
38//     PageInfo() *iterator.PageInfo
39// then exact pagination with iterator.Pager is also tested. Pagination testing
40// will be more informative if the want slice contains at least three elements.
41//
42// On success, TestIterator returns ("", true). On failure, it returns a
43// suitable error message and false.
44func TestIterator(want interface{}, create func() interface{}, next func(interface{}) (interface{}, error)) (string, bool) {
45	vWant := reflect.ValueOf(want)
46	if vWant.Kind() != reflect.Slice {
47		return "'want' must be a slice", false
48	}
49	it := create()
50	msg, ok := testNext(vWant, it, next)
51	if !ok {
52		return msg, ok
53	}
54	if _, ok := it.(iterator.Pageable); !ok || vWant.Len() == 0 {
55		return "", true
56	}
57	return testPaging(vWant, create, next)
58}
59
60// Check that the iterator returns vWant, the desired sequence.
61func testNext(vWant reflect.Value, it interface{}, next func(interface{}) (interface{}, error)) (string, bool) {
62	for i := 0; i < vWant.Len(); i++ {
63		got, err := next(it)
64		if err != nil {
65			return fmt.Sprintf("#%d: got %v, expected an item", i, err), false
66		}
67		w := vWant.Index(i).Interface()
68		if !reflect.DeepEqual(got, w) {
69			return fmt.Sprintf("#%d: got %+v, want %+v", i, got, w), false
70		}
71	}
72	// We now should see (<zero value of item type>, done), no matter how many
73	// additional calls we make.
74	zero := reflect.Zero(vWant.Type().Elem()).Interface()
75	for i := 0; i < 3; i++ {
76		got, err := next(it)
77		if err != iterator.Done {
78			return fmt.Sprintf("at end: got error %v, want iterator.Done", err), false
79		}
80		// Since err == iterator.Done, got should be zero.
81		if got != zero {
82			return fmt.Sprintf("got %+v with done, want zero %T", got, zero), false
83		}
84	}
85	return "", true
86}
87
88// Test the iterator's behavior when used with iterator.Pager.
89func testPaging(vWant reflect.Value, create func() interface{}, next func(interface{}) (interface{}, error)) (string, bool) {
90	// Test page sizes that are smaller, equal to, and greater than the length
91	// of the expected sequence.
92	for _, pageSize := range []int{1, 2, vWant.Len(), vWant.Len() + 10} {
93		wantPages := wantedPages(vWant, pageSize)
94		// Test the Pager in two ways.
95		// First, by creating a single Pager and calling NextPage in a loop,
96		// ignoring the page token except for detecting the end of the
97		// iteration.
98		it := create().(iterator.Pageable)
99		pager := iterator.NewPager(it, pageSize, "")
100		msg, ok := testPager(fmt.Sprintf("ignore page token, pageSize %d", pageSize),
101			vWant.Type(), wantPages,
102			func(_ string, pagep interface{}) (string, error) {
103				return pager.NextPage(pagep)
104			})
105		if !ok {
106			return msg, false
107		}
108		// Second, by creating a new Pager for each page, passing in the page
109		// token from the previous page, as would be done in a web handler.
110		it = create().(iterator.Pageable)
111		msg, ok = testPager(fmt.Sprintf("use page token, pageSize %d", pageSize),
112			vWant.Type(), wantPages,
113			func(pageToken string, pagep interface{}) (string, error) {
114				return iterator.NewPager(it, pageSize, pageToken).NextPage(pagep)
115			})
116		if !ok {
117			return msg, false
118		}
119	}
120	return "", true
121}
122
123// Create the pages we expect to see.
124func wantedPages(vWant reflect.Value, pageSize int) []interface{} {
125	var pages []interface{}
126	for i, j := 0, pageSize; i < vWant.Len(); i, j = j, j+pageSize {
127		if j > vWant.Len() {
128			j = vWant.Len()
129		}
130		pages = append(pages, vWant.Slice(i, j).Interface())
131	}
132	return pages
133}
134
135func testPager(prefix string, sliceType reflect.Type, wantPages []interface{},
136	nextPage func(pageToken string, pagep interface{}) (string, error)) (string, bool) {
137	tok := ""
138	var err error
139	for i := 0; i < len(wantPages)+1; i++ {
140		vpagep := reflect.New(sliceType)
141		tok, err = nextPage(tok, vpagep.Interface())
142		if err != nil {
143			return fmt.Sprintf("%s, page #%d: got error %v", prefix, i, err), false
144		}
145		if i == len(wantPages) {
146			// Allow one empty page at the end.
147			if vpagep.Elem().Len() != 0 || tok != "" {
148				return fmt.Sprintf("%s: did not get one empty page at end", prefix), false
149			}
150			break
151		}
152		if msg, ok := compareSlices(vpagep.Elem(), reflect.ValueOf(wantPages[i])); !ok {
153			return fmt.Sprintf("%s, page #%d:\n%s", prefix, i, msg), false
154		}
155		if tok == "" {
156			if i != len(wantPages)-1 {
157				return fmt.Sprintf("%s, page #%d: got empty page token", prefix, i), false
158			}
159			break
160		}
161	}
162	return "", true
163}
164
165// Compare two slices element-by-element. If they are equal, return ("", true).
166// Otherwise, return a description of the difference and false.
167func compareSlices(vgot, vwant reflect.Value) (string, bool) {
168	if got, want := vgot.Len(), vwant.Len(); got != want {
169		return fmt.Sprintf("got %d items, want %d", got, want), false
170	}
171	for i := 0; i < vgot.Len(); i++ {
172		if got, want := vgot.Index(i).Interface(), vwant.Index(i).Interface(); !reflect.DeepEqual(got, want) {
173			return fmt.Sprintf("got[%d] = %+v\nwant   = %+v", i, got, want), false
174		}
175	}
176	return "", true
177}
178