mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-03 16:38:35 +08:00
285 lines
6.6 KiB
Go
285 lines
6.6 KiB
Go
// Copyright 2022 Gravitational, Inc
|
|
//
|
|
// 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 interval
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestMultiIntervalReset verifies the basic behavior of the multi interval reset functionality.
|
|
// Since time based tests tend to be flaky, this test passes if it has a >50% success
|
|
// rate (i.e. >50% of resets seemed to have actually extended the timer successfully).
|
|
func TestMultiIntervalReset(t *testing.T) {
|
|
const iterations = 1_000
|
|
const duration = time.Millisecond * 666
|
|
t.Parallel()
|
|
|
|
var success, failure atomic.Uint64
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
for i := 0; i < iterations; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
resetTimer := time.NewTimer(duration / 3)
|
|
defer resetTimer.Stop()
|
|
|
|
interval := NewMulti[string](SubInterval[string]{
|
|
Key: "key",
|
|
Duration: duration,
|
|
})
|
|
defer interval.Stop()
|
|
|
|
start := time.Now()
|
|
|
|
for i := 0; i < 6; i++ {
|
|
select {
|
|
case <-interval.Next():
|
|
failure.Add(1)
|
|
return
|
|
case <-resetTimer.C:
|
|
interval.Reset("key")
|
|
resetTimer.Reset(duration / 3)
|
|
}
|
|
}
|
|
|
|
<-interval.Next()
|
|
elapsed := time.Since(start)
|
|
// we expect this test to produce elapsed times of
|
|
// 3*duration if it is working properly. we accept a
|
|
// margin or error of +/- 1 duration in order to
|
|
// minimize flakiness.
|
|
if elapsed > duration*2 && elapsed < duration*4 {
|
|
success.Add(1)
|
|
} else {
|
|
failure.Add(1)
|
|
}
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
require.Greater(t, success.Load(), failure.Load())
|
|
}
|
|
|
|
// TestMultiIntervalBasics verifies basic expected multiinterval behavior. Due to the general
|
|
// flakiness of time-based tests, this test is designed to only care about which sub-intervals
|
|
// tick *more* relative to one another, and not about the specific relative frequency of observed
|
|
// ticks.
|
|
func TestMultiIntervalBasics(t *testing.T) {
|
|
t.Parallel()
|
|
interval := NewMulti[string](
|
|
SubInterval[string]{
|
|
Key: "fast",
|
|
Duration: time.Millisecond * 8,
|
|
},
|
|
SubInterval[string]{
|
|
Key: "slow",
|
|
Duration: time.Millisecond * 16,
|
|
},
|
|
SubInterval[string]{
|
|
Key: "once",
|
|
Duration: time.Hour,
|
|
FirstDuration: time.Millisecond,
|
|
},
|
|
)
|
|
defer interval.Stop()
|
|
|
|
var fast, slow, once int
|
|
var prevT time.Time
|
|
for i := 0; i < 60; i++ {
|
|
tick := <-interval.Next()
|
|
require.True(t, !tick.Time.IsZero())
|
|
require.True(t, tick.Time.After(prevT) || tick.Time == prevT)
|
|
prevT = tick.Time
|
|
switch tick.Key {
|
|
case "fast":
|
|
fast++
|
|
case "slow":
|
|
slow++
|
|
case "once":
|
|
once++
|
|
}
|
|
}
|
|
require.Equal(t, 1, once)
|
|
require.Greater(t, slow, once)
|
|
require.Greater(t, fast, slow)
|
|
}
|
|
|
|
// TestMultiIntervalPush verifies the expected behavior of MultiInterval.Push, both in terms of
|
|
// its ability to add new sub-intervals, and to overwrite existing sub-intervals.
|
|
func TestMultiIntervalPush(t *testing.T) {
|
|
t.Parallel()
|
|
interval := NewMulti[string](
|
|
SubInterval[string]{
|
|
Key: "foo",
|
|
Duration: time.Millisecond * 6,
|
|
},
|
|
)
|
|
defer interval.Stop()
|
|
|
|
// verify that single-interval is working
|
|
for i := 0; i < 3; i++ {
|
|
tick := <-interval.Next()
|
|
require.Equal(t, "foo", tick.Key)
|
|
}
|
|
|
|
// push a new slower sub-interval
|
|
interval.Push(SubInterval[string]{
|
|
Key: "bar",
|
|
Duration: time.Millisecond * 12,
|
|
})
|
|
|
|
// aggregate rates of both sub-intervals
|
|
var foo, bar int
|
|
for i := 0; i < 60; i++ {
|
|
tick := <-interval.Next()
|
|
switch tick.Key {
|
|
case "foo":
|
|
foo++
|
|
case "bar":
|
|
bar++
|
|
}
|
|
}
|
|
// verify that both sub-intervals are firing, and that
|
|
// foo is firing more frequently.
|
|
require.NotZero(t, foo)
|
|
require.NotZero(t, bar)
|
|
require.Greater(t, foo, bar)
|
|
|
|
// overwrite the old sub-intervals, inverting their respective
|
|
// tick rates.
|
|
interval.Push(SubInterval[string]{
|
|
Key: "foo",
|
|
Duration: time.Millisecond * 12,
|
|
})
|
|
interval.Push(SubInterval[string]{
|
|
Key: "bar",
|
|
Duration: time.Millisecond * 6,
|
|
})
|
|
|
|
// aggregate new rates for both sub-intervals
|
|
foo = 0
|
|
bar = 0
|
|
for i := 0; i < 60; i++ {
|
|
tick := <-interval.Next()
|
|
switch tick.Key {
|
|
case "foo":
|
|
foo++
|
|
case "bar":
|
|
bar++
|
|
}
|
|
}
|
|
// verify that both sub-intervals are firing, and that
|
|
// foo their relative rates have flipped, with bar now
|
|
// firing more frequently.
|
|
require.NotZero(t, foo)
|
|
require.NotZero(t, bar)
|
|
require.Greater(t, bar, foo)
|
|
}
|
|
|
|
// TestMultiIntervalFireNow verifies the expected behavior of MultiInterval.FireNow.
|
|
func TestMultiIntervalFireNow(t *testing.T) {
|
|
t.Parallel()
|
|
// set up one sub-interval that fires frequently, and another that will never
|
|
// fire during this test unless we trigger with FireNow.
|
|
interval := NewMulti[string](
|
|
SubInterval[string]{
|
|
Key: "slow",
|
|
Duration: time.Hour,
|
|
},
|
|
SubInterval[string]{
|
|
Key: "fast",
|
|
Duration: time.Millisecond * 10,
|
|
},
|
|
)
|
|
defer interval.Stop()
|
|
|
|
// verify that only the 'fast' interval is firing
|
|
for i := 0; i < 10; i++ {
|
|
tick := <-interval.Next()
|
|
require.Equal(t, "fast", tick.Key)
|
|
}
|
|
|
|
// trigger the slow interval
|
|
interval.FireNow("slow")
|
|
|
|
// make sure that we observe slow interval firing
|
|
var seenSlow bool
|
|
for i := 0; i < 60; i++ {
|
|
tick := <-interval.Next()
|
|
if tick.Key == "slow" {
|
|
seenSlow = true
|
|
break
|
|
}
|
|
}
|
|
|
|
require.True(t, seenSlow)
|
|
}
|
|
|
|
// TestPendingTicks tests the expected behavior of the pendingTicks helper.
|
|
func TestPendingTicks(t *testing.T) {
|
|
// "backlog" of fake ticks with lots of duplicates
|
|
tks := []string{
|
|
"foo",
|
|
"bar",
|
|
"foo",
|
|
"foo",
|
|
"bin",
|
|
"bar",
|
|
"bar",
|
|
"baz",
|
|
"foo",
|
|
}
|
|
|
|
var pending pendingTicks[string]
|
|
|
|
// insert out fake ticks
|
|
for _, tk := range tks {
|
|
pending.add(time.Now(), tk)
|
|
}
|
|
|
|
// we expect to have not stored any duplicate keys (important for
|
|
// preventing runaway memory growth due to neglected interval).
|
|
require.Len(t, pending.keys, 4)
|
|
|
|
// expected order of ticks to be emitted
|
|
expect := []string{
|
|
"foo",
|
|
"bar",
|
|
"bin",
|
|
"baz",
|
|
}
|
|
|
|
// verify that we see the ticks in the order we expect
|
|
for _, exp := range expect {
|
|
tick, ok := pending.next()
|
|
require.True(t, ok)
|
|
require.Equal(t, exp, tick.Key)
|
|
pending.remove(exp)
|
|
}
|
|
|
|
// verify that no more ticks are available
|
|
_, ok := pending.next()
|
|
require.False(t, ok)
|
|
}
|