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