2016-04-27 01:07:38 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2016 The Kubernetes Authors .
2016-04-27 01:07:38 +00:00
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 goroutinemap
import (
2016-05-10 08:11:08 +00:00
"fmt"
2016-04-27 01:07:38 +00:00
"testing"
"time"
2017-01-11 14:09:48 +00:00
"k8s.io/apimachinery/pkg/util/wait"
2016-04-27 01:07:38 +00:00
)
2016-06-16 06:48:04 +00:00
const (
// testTimeout is a timeout of goroutines to finish. This _should_ be just a
// "context switch" and it should take several ms, however, Clayton says "We
// have had flakes due to tests that assumed that 15s is long enough to sleep")
testTimeout time . Duration = 1 * time . Minute
// initialOperationWaitTimeShort is the initial amount of time the test will
// wait for an operation to complete (each successive failure results in
// exponential backoff).
initialOperationWaitTimeShort time . Duration = 20 * time . Millisecond
// initialOperationWaitTimeLong is the initial amount of time the test will
// wait for an operation to complete (each successive failure results in
// exponential backoff).
initialOperationWaitTimeLong time . Duration = 500 * time . Millisecond
)
2016-05-20 08:14:47 +00:00
2016-04-27 01:07:38 +00:00
func Test_NewGoRoutineMap_Positive_SingleOp ( t * testing . T ) {
// Arrange
2016-06-16 06:48:04 +00:00
grm := NewGoRoutineMap ( false /* exponentialBackOffOnError */ )
operationName := "operation-name"
operation := func ( ) error { return nil }
// Act
err := grm . Run ( operationName , operation )
// Assert
if err != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err )
}
}
2016-07-14 05:38:54 +00:00
func Test_NewGoRoutineMap_Positive_TwoOps ( t * testing . T ) {
// Arrange
grm := NewGoRoutineMap ( false /* exponentialBackOffOnError */ )
operation1Name := "operation1-name"
operation2Name := "operation2-name"
operation := func ( ) error { return nil }
// Act
err1 := grm . Run ( operation1Name , operation )
err2 := grm . Run ( operation2Name , operation )
// Assert
if err1 != nil {
t . Fatalf ( "NewGoRoutine %q failed. Expected: <no error> Actual: <%v>" , operation1Name , err1 )
}
if err2 != nil {
t . Fatalf ( "NewGoRoutine %q failed. Expected: <no error> Actual: <%v>" , operation2Name , err2 )
}
}
2016-06-16 06:48:04 +00:00
func Test_NewGoRoutineMap_Positive_SingleOpWithExpBackoff ( t * testing . T ) {
// Arrange
grm := NewGoRoutineMap ( true /* exponentialBackOffOnError */ )
2016-04-27 01:07:38 +00:00
operationName := "operation-name"
operation := func ( ) error { return nil }
// Act
2016-05-13 23:05:55 +00:00
err := grm . Run ( operationName , operation )
2016-04-27 01:07:38 +00:00
// Assert
if err != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err )
}
}
func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstCompletes ( t * testing . T ) {
// Arrange
2016-06-16 06:48:04 +00:00
grm := NewGoRoutineMap ( false /* exponentialBackOffOnError */ )
operationName := "operation-name"
operation1DoneCh := make ( chan interface { } , 0 /* bufferSize */ )
operation1 := generateCallbackFunc ( operation1DoneCh )
err1 := grm . Run ( operationName , operation1 )
if err1 != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err1 )
}
operation2 := generateNoopFunc ( )
<- operation1DoneCh // Force operation1 to complete
// Act
err2 := retryWithExponentialBackOff (
time . Duration ( initialOperationWaitTimeShort ) ,
func ( ) ( bool , error ) {
err := grm . Run ( operationName , operation2 )
if err != nil {
t . Logf ( "Warning: NewGoRoutine failed with %v. Will retry." , err )
return false , nil
}
return true , nil
} ,
)
// Assert
if err2 != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err2 )
}
}
func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstCompletesWithExpBackoff ( t * testing . T ) {
// Arrange
grm := NewGoRoutineMap ( true /* exponentialBackOffOnError */ )
2016-04-27 01:07:38 +00:00
operationName := "operation-name"
operation1DoneCh := make ( chan interface { } , 0 /* bufferSize */ )
operation1 := generateCallbackFunc ( operation1DoneCh )
2016-05-13 23:05:55 +00:00
err1 := grm . Run ( operationName , operation1 )
2016-04-27 01:07:38 +00:00
if err1 != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err1 )
}
operation2 := generateNoopFunc ( )
<- operation1DoneCh // Force operation1 to complete
// Act
err2 := retryWithExponentialBackOff (
2016-06-16 06:48:04 +00:00
time . Duration ( initialOperationWaitTimeShort ) ,
2016-04-27 01:07:38 +00:00
func ( ) ( bool , error ) {
2016-05-13 23:05:55 +00:00
err := grm . Run ( operationName , operation2 )
2016-04-27 01:07:38 +00:00
if err != nil {
2016-06-16 06:48:04 +00:00
t . Logf ( "Warning: NewGoRoutine failed with %v. Will retry." , err )
2016-04-27 01:07:38 +00:00
return false , nil
}
return true , nil
} ,
)
// Assert
if err2 != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err2 )
}
}
func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstPanics ( t * testing . T ) {
// Arrange
2016-06-16 06:48:04 +00:00
grm := NewGoRoutineMap ( false /* exponentialBackOffOnError */ )
2016-04-27 01:07:38 +00:00
operationName := "operation-name"
operation1 := generatePanicFunc ( )
2016-05-13 23:05:55 +00:00
err1 := grm . Run ( operationName , operation1 )
2016-04-27 01:07:38 +00:00
if err1 != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err1 )
}
operation2 := generateNoopFunc ( )
// Act
err2 := retryWithExponentialBackOff (
2016-06-16 06:48:04 +00:00
time . Duration ( initialOperationWaitTimeShort ) ,
2016-04-27 01:07:38 +00:00
func ( ) ( bool , error ) {
2016-05-13 23:05:55 +00:00
err := grm . Run ( operationName , operation2 )
2016-04-27 01:07:38 +00:00
if err != nil {
2016-06-16 06:48:04 +00:00
t . Logf ( "Warning: NewGoRoutine failed with %v. Will retry." , err )
return false , nil
}
return true , nil
} ,
)
// Assert
if err2 != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err2 )
}
}
func Test_NewGoRoutineMap_Positive_SecondOpAfterFirstPanicsWithExpBackoff ( t * testing . T ) {
// Arrange
grm := NewGoRoutineMap ( true /* exponentialBackOffOnError */ )
operationName := "operation-name"
operation1 := generatePanicFunc ( )
err1 := grm . Run ( operationName , operation1 )
if err1 != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err1 )
}
operation2 := generateNoopFunc ( )
// Act
err2 := retryWithExponentialBackOff (
time . Duration ( initialOperationWaitTimeLong ) , // Longer duration to accommodate for backoff
func ( ) ( bool , error ) {
err := grm . Run ( operationName , operation2 )
if err != nil {
t . Logf ( "Warning: NewGoRoutine failed with %v. Will retry." , err )
2016-04-27 01:07:38 +00:00
return false , nil
}
return true , nil
} ,
)
// Assert
if err2 != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err2 )
}
}
func Test_NewGoRoutineMap_Negative_SecondOpBeforeFirstCompletes ( t * testing . T ) {
// Arrange
2016-06-16 06:48:04 +00:00
grm := NewGoRoutineMap ( false /* exponentialBackOffOnError */ )
operationName := "operation-name"
operation1DoneCh := make ( chan interface { } , 0 /* bufferSize */ )
operation1 := generateWaitFunc ( operation1DoneCh )
err1 := grm . Run ( operationName , operation1 )
if err1 != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err1 )
}
operation2 := generateNoopFunc ( )
// Act
err2 := grm . Run ( operationName , operation2 )
// Assert
if err2 == nil {
t . Fatalf ( "NewGoRoutine did not fail. Expected: <Failed to create operation with name \"%s\". An operation with that name already exists.> Actual: <no error>" , operationName )
}
if ! IsAlreadyExists ( err2 ) {
t . Fatalf ( "NewGoRoutine did not return alreadyExistsError, got: %v" , err2 )
}
}
func Test_NewGoRoutineMap_Negative_SecondOpBeforeFirstCompletesWithExpBackoff ( t * testing . T ) {
// Arrange
grm := NewGoRoutineMap ( true /* exponentialBackOffOnError */ )
2016-04-27 01:07:38 +00:00
operationName := "operation-name"
operation1DoneCh := make ( chan interface { } , 0 /* bufferSize */ )
operation1 := generateWaitFunc ( operation1DoneCh )
2016-05-13 23:05:55 +00:00
err1 := grm . Run ( operationName , operation1 )
2016-04-27 01:07:38 +00:00
if err1 != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err1 )
}
operation2 := generateNoopFunc ( )
// Act
2016-05-13 23:05:55 +00:00
err2 := grm . Run ( operationName , operation2 )
2016-04-27 01:07:38 +00:00
// Assert
if err2 == nil {
t . Fatalf ( "NewGoRoutine did not fail. Expected: <Failed to create operation with name \"%s\". An operation with that name already exists.> Actual: <no error>" , operationName )
}
2016-05-16 08:43:42 +00:00
if ! IsAlreadyExists ( err2 ) {
t . Fatalf ( "NewGoRoutine did not return alreadyExistsError, got: %v" , err2 )
}
2016-04-27 01:07:38 +00:00
}
func Test_NewGoRoutineMap_Positive_ThirdOpAfterFirstCompletes ( t * testing . T ) {
// Arrange
2016-06-16 06:48:04 +00:00
grm := NewGoRoutineMap ( false /* exponentialBackOffOnError */ )
2016-04-27 01:07:38 +00:00
operationName := "operation-name"
operation1DoneCh := make ( chan interface { } , 0 /* bufferSize */ )
operation1 := generateWaitFunc ( operation1DoneCh )
2016-05-13 23:05:55 +00:00
err1 := grm . Run ( operationName , operation1 )
2016-04-27 01:07:38 +00:00
if err1 != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err1 )
}
operation2 := generateNoopFunc ( )
operation3 := generateNoopFunc ( )
// Act
2016-05-13 23:05:55 +00:00
err2 := grm . Run ( operationName , operation2 )
2016-04-27 01:07:38 +00:00
// Assert
if err2 == nil {
t . Fatalf ( "NewGoRoutine did not fail. Expected: <Failed to create operation with name \"%s\". An operation with that name already exists.> Actual: <no error>" , operationName )
}
2016-05-16 08:43:42 +00:00
if ! IsAlreadyExists ( err2 ) {
t . Fatalf ( "NewGoRoutine did not return alreadyExistsError, got: %v" , err2 )
}
2016-04-27 01:07:38 +00:00
// Act
operation1DoneCh <- true // Force operation1 to complete
err3 := retryWithExponentialBackOff (
2016-06-16 06:48:04 +00:00
time . Duration ( initialOperationWaitTimeShort ) ,
2016-04-27 01:07:38 +00:00
func ( ) ( bool , error ) {
2016-05-13 23:05:55 +00:00
err := grm . Run ( operationName , operation3 )
2016-04-27 01:07:38 +00:00
if err != nil {
2016-06-16 06:48:04 +00:00
t . Logf ( "Warning: NewGoRoutine failed with %v. Will retry." , err )
2016-04-27 01:07:38 +00:00
return false , nil
}
return true , nil
} ,
)
// Assert
if err3 != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err3 )
}
}
2016-06-16 06:48:04 +00:00
func Test_NewGoRoutineMap_Positive_ThirdOpAfterFirstCompletesWithExpBackoff ( t * testing . T ) {
// Arrange
grm := NewGoRoutineMap ( true /* exponentialBackOffOnError */ )
operationName := "operation-name"
operation1DoneCh := make ( chan interface { } , 0 /* bufferSize */ )
operation1 := generateWaitFunc ( operation1DoneCh )
err1 := grm . Run ( operationName , operation1 )
if err1 != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err1 )
2016-04-27 01:07:38 +00:00
}
2016-06-16 06:48:04 +00:00
operation2 := generateNoopFunc ( )
operation3 := generateNoopFunc ( )
2016-04-27 01:07:38 +00:00
2016-06-16 06:48:04 +00:00
// Act
err2 := grm . Run ( operationName , operation2 )
// Assert
if err2 == nil {
t . Fatalf ( "NewGoRoutine did not fail. Expected: <Failed to create operation with name \"%s\". An operation with that name already exists.> Actual: <no error>" , operationName )
}
if ! IsAlreadyExists ( err2 ) {
t . Fatalf ( "NewGoRoutine did not return alreadyExistsError, got: %v" , err2 )
2016-04-27 01:07:38 +00:00
}
2016-06-16 06:48:04 +00:00
// Act
operation1DoneCh <- true // Force operation1 to complete
err3 := retryWithExponentialBackOff (
time . Duration ( initialOperationWaitTimeShort ) ,
func ( ) ( bool , error ) {
err := grm . Run ( operationName , operation3 )
if err != nil {
t . Logf ( "Warning: NewGoRoutine failed with %v. Will retry." , err )
return false , nil
}
return true , nil
} ,
)
// Assert
if err3 != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err3 )
2016-04-27 01:07:38 +00:00
}
}
2016-06-16 06:48:04 +00:00
func Test_NewGoRoutineMap_Positive_WaitEmpty ( t * testing . T ) {
// Test than Wait() on empty GoRoutineMap always succeeds without blocking
// Arrange
grm := NewGoRoutineMap ( false /* exponentialBackOffOnError */ )
2016-04-27 01:07:38 +00:00
2016-06-16 06:48:04 +00:00
// Act
waitDoneCh := make ( chan interface { } , 1 )
go func ( ) {
grm . Wait ( )
waitDoneCh <- true
} ( )
// Assert
err := waitChannelWithTimeout ( waitDoneCh , testTimeout )
if err != nil {
t . Errorf ( "Error waiting for GoRoutineMap.Wait: %v" , err )
2016-04-27 01:07:38 +00:00
}
}
2016-05-10 08:11:08 +00:00
2016-06-16 06:48:04 +00:00
func Test_NewGoRoutineMap_Positive_WaitEmptyWithExpBackoff ( t * testing . T ) {
2016-05-10 08:11:08 +00:00
// Test than Wait() on empty GoRoutineMap always succeeds without blocking
// Arrange
2016-06-16 06:48:04 +00:00
grm := NewGoRoutineMap ( true /* exponentialBackOffOnError */ )
2016-05-10 08:11:08 +00:00
// Act
waitDoneCh := make ( chan interface { } , 1 )
go func ( ) {
grm . Wait ( )
waitDoneCh <- true
} ( )
// Assert
2016-05-20 08:14:47 +00:00
err := waitChannelWithTimeout ( waitDoneCh , testTimeout )
2016-05-10 08:11:08 +00:00
if err != nil {
t . Errorf ( "Error waiting for GoRoutineMap.Wait: %v" , err )
}
}
func Test_NewGoRoutineMap_Positive_Wait ( t * testing . T ) {
// Test that Wait() really blocks until the last operation succeeds
// Arrange
2016-06-16 06:48:04 +00:00
grm := NewGoRoutineMap ( false /* exponentialBackOffOnError */ )
2016-05-10 08:11:08 +00:00
operationName := "operation-name"
operation1DoneCh := make ( chan interface { } , 0 /* bufferSize */ )
operation1 := generateWaitFunc ( operation1DoneCh )
2016-05-13 23:05:55 +00:00
err := grm . Run ( operationName , operation1 )
2016-05-10 08:11:08 +00:00
if err != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err )
}
// Act
waitDoneCh := make ( chan interface { } , 1 )
go func ( ) {
grm . Wait ( )
waitDoneCh <- true
} ( )
// Finish the operation
operation1DoneCh <- true
2016-05-20 08:14:47 +00:00
// Assert
err = waitChannelWithTimeout ( waitDoneCh , testTimeout )
2016-05-10 08:11:08 +00:00
if err != nil {
t . Fatalf ( "Error waiting for GoRoutineMap.Wait: %v" , err )
}
}
2016-06-16 06:48:04 +00:00
func Test_NewGoRoutineMap_Positive_WaitWithExpBackoff ( t * testing . T ) {
// Test that Wait() really blocks until the last operation succeeds
// Arrange
grm := NewGoRoutineMap ( true /* exponentialBackOffOnError */ )
operationName := "operation-name"
operation1DoneCh := make ( chan interface { } , 0 /* bufferSize */ )
operation1 := generateWaitFunc ( operation1DoneCh )
err := grm . Run ( operationName , operation1 )
if err != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err )
}
// Act
waitDoneCh := make ( chan interface { } , 1 )
go func ( ) {
grm . Wait ( )
waitDoneCh <- true
} ( )
// Finish the operation
operation1DoneCh <- true
// Assert
err = waitChannelWithTimeout ( waitDoneCh , testTimeout )
if err != nil {
t . Fatalf ( "Error waiting for GoRoutineMap.Wait: %v" , err )
}
}
2016-12-16 04:03:58 +00:00
func Test_NewGoRoutineMap_WaitForCompletionWithExpBackoff ( t * testing . T ) {
grm := NewGoRoutineMap ( true /* exponentialBackOffOnError */ )
operationName := "operation-err"
operation1DoneCh := make ( chan interface { } , 0 /* bufferSize */ )
operation1 := generateErrorFunc ( operation1DoneCh )
err := grm . Run ( operationName , operation1 )
if err != nil {
t . Fatalf ( "NewGoRoutine failed. Expected: <no error> Actual: <%v>" , err )
}
// Act
waitDoneCh := make ( chan interface { } , 1 )
go func ( ) {
grm . WaitForCompletion ( )
waitDoneCh <- true
} ( )
// Finish the operation
operation1DoneCh <- true
// Assert that WaitForCompletion returns even if scheduled op had error
err = waitChannelWithTimeout ( waitDoneCh , testTimeout )
if err != nil {
t . Fatalf ( "Error waiting for GoRoutineMap.Wait: %v" , err )
}
}
2016-06-16 06:48:04 +00:00
func generateCallbackFunc ( done chan <- interface { } ) func ( ) error {
return func ( ) error {
done <- true
return nil
}
}
2016-12-16 04:03:58 +00:00
func generateErrorFunc ( done <- chan interface { } ) func ( ) error {
return func ( ) error {
<- done
return fmt . Errorf ( "Generic error" )
}
}
2016-06-16 06:48:04 +00:00
func generateWaitFunc ( done <- chan interface { } ) func ( ) error {
return func ( ) error {
<- done
return nil
}
}
func generatePanicFunc ( ) func ( ) error {
return func ( ) error {
panic ( "testing panic" )
}
}
func generateNoopFunc ( ) func ( ) error {
return func ( ) error { return nil }
}
func retryWithExponentialBackOff ( initialDuration time . Duration , fn wait . ConditionFunc ) error {
backoff := wait . Backoff {
Duration : initialDuration ,
Factor : 3 ,
Jitter : 0 ,
Steps : 4 ,
}
return wait . ExponentialBackoff ( backoff , fn )
}
2016-05-10 08:11:08 +00:00
func waitChannelWithTimeout ( ch <- chan interface { } , timeout time . Duration ) error {
timer := time . NewTimer ( timeout )
2016-07-26 03:53:21 +00:00
defer timer . Stop ( )
2016-05-10 08:11:08 +00:00
select {
case <- ch :
// Success!
return nil
case <- timer . C :
return fmt . Errorf ( "timeout after %v" , timeout )
}
}