...

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

Documentation: google.golang.org/api/iterator/testing

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

View as plain text