github.com/ethersphere/bee/v2@v2.2.0/pkg/p2p/libp2p/internal/breaker/breaker_test.go (about)

     1  // Copyright 2020 The Swarm Authors. All rights reserved.
     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 breaker_test
     6  
     7  import (
     8  	"errors"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/ethersphere/bee/v2/pkg/p2p/libp2p/internal/breaker"
    13  )
    14  
    15  func TestExecute(t *testing.T) {
    16  	t.Parallel()
    17  
    18  	testErr := errors.New("test error")
    19  	shouldNotBeCalledErr := errors.New("should not be called")
    20  	failInterval := 10 * time.Minute
    21  	startBackoff := 1 * time.Minute
    22  	initTime := time.Now()
    23  
    24  	testCases := map[string]struct {
    25  		limit        int
    26  		ferrors      []error
    27  		iterations   int
    28  		times        []time.Time
    29  		expectedErrs []error
    30  	}{
    31  		"f() returns nil": {
    32  			limit:        5,
    33  			iterations:   1,
    34  			ferrors:      []error{nil},
    35  			times:        nil,
    36  			expectedErrs: []error{nil},
    37  		},
    38  		"f() returns error": {
    39  			limit:        5,
    40  			ferrors:      []error{testErr},
    41  			iterations:   1,
    42  			times:        nil,
    43  			expectedErrs: []error{testErr},
    44  		},
    45  		"Break error": {
    46  			limit:        1,
    47  			ferrors:      []error{testErr, shouldNotBeCalledErr},
    48  			iterations:   3,
    49  			times:        nil,
    50  			expectedErrs: []error{testErr, breaker.ErrClosed, breaker.ErrClosed},
    51  		},
    52  		"Break error - mix iterations": {
    53  			limit:        3,
    54  			ferrors:      []error{testErr, nil, testErr, testErr, testErr, shouldNotBeCalledErr},
    55  			iterations:   6,
    56  			times:        nil,
    57  			expectedErrs: []error{testErr, nil, testErr, testErr, testErr, breaker.ErrClosed},
    58  		},
    59  		"Expiration - return f() error": {
    60  			limit:        3,
    61  			ferrors:      []error{testErr, testErr, testErr, testErr, testErr},
    62  			iterations:   5,
    63  			times:        []time.Time{initTime, initTime, initTime.Add(2 * failInterval), initTime, initTime, initTime, initTime},
    64  			expectedErrs: []error{testErr, testErr, testErr, testErr, testErr},
    65  		},
    66  		"Backoff - close, reopen, close, don't open": {
    67  			limit:        1,
    68  			ferrors:      []error{testErr, shouldNotBeCalledErr, testErr, shouldNotBeCalledErr, testErr, shouldNotBeCalledErr, shouldNotBeCalledErr},
    69  			iterations:   7,
    70  			times:        []time.Time{initTime, initTime, initTime, initTime.Add(startBackoff + time.Second), initTime, initTime, initTime, initTime.Add(2*startBackoff + time.Second), initTime, initTime, initTime, initTime.Add(startBackoff + time.Second)},
    71  			expectedErrs: []error{testErr, breaker.ErrClosed, testErr, breaker.ErrClosed, testErr, breaker.ErrClosed, breaker.ErrClosed},
    72  		},
    73  	}
    74  
    75  	for name, tc := range testCases {
    76  		tc := tc
    77  		t.Run(name, func(t *testing.T) {
    78  			t.Parallel()
    79  
    80  			ctMock := &currentTimeMock{
    81  				times: tc.times,
    82  			}
    83  
    84  			b := breaker.NewBreakerWithCurrentTimeFn(breaker.Options{
    85  				Limit:        tc.limit,
    86  				StartBackoff: startBackoff,
    87  				FailInterval: failInterval,
    88  			}, ctMock.Time)
    89  
    90  			for i := 0; i < tc.iterations; i++ {
    91  				if err := b.Execute(func() error {
    92  					if errors.Is(tc.ferrors[i], shouldNotBeCalledErr) {
    93  						t.Fatal(tc.ferrors[i])
    94  					}
    95  
    96  					return tc.ferrors[i]
    97  				}); !errors.Is(err, tc.expectedErrs[i]) {
    98  					t.Fatalf("expected err: %s, got: %s, iteration %v", tc.expectedErrs[i], err, i)
    99  				}
   100  			}
   101  		})
   102  	}
   103  }
   104  
   105  func TestClosedUntil(t *testing.T) {
   106  	t.Parallel()
   107  
   108  	timestamp := time.Now()
   109  	startBackoff := 1 * time.Minute
   110  	testError := errors.New("test error")
   111  	ctMock := &currentTimeMock{
   112  		times: []time.Time{timestamp, timestamp, timestamp},
   113  	}
   114  
   115  	b := breaker.NewBreakerWithCurrentTimeFn(breaker.Options{
   116  		Limit:        1,
   117  		StartBackoff: startBackoff,
   118  	}, ctMock.Time)
   119  
   120  	notClosed := b.ClosedUntil()
   121  	if notClosed != timestamp {
   122  		t.Fatalf("expected: %s, got: %s", timestamp, notClosed)
   123  	}
   124  
   125  	if err := b.Execute(func() error {
   126  		return testError
   127  	}); !errors.Is(err, testError) {
   128  		t.Fatalf("expected nil got %s", err)
   129  	}
   130  
   131  	closed := b.ClosedUntil()
   132  	if closed != timestamp.Add(startBackoff) {
   133  		t.Fatalf("expected: %s, got: %s", timestamp.Add(startBackoff), notClosed)
   134  	}
   135  }
   136  
   137  type currentTimeMock struct {
   138  	times []time.Time
   139  	curr  int
   140  }
   141  
   142  func (c *currentTimeMock) Time() time.Time {
   143  	if c.times == nil {
   144  		return time.Now()
   145  	}
   146  
   147  	t := c.times[c.curr]
   148  	c.curr++
   149  	return t
   150  }