|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- // Copyright 2016 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- // Package testing provides support functions for testing iterators conforming
- // to the standard pattern.
- // See package google.golang.org/api/iterator and
- // https://github.com/GoogleCloudPlatform/gcloud-golang/wiki/Iterator-Guidelines.
- package testing
-
- import (
- "fmt"
- "reflect"
-
- "google.golang.org/api/iterator"
- )
-
- // TestIterator tests the Next method of a standard iterator. It assumes that
- // the underlying sequence to be iterated over already exists.
- //
- // The want argument should be a slice that contains the elements of this
- // sequence. It may be an empty slice, but it must not be the nil interface
- // value. The elements must be comparable with reflect.DeepEqual.
- //
- // The create function should create and return a new iterator.
- // It will typically look like
- // func() interface{} { return client.Items(ctx) }
- //
- // The next function takes the return value of create and should return the
- // result of calling Next on the iterator. It can usually be defined as
- // func(it interface{}) (interface{}, error) { return it.(*ItemIterator).Next() }
- //
- // TestIterator checks that the iterator returns all the elements of want
- // in order, followed by (zero, done). It also confirms that subsequent calls
- // to next also return (zero, done).
- //
- // If the iterator implements the method
- // PageInfo() *iterator.PageInfo
- // then exact pagination with iterator.Pager is also tested. Pagination testing
- // will be more informative if the want slice contains at least three elements.
- //
- // On success, TestIterator returns ("", true). On failure, it returns a
- // suitable error message and false.
- func TestIterator(want interface{}, create func() interface{}, next func(interface{}) (interface{}, error)) (string, bool) {
- vWant := reflect.ValueOf(want)
- if vWant.Kind() != reflect.Slice {
- return "'want' must be a slice", false
- }
- it := create()
- msg, ok := testNext(vWant, it, next)
- if !ok {
- return msg, ok
- }
- if _, ok := it.(iterator.Pageable); !ok || vWant.Len() == 0 {
- return "", true
- }
- return testPaging(vWant, create, next)
- }
-
- // Check that the iterator returns vWant, the desired sequence.
- func testNext(vWant reflect.Value, it interface{}, next func(interface{}) (interface{}, error)) (string, bool) {
- for i := 0; i < vWant.Len(); i++ {
- got, err := next(it)
- if err != nil {
- return fmt.Sprintf("#%d: got %v, expected an item", i, err), false
- }
- w := vWant.Index(i).Interface()
- if !reflect.DeepEqual(got, w) {
- return fmt.Sprintf("#%d: got %+v, want %+v", i, got, w), false
- }
- }
- // We now should see (<zero value of item type>, done), no matter how many
- // additional calls we make.
- zero := reflect.Zero(vWant.Type().Elem()).Interface()
- for i := 0; i < 3; i++ {
- got, err := next(it)
- if err != iterator.Done {
- return fmt.Sprintf("at end: got error %v, want iterator.Done", err), false
- }
- // Since err == iterator.Done, got should be zero.
- if got != zero {
- return fmt.Sprintf("got %+v with done, want zero %T", got, zero), false
- }
- }
- return "", true
- }
-
- // Test the iterator's behavior when used with iterator.Pager.
- func testPaging(vWant reflect.Value, create func() interface{}, next func(interface{}) (interface{}, error)) (string, bool) {
- // Test page sizes that are smaller, equal to, and greater than the length
- // of the expected sequence.
- for _, pageSize := range []int{1, 2, vWant.Len(), vWant.Len() + 10} {
- wantPages := wantedPages(vWant, pageSize)
- // Test the Pager in two ways.
- // First, by creating a single Pager and calling NextPage in a loop,
- // ignoring the page token except for detecting the end of the
- // iteration.
- it := create().(iterator.Pageable)
- pager := iterator.NewPager(it, pageSize, "")
- msg, ok := testPager(fmt.Sprintf("ignore page token, pageSize %d", pageSize),
- vWant.Type(), wantPages,
- func(_ string, pagep interface{}) (string, error) {
- return pager.NextPage(pagep)
- })
- if !ok {
- return msg, false
- }
- // Second, by creating a new Pager for each page, passing in the page
- // token from the previous page, as would be done in a web handler.
- it = create().(iterator.Pageable)
- msg, ok = testPager(fmt.Sprintf("use page token, pageSize %d", pageSize),
- vWant.Type(), wantPages,
- func(pageToken string, pagep interface{}) (string, error) {
- return iterator.NewPager(it, pageSize, pageToken).NextPage(pagep)
- })
- if !ok {
- return msg, false
- }
- }
- return "", true
- }
-
- // Create the pages we expect to see.
- func wantedPages(vWant reflect.Value, pageSize int) []interface{} {
- var pages []interface{}
- for i, j := 0, pageSize; i < vWant.Len(); i, j = j, j+pageSize {
- if j > vWant.Len() {
- j = vWant.Len()
- }
- pages = append(pages, vWant.Slice(i, j).Interface())
- }
- return pages
- }
-
- func testPager(prefix string, sliceType reflect.Type, wantPages []interface{},
- nextPage func(pageToken string, pagep interface{}) (string, error)) (string, bool) {
- tok := ""
- var err error
- for i := 0; i < len(wantPages)+1; i++ {
- vpagep := reflect.New(sliceType)
- tok, err = nextPage(tok, vpagep.Interface())
- if err != nil {
- return fmt.Sprintf("%s, page #%d: got error %v", prefix, i, err), false
- }
- if i == len(wantPages) {
- // Allow one empty page at the end.
- if vpagep.Elem().Len() != 0 || tok != "" {
- return fmt.Sprintf("%s: did not get one empty page at end", prefix), false
- }
- break
- }
- if msg, ok := compareSlices(vpagep.Elem(), reflect.ValueOf(wantPages[i])); !ok {
- return fmt.Sprintf("%s, page #%d:\n%s", prefix, i, msg), false
- }
- if tok == "" {
- if i != len(wantPages)-1 {
- return fmt.Sprintf("%s, page #%d: got empty page token", prefix, i), false
- }
- break
- }
- }
- return "", true
- }
-
- // Compare two slices element-by-element. If they are equal, return ("", true).
- // Otherwise, return a description of the difference and false.
- func compareSlices(vgot, vwant reflect.Value) (string, bool) {
- if got, want := vgot.Len(), vwant.Len(); got != want {
- return fmt.Sprintf("got %d items, want %d", got, want), false
- }
- for i := 0; i < vgot.Len(); i++ {
- if got, want := vgot.Index(i).Interface(), vwant.Index(i).Interface(); !reflect.DeepEqual(got, want) {
- return fmt.Sprintf("got[%d] = %+v\nwant = %+v", i, got, want), false
- }
- }
- return "", true
- }
|