diff --git a/lib/auth/helpers_mfa.go b/lib/auth/helpers_mfa.go index eca034791db4c..3eddd890b3d4e 100644 --- a/lib/auth/helpers_mfa.go +++ b/lib/auth/helpers_mfa.go @@ -33,6 +33,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth/mocku2f" wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes" + "github.com/gravitational/teleport/lib/utils/clocki" ) // TestDevice is a test MFA device. @@ -190,9 +191,8 @@ func (d *TestDevice) solveAuthnTOTP(c *proto.MFAAuthenticateChallenge) (*proto.M if d.clock == nil { return nil, trace.BadParameter("clock not set") } - if c, ok := d.clock.(clockwork.FakeClock); ok { - c.Advance(30 * time.Second) - } + clocki.Advance(d.clock, 30*time.Second) + code, err := totp.GenerateCode(d.TOTPSecret, d.clock.Now()) if err != nil { return nil, trace.Wrap(err) @@ -244,9 +244,7 @@ func (d *TestDevice) solveRegisterTOTP(c *proto.MFARegisterChallenge) (*proto.MF if d.clock == nil { return nil, trace.BadParameter("clock not set") } - if c, ok := d.clock.(clockwork.FakeClock); ok { - c.Advance(30 * time.Second) - } + clocki.Advance(d.clock, 30*time.Second) if c.GetTOTP().Algorithm != otp.AlgorithmSHA1.String() { return nil, trace.BadParameter("unexpected TOTP challenge algorithm: %s", c.GetTOTP().Algorithm) diff --git a/lib/backend/dynamo/atomicwrite_test.go b/lib/backend/dynamo/atomicwrite_test.go index d17255105b558..99ce7b8f652ea 100644 --- a/lib/backend/dynamo/atomicwrite_test.go +++ b/lib/backend/dynamo/atomicwrite_test.go @@ -28,9 +28,10 @@ import ( "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/test" + "github.com/gravitational/teleport/lib/utils/clocki" ) -func newAtomicWriteBackend(options ...test.ConstructionOption) (backend.Backend, clockwork.FakeClock, error) { +func newAtomicWriteBackend(options ...test.ConstructionOption) (backend.Backend, clocki.FakeClock, error) { dynamoCfg := map[string]interface{}{ "table_name": dynamoDBTestTable(), "poll_stream_period": 300 * time.Millisecond, diff --git a/lib/backend/dynamo/dynamodbbk_test.go b/lib/backend/dynamo/dynamodbbk_test.go index 4c6739d702c54..803310c86fd55 100644 --- a/lib/backend/dynamo/dynamodbbk_test.go +++ b/lib/backend/dynamo/dynamodbbk_test.go @@ -43,6 +43,7 @@ import ( "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/test" "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/teleport/lib/utils/clocki" ) func TestMain(m *testing.M) { @@ -74,7 +75,7 @@ func TestDynamoDB(t *testing.T) { "poll_stream_period": 300 * time.Millisecond, } - newBackend := func(options ...test.ConstructionOption) (backend.Backend, clockwork.FakeClock, error) { + newBackend := func(options ...test.ConstructionOption) (backend.Backend, clocki.FakeClock, error) { testCfg, err := test.ApplyOptions(options) if err != nil { return nil, nil, trace.Wrap(err) diff --git a/lib/backend/etcdbk/atomicwrite_test.go b/lib/backend/etcdbk/atomicwrite_test.go index 25369f05f4703..ac64bd3ae007b 100644 --- a/lib/backend/etcdbk/atomicwrite_test.go +++ b/lib/backend/etcdbk/atomicwrite_test.go @@ -23,15 +23,15 @@ import ( "testing" "github.com/gravitational/trace" - "github.com/jonboulle/clockwork" "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/test" + "github.com/gravitational/teleport/lib/utils/clocki" ) // newAtomicWriteTestBackend builds a backend suitable for the atomic write test suite. Once all backends implement AtomicWrite, // it will be integrated into the main backend interface and we can get rid of this separate helper. -func newAtomicWriteTestBackend(options ...test.ConstructionOption) (backend.Backend, clockwork.FakeClock, error) { +func newAtomicWriteTestBackend(options ...test.ConstructionOption) (backend.Backend, clocki.FakeClock, error) { opts, err := test.ApplyOptions(options) if err != nil { return nil, nil, trace.Wrap(err) diff --git a/lib/backend/etcdbk/etcd_test.go b/lib/backend/etcdbk/etcd_test.go index 7adc7cebb91f4..ac6780ed1ef9d 100644 --- a/lib/backend/etcdbk/etcd_test.go +++ b/lib/backend/etcdbk/etcd_test.go @@ -28,13 +28,13 @@ import ( "time" "github.com/gravitational/trace" - "github.com/jonboulle/clockwork" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/test" "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/teleport/lib/utils/clocki" ) const ( @@ -65,7 +65,7 @@ func TestEtcd(t *testing.T) { t.Skip("This test requires etcd, run `make run-etcd` and set TELEPORT_ETCD_TEST=yes in your environment") } - newBackend := func(options ...test.ConstructionOption) (backend.Backend, clockwork.FakeClock, error) { + newBackend := func(options ...test.ConstructionOption) (backend.Backend, clocki.FakeClock, error) { opts, err := test.ApplyOptions(options) if err != nil { return nil, nil, trace.Wrap(err) diff --git a/lib/backend/firestore/atomicwrite_test.go b/lib/backend/firestore/atomicwrite_test.go index 1f20b1fa244cf..b1290f85af623 100644 --- a/lib/backend/firestore/atomicwrite_test.go +++ b/lib/backend/firestore/atomicwrite_test.go @@ -27,9 +27,10 @@ import ( "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/test" + "github.com/gravitational/teleport/lib/utils/clocki" ) -func newAtomicWriteTestBackend(options ...test.ConstructionOption) (backend.Backend, clockwork.FakeClock, error) { +func newAtomicWriteTestBackend(options ...test.ConstructionOption) (backend.Backend, clocki.FakeClock, error) { cfg := firestoreParams() testCfg, err := test.ApplyOptions(options) diff --git a/lib/backend/firestore/firestorebk_test.go b/lib/backend/firestore/firestorebk_test.go index baf93c6184fac..26bd799f8a5de 100644 --- a/lib/backend/firestore/firestorebk_test.go +++ b/lib/backend/firestore/firestorebk_test.go @@ -53,6 +53,7 @@ import ( "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/test" "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/teleport/lib/utils/clocki" ) func TestMain(m *testing.M) { @@ -126,7 +127,7 @@ func TestFirestoreDB(t *testing.T) { ensureTestsEnabled(t) ensureEmulatorRunning(t, cfg) - newBackend := func(options ...test.ConstructionOption) (backend.Backend, clockwork.FakeClock, error) { + newBackend := func(options ...test.ConstructionOption) (backend.Backend, clocki.FakeClock, error) { testCfg, err := test.ApplyOptions(options) if err != nil { return nil, nil, trace.Wrap(err) diff --git a/lib/backend/lite/atomicwrite_test.go b/lib/backend/lite/atomicwrite_test.go index 543a0431586e7..f2d8709d79feb 100644 --- a/lib/backend/lite/atomicwrite_test.go +++ b/lib/backend/lite/atomicwrite_test.go @@ -28,12 +28,13 @@ import ( "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/test" + "github.com/gravitational/teleport/lib/utils/clocki" ) // newAtomicWriteTestBackendBuilder builds a backend suitable for the atomic write test suite. Once all backends implement AtomicWrite, // it will be integrated into the main backend interface and we can get rid of this separate helper. -func newAtomicWriteTestBackendBuilder(t *testing.T) func(options ...test.ConstructionOption) (backend.Backend, clockwork.FakeClock, error) { - return func(options ...test.ConstructionOption) (backend.Backend, clockwork.FakeClock, error) { +func newAtomicWriteTestBackendBuilder(t *testing.T) test.Constructor[clocki.FakeClock] { + return func(options ...test.ConstructionOption) (backend.Backend, clocki.FakeClock, error) { clock := clockwork.NewFakeClock() cfg, err := test.ApplyOptions(options) diff --git a/lib/backend/lite/lite_test.go b/lib/backend/lite/lite_test.go index b401b595ebbcd..e3383b3082802 100644 --- a/lib/backend/lite/lite_test.go +++ b/lib/backend/lite/lite_test.go @@ -31,6 +31,7 @@ import ( "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/test" "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/teleport/lib/utils/clocki" ) func TestMain(m *testing.M) { @@ -39,7 +40,7 @@ func TestMain(m *testing.M) { } func TestLite(t *testing.T) { - newBackend := func(options ...test.ConstructionOption) (backend.Backend, clockwork.FakeClock, error) { + newBackend := func(options ...test.ConstructionOption) (backend.Backend, clocki.FakeClock, error) { clock := clockwork.NewFakeClock() cfg, err := test.ApplyOptions(options) diff --git a/lib/backend/memory/atomicwrite_test.go b/lib/backend/memory/atomicwrite_test.go index 95314c7776e64..a3bf7747855d4 100644 --- a/lib/backend/memory/atomicwrite_test.go +++ b/lib/backend/memory/atomicwrite_test.go @@ -26,11 +26,12 @@ import ( "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/test" + "github.com/gravitational/teleport/lib/utils/clocki" ) // newAtomicWriteTestBackend builds a backend suitable for the atomic write test suite. Once all backends implement AtomicWrite, // it will be integrated into the main backend interface and we can get rid of this separate helper. -func newAtomicWriteTestBackend(options ...test.ConstructionOption) (backend.Backend, clockwork.FakeClock, error) { +func newAtomicWriteTestBackend(options ...test.ConstructionOption) (backend.Backend, clocki.FakeClock, error) { cfg, err := test.ApplyOptions(options) if err != nil { diff --git a/lib/backend/memory/memory_test.go b/lib/backend/memory/memory_test.go index 46e8b7532fdfa..442b97343a0ee 100644 --- a/lib/backend/memory/memory_test.go +++ b/lib/backend/memory/memory_test.go @@ -32,6 +32,7 @@ import ( "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/test" "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/teleport/lib/utils/clocki" ) func TestMain(m *testing.M) { @@ -40,7 +41,7 @@ func TestMain(m *testing.M) { } func TestMemory(t *testing.T) { - newBackend := func(options ...test.ConstructionOption) (backend.Backend, clockwork.FakeClock, error) { + newBackend := func(options ...test.ConstructionOption) (backend.Backend, clocki.FakeClock, error) { cfg, err := test.ApplyOptions(options) if err != nil { diff --git a/lib/backend/pgbk/atomicwrite_test.go b/lib/backend/pgbk/atomicwrite_test.go index d694b1de968fe..5f0379eb33a41 100644 --- a/lib/backend/pgbk/atomicwrite_test.go +++ b/lib/backend/pgbk/atomicwrite_test.go @@ -29,6 +29,7 @@ import ( "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/test" + "github.com/gravitational/teleport/lib/utils/clocki" ) // Testing requires a local psql backend to be set up, and for params to be passed via env. Ex: @@ -37,7 +38,7 @@ import ( // newAtomicWriteTestBackend builds a backend suitable for the atomic write test suite. Once all backends implement AtomicWrite, // it will be integrated into the main backend interface and we can get rid of this separate helper. -func newAtomicWriteTestBackend(options ...test.ConstructionOption) (backend.Backend, clockwork.FakeClock, error) { +func newAtomicWriteTestBackend(options ...test.ConstructionOption) (backend.Backend, clocki.FakeClock, error) { testCfg, err := test.ApplyOptions(options) if err != nil { return nil, nil, trace.Wrap(err) diff --git a/lib/backend/pgbk/pgbk_test.go b/lib/backend/pgbk/pgbk_test.go index 11471ebe638ee..fe4e66d439144 100644 --- a/lib/backend/pgbk/pgbk_test.go +++ b/lib/backend/pgbk/pgbk_test.go @@ -31,6 +31,7 @@ import ( "github.com/gravitational/teleport/lib/backend" "github.com/gravitational/teleport/lib/backend/test" "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/teleport/lib/utils/clocki" ) func TestMain(m *testing.M) { @@ -47,7 +48,7 @@ func TestPostgresBackend(t *testing.T) { t.Skip("Postgres backend tests are disabled. Enable them by setting the TELEPORT_PGBK_TEST_PARAMS_JSON variable.") } - newBackend := func(options ...test.ConstructionOption) (backend.Backend, clockwork.FakeClock, error) { + newBackend := func(options ...test.ConstructionOption) (backend.Backend, clocki.FakeClock, error) { testCfg, err := test.ApplyOptions(options) if err != nil { return nil, nil, trace.Wrap(err) diff --git a/lib/backend/test/atomicwrite.go b/lib/backend/test/atomicwrite.go index eb2a6a566af96..756d232623e42 100644 --- a/lib/backend/test/atomicwrite.go +++ b/lib/backend/test/atomicwrite.go @@ -30,9 +30,10 @@ import ( "golang.org/x/sync/errgroup" "github.com/gravitational/teleport/lib/backend" + "github.com/gravitational/teleport/lib/utils/clocki" ) -func RunAtomicWriteComplianceSuite(t *testing.T, newBackend Constructor) { +func RunAtomicWriteComplianceSuite[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { t.Run("Move", func(t *testing.T) { testAtomicWriteMove(t, newBackend) }) @@ -59,7 +60,7 @@ func RunAtomicWriteComplianceSuite(t *testing.T, newBackend Constructor) { } // testAtomicWriteMove verifies the correct behavior of "move" operations. -func testAtomicWriteMove(t *testing.T, newBackend Constructor) { +func testAtomicWriteMove[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { bk, _, err := newBackend() require.NoError(t, err) @@ -121,7 +122,7 @@ func testAtomicWriteMove(t *testing.T, newBackend Constructor) { // testAtomicWriteLock verifies correct behavior of various "lock" patterns (i.e. where some update on key X is conditional on // the state of key Y). -func testAtomicWriteLock(t *testing.T, newBackend Constructor) { +func testAtomicWriteLock[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { bk, _, err := newBackend() require.NoError(t, err) @@ -275,7 +276,7 @@ func testAtomicWriteLock(t *testing.T, newBackend Constructor) { } // testAtomicWriteMax verifies correct behavior of very large atomic writes. -func testAtomicWriteMax(t *testing.T, newBackend Constructor) { +func testAtomicWriteMax[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { bk, _, err := newBackend() require.NoError(t, err) @@ -361,7 +362,7 @@ func testAtomicWriteMax(t *testing.T, newBackend Constructor) { } // testAtomicWriteConcurrent is a sanity-check intended to verify the correctness of AtomicWrite under high concurrency. -func testAtomicWriteConcurrent(t *testing.T, newBackend Constructor) { +func testAtomicWriteConcurrent[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { const ( increments = 200 workers = 20 @@ -454,7 +455,7 @@ func testAtomicWriteConcurrent(t *testing.T, newBackend Constructor) { // testAtomicWriteNonConflicting verifies that non-conflicting but overlapping transactions all succeed // on the first attempt when running concurrently, meaning that backends that treat overlap as conflict (e.g. dynamo) // handle such conflicts internally. -func testAtomicWriteNonConflicting(t *testing.T, newBackend Constructor) { +func testAtomicWriteNonConflicting[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { const workers = 60 bk, _, err := newBackend() @@ -521,7 +522,7 @@ func testAtomicWriteNonConflicting(t *testing.T, newBackend Constructor) { // testAtomicWriteOther verifies some minor edge-cases that may not be covered by other tests. Specifically, // it verifies that Item.Key has no effect on writes or subsequent reads, and that ineffectual writes still // update the value of revision. -func testAtomicWriteOther(t *testing.T, newBackend Constructor) { +func testAtomicWriteOther[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { bk, _, err := newBackend() require.NoError(t, err) diff --git a/lib/backend/test/atomicwrite_shim.go b/lib/backend/test/atomicwrite_shim.go index d2df92aa07224..937de22eddeeb 100644 --- a/lib/backend/test/atomicwrite_shim.go +++ b/lib/backend/test/atomicwrite_shim.go @@ -26,17 +26,17 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" - "github.com/jonboulle/clockwork" "github.com/gravitational/teleport/lib/backend" + "github.com/gravitational/teleport/lib/utils/clocki" ) // RunBackendComplianceSuiteWithAtomicWriteShim runs the old backend compliance suite against the provided backend // with a shim that converts all calls to single-write methods (all write methods but DeleteRange) into calls to // AtomicWrite. This is done to ensure that the relationship between the conditional actions of AtomicWrite and the // single-write methods is well defined, and to improve overall coverage of AtomicWrite implementations via reuse. -func RunBackendComplianceSuiteWithAtomicWriteShim(t *testing.T, newBackend Constructor) { - RunBackendComplianceSuite(t, func(options ...ConstructionOption) (backend.Backend, clockwork.FakeClock, error) { +func RunBackendComplianceSuiteWithAtomicWriteShim[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { + RunBackendComplianceSuite(t, func(options ...ConstructionOption) (backend.Backend, clocki.FakeClock, error) { bk, clock, err := newBackend(options...) if err != nil { return nil, nil, trace.Wrap(err) diff --git a/lib/backend/test/suite.go b/lib/backend/test/suite.go index ec00969240a6b..e0b3a55d8f524 100644 --- a/lib/backend/test/suite.go +++ b/lib/backend/test/suite.go @@ -40,6 +40,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/backend" + "github.com/gravitational/teleport/lib/utils/clocki" ) var ( @@ -123,7 +124,7 @@ func (r BlockingFakeClock) BlockUntil(int) { // Constructor describes a function for constructing new instances of a // backend, with various options as required by a given test. Note that // it's the caller's responsibility to close it when the test is finished. -type Constructor func(options ...ConstructionOption) (backend.Backend, clockwork.FakeClock, error) +type Constructor[FakeClock clocki.FakeClock] func(options ...ConstructionOption) (backend.Backend, FakeClock, error) // RunBackendComplianceSuite runs the entire backend compliance suite, // creating a collection of named subtests under the context provided @@ -132,7 +133,7 @@ type Constructor func(options ...ConstructionOption) (backend.Backend, clockwork // As each test requires a new backend instance it will invoke the supplied // `newBackend` function, which callers will use inject instances of the // backend under test. -func RunBackendComplianceSuite(t *testing.T, newBackend Constructor) { +func RunBackendComplianceSuite[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { t.Run("CRUD", func(t *testing.T) { testCRUD(t, newBackend) }) @@ -206,7 +207,7 @@ func RequireItems(t *testing.T, expected, actual []backend.Item) { } // testCRUD tests create read update scenarios -func testCRUD(t *testing.T, newBackend Constructor) { +func testCRUD[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { uut, _, err := newBackend() require.NoError(t, err) defer func() { require.NoError(t, uut.Close()) }() @@ -295,7 +296,7 @@ func testCRUD(t *testing.T, newBackend Constructor) { require.Equal(t, lease.Revision, out.Revision) } -func testQueryRange(t *testing.T, newBackend Constructor) { +func testQueryRange[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { uut, _, err := newBackend() require.NoError(t, err) defer func() { require.NoError(t, uut.Close()) }() @@ -357,7 +358,7 @@ func testQueryRange(t *testing.T, newBackend Constructor) { } // testDeleteRange tests delete items by range -func testDeleteRange(t *testing.T, newBackend Constructor) { +func testDeleteRange[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { uut, _, err := newBackend() require.NoError(t, err) defer func() { require.NoError(t, uut.Close()) }() @@ -396,7 +397,7 @@ func testDeleteRange(t *testing.T, newBackend Constructor) { } // testCompareAndSwap tests compare and swap functionality -func testCompareAndSwap(t *testing.T, newBackend Constructor) { +func testCompareAndSwap[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { uut, clock, err := newBackend() require.NoError(t, err) defer func() { require.NoError(t, uut.Close()) }() @@ -473,7 +474,7 @@ func testCompareAndSwap(t *testing.T, newBackend Constructor) { } // testExpiration tests scenario with expiring values -func testExpiration(t *testing.T, newBackend Constructor) { +func testExpiration[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { uut, clock, err := newBackend() require.NoError(t, err) defer func() { require.NoError(t, uut.Close()) }() @@ -505,7 +506,7 @@ func addSeconds(t time.Time, seconds int64) time.Time { } // testKeepAlive tests keep alive API -func testKeepAlive(t *testing.T, newBackend Constructor) { +func testKeepAlive[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { const eventTimeout = 10 * time.Second uut, clock, err := newBackend() @@ -567,7 +568,7 @@ func testKeepAlive(t *testing.T, newBackend Constructor) { } // testEvents tests scenarios with event watches -func testEvents(t *testing.T, newBackend Constructor) { +func testEvents[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { const eventTimeout = 10 * time.Second var ttlDeleteTimeout = eventTimeout // TELEPORT_BACKEND_TEST_TTL_DELETE_TIMEOUT may be set to extend the time waited @@ -649,7 +650,7 @@ func testEvents(t *testing.T, newBackend Constructor) { } // testFetchLimit tests fetch max items size limit. -func testFetchLimit(t *testing.T, newBackend Constructor) { +func testFetchLimit[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { uut, _, err := newBackend() require.NoError(t, err) defer func() { require.NoError(t, uut.Close()) }() @@ -674,7 +675,7 @@ func testFetchLimit(t *testing.T, newBackend Constructor) { } // testLimit tests limit. -func testLimit(t *testing.T, newBackend Constructor) { +func testLimit[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { uut, clock, err := newBackend() require.NoError(t, err) defer func() { require.NoError(t, uut.Close()) }() @@ -751,7 +752,7 @@ func requireNoEvent(t *testing.T, watcher backend.Watcher, timeout time.Duration } // WatchersClose tests scenarios with watches close -func testWatchersClose(t *testing.T, newBackend Constructor) { +func testWatchersClose[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { uut, _, err := newBackend() require.NoError(t, err) @@ -792,7 +793,7 @@ func testWatchersClose(t *testing.T, newBackend Constructor) { } // testLocking tests locking logic -func testLocking(t *testing.T, newBackend Constructor) { +func testLocking[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { tok1 := "token1" tok2 := "token2" ttl := 30 * time.Second @@ -927,7 +928,7 @@ func testLocking(t *testing.T, newBackend Constructor) { // testConcurrentOperations tests concurrent operations on the same // shared backend -func testConcurrentOperations(t *testing.T, newBackend Constructor) { +func testConcurrentOperations[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { uutA, _, err := newBackend() require.NoError(t, err) defer func() { require.NoError(t, uutA.Close()) }() @@ -1022,7 +1023,7 @@ func testConcurrentOperations(t *testing.T, newBackend Constructor) { // Mirror tests mirror mode for backends (used in caches). Only some backends // support mirror mode (like memory). -func testMirror(t *testing.T, newBackend Constructor) { +func testMirror[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { const eventTimeout = 2 * time.Second uut, _, err := newBackend(WithMirrorMode(true)) @@ -1103,7 +1104,7 @@ func testMirror(t *testing.T, newBackend Constructor) { require.NoError(t, err) } -func testConditionalDelete(t *testing.T, newBackend Constructor) { +func testConditionalDelete[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { uut, _, err := newBackend() require.NoError(t, err) defer func() { require.NoError(t, uut.Close()) }() @@ -1143,7 +1144,7 @@ func testConditionalDelete(t *testing.T, newBackend Constructor) { require.ErrorIs(t, err, backend.ErrIncorrectRevision) } -func testConditionalUpdate(t *testing.T, newBackend Constructor) { +func testConditionalUpdate[FakeClock clocki.FakeClock](t *testing.T, newBackend Constructor[FakeClock]) { uut, _, err := newBackend() require.NoError(t, err) defer func() { require.NoError(t, uut.Close()) }() diff --git a/lib/srv/mock.go b/lib/srv/mock.go index 548019d2928d6..cda6f938a9dc5 100644 --- a/lib/srv/mock.go +++ b/lib/srv/mock.go @@ -47,6 +47,7 @@ import ( rsession "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/teleport/lib/utils/clocki" ) func newTestServerContext(t *testing.T, srv Server, roleSet services.RoleSet) *ServerContext { @@ -159,7 +160,7 @@ type mockServer struct { datadir string auth *auth.Server component string - clock clockwork.FakeClock + clock clocki.FakeClock bpf bpf.BPF } diff --git a/lib/utils/clocki/advance.go b/lib/utils/clocki/advance.go new file mode 100644 index 0000000000000..12cb83baba172 --- /dev/null +++ b/lib/utils/clocki/advance.go @@ -0,0 +1,31 @@ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package clocki + +import ( + "time" + + "github.com/jonboulle/clockwork" +) + +// Advance attempts to advance an underlying fake clock. +// It's a noop on real clocks. +func Advance(clock clockwork.Clock, d time.Duration) { + if c, ok := clock.(interface{ Advance(time.Duration) }); ok { + c.Advance(d) + } +} diff --git a/lib/utils/clocki/doc.go b/lib/utils/clocki/doc.go new file mode 100644 index 0000000000000..1d100b81bc4a0 --- /dev/null +++ b/lib/utils/clocki/doc.go @@ -0,0 +1,19 @@ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Package clocki holds interfaces and utilities to deal with clockwork that +// are resilient to its breaking changes. +package clocki diff --git a/lib/utils/clocki/fake_clock.go b/lib/utils/clocki/fake_clock.go new file mode 100644 index 0000000000000..f0be42cb32dfa --- /dev/null +++ b/lib/utils/clocki/fake_clock.go @@ -0,0 +1,35 @@ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package clocki + +import ( + "time" + + "github.com/jonboulle/clockwork" +) + +// FakeClock duplicates its namesake interface as defined by clockwork versions +// prior to v0.5.0. +type FakeClock interface { + clockwork.Clock + + // Advance advances the FakeClock to a new point in time, ensuring any existing + // waiters are notified appropriately before returning. + Advance(d time.Duration) + // BlockUntil blocks until the FakeClock has the given number of waiters. + BlockUntil(waiters int) +}