mirror of
https://github.com/kubernetes-sigs/descheduler.git
synced 2026-01-27 22:14:52 +01:00
187 lines
7.0 KiB
Go
187 lines
7.0 KiB
Go
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
|
|
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 testutil
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
)
|
|
|
|
// TestIteratorNext tests the Next method of a standard client iterator (see
|
|
// https://github.com/GoogleCloudPlatform/google-cloud-go/wiki/Iterator-Guidelines).
|
|
//
|
|
// This function assumes that an iterator has already been created, and that
|
|
// the underlying sequence to be iterated over already exists. want should be a
|
|
// slice that contains the elements of this sequence. It must contain at least
|
|
// one element.
|
|
//
|
|
// done is the special error value that signals that the iterator has provided all the items.
|
|
//
|
|
// next is a function that returns the result of calling Next on the iterator. It can usually be
|
|
// defined as
|
|
// func() (interface{}, error) { return iter.Next() }
|
|
//
|
|
// TestIteratorNext 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).
|
|
//
|
|
// On success, TestIteratorNext returns ("", true). On failure, it returns a
|
|
// suitable error message and false.
|
|
func TestIteratorNext(want interface{}, done error, next func() (interface{}, error)) (string, bool) {
|
|
wVal := reflect.ValueOf(want)
|
|
if wVal.Kind() != reflect.Slice {
|
|
return "'want' must be a slice", false
|
|
}
|
|
for i := 0; i < wVal.Len(); i++ {
|
|
got, err := next()
|
|
if err != nil {
|
|
return fmt.Sprintf("#%d: got %v, expected an item", i, err), false
|
|
}
|
|
w := wVal.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(wVal.Type().Elem()).Interface()
|
|
for i := 0; i < 3; i++ {
|
|
got, err := next()
|
|
if err != done {
|
|
return fmt.Sprintf("at end: got error %v, want done", err), false
|
|
}
|
|
// Since err == done, got should be zero.
|
|
if got != zero {
|
|
return fmt.Sprintf("got %+v with done, want zero %T", got, zero), false
|
|
}
|
|
}
|
|
return "", true
|
|
}
|
|
|
|
// PagingIterator describes the standard client iterator pattern with paging as best as possible in Go.
|
|
// See https://github.com/GoogleCloudPlatform/google-cloud-go/wiki/Iterator-Guidelines.
|
|
type PagingIterator interface {
|
|
SetPageSize(int)
|
|
SetPageToken(string)
|
|
NextPageToken() string
|
|
// NextPage() ([]T, error)
|
|
}
|
|
|
|
// TestIteratorNextPageExact tests the NextPage method of a standard client
|
|
// iterator with paging (see PagingIterator).
|
|
//
|
|
// This function assumes that the underlying sequence to be iterated over
|
|
// already exists. want should be a slice that contains the elements of this
|
|
// sequence. It must contain at least three elements, in order to test
|
|
// non-trivial paging behavior.
|
|
//
|
|
// done is the special error value that signals that the iterator has provided all the items.
|
|
//
|
|
// defaultPageSize is the page size to use when the user has not called SetPageSize, or calls
|
|
// it with a value <= 0.
|
|
//
|
|
// newIter should return a new iterator each time it is called.
|
|
//
|
|
// nextPage should return the result of calling NextPage on the iterator. It can usually be
|
|
// defined as
|
|
// func(i testutil.PagingIterator) (interface{}, error) { return i.(*<iteratorType>).NextPage() }
|
|
//
|
|
// TestIteratorNextPageExact checks that the iterator returns all the elements
|
|
// of want in order, divided into pages of the exactly the right size. It
|
|
// confirms that if the last page is partial, done is returned along with it,
|
|
// and in any case, done is returned subsequently along with a zero-length
|
|
// slice.
|
|
//
|
|
// On success, TestIteratorNextPageExact returns ("", true). On failure, it returns a
|
|
// suitable error message and false.
|
|
func TestIteratorNextPageExact(want interface{}, done error, defaultPageSize int, newIter func() PagingIterator, nextPage func(PagingIterator) (interface{}, error)) (string, bool) {
|
|
wVal := reflect.ValueOf(want)
|
|
if wVal.Kind() != reflect.Slice {
|
|
return "'want' must be a slice", false
|
|
}
|
|
if wVal.Len() < 3 {
|
|
return "need at least 3 values for 'want' to effectively test paging", false
|
|
}
|
|
const doNotSetPageSize = -999
|
|
for _, pageSize := range []int{doNotSetPageSize, -7, 0, 1, 2, wVal.Len(), wVal.Len() + 10} {
|
|
adjustedPageSize := int(pageSize)
|
|
if pageSize <= 0 {
|
|
adjustedPageSize = int(defaultPageSize)
|
|
}
|
|
// Create the pages we expect to see.
|
|
var wantPages []interface{}
|
|
for i, j := 0, adjustedPageSize; i < wVal.Len(); i, j = j, j+adjustedPageSize {
|
|
if j > wVal.Len() {
|
|
j = wVal.Len()
|
|
}
|
|
wantPages = append(wantPages, wVal.Slice(i, j).Interface())
|
|
}
|
|
for _, usePageToken := range []bool{false, true} {
|
|
it := newIter()
|
|
if pageSize != doNotSetPageSize {
|
|
it.SetPageSize(pageSize)
|
|
}
|
|
for i, wantPage := range wantPages {
|
|
gotPage, err := nextPage(it)
|
|
if err != nil && err != done {
|
|
return fmt.Sprintf("usePageToken %v, pageSize %d, #%d: got %v, expected a page",
|
|
usePageToken, pageSize, i, err), false
|
|
}
|
|
if !reflect.DeepEqual(gotPage, wantPage) {
|
|
return fmt.Sprintf("usePageToken %v, pageSize %d, #%d:\ngot %v\nwant %+v",
|
|
usePageToken, pageSize, i, gotPage, wantPage), false
|
|
}
|
|
// If the last page is partial, NextPage must return done.
|
|
if reflect.ValueOf(gotPage).Len() < adjustedPageSize && err != done {
|
|
return fmt.Sprintf("usePageToken %v, pageSize %d, #%d: expected done on partial page, got %v",
|
|
usePageToken, pageSize, i, err), false
|
|
}
|
|
if usePageToken {
|
|
// Pretend that we are displaying a paginated listing on the web, and the next
|
|
// page may be served by a different process.
|
|
// Empty page token implies done, and vice versa.
|
|
if (it.NextPageToken() == "") != (err == done) {
|
|
return fmt.Sprintf("pageSize %d: next page token = %q and err = %v; expected empty page token iff done",
|
|
pageSize, it.NextPageToken(), err), false
|
|
}
|
|
if err == nil {
|
|
token := it.NextPageToken()
|
|
it = newIter()
|
|
it.SetPageSize(pageSize)
|
|
it.SetPageToken(token)
|
|
}
|
|
}
|
|
}
|
|
// We now should see (<zero-length or nil slice>, done), no matter how many
|
|
// additional calls we make.
|
|
for i := 0; i < 3; i++ {
|
|
gotPage, err := nextPage(it)
|
|
if err != done {
|
|
return fmt.Sprintf("usePageToken %v, pageSize %d, at end: got error %v, want done",
|
|
usePageToken, pageSize, err), false
|
|
}
|
|
pVal := reflect.ValueOf(gotPage)
|
|
if pVal.Kind() != reflect.Slice || pVal.Len() != 0 {
|
|
return fmt.Sprintf("usePageToken %v, pageSize %d, at end: got %+v with done, want zero-length slice",
|
|
usePageToken, pageSize, gotPage), false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return "", true
|
|
}
|