From 37771f8c562a8b874ebd0dfd78c923b754cd9f4c Mon Sep 17 00:00:00 2001 From: Max Kuznetsov Date: Thu, 6 Jul 2023 05:41:41 -0400 Subject: [PATCH] leaderboard rank cache improvements --- CHANGELOG.md | 1 + internal/skiplist/element.go | 15 +- internal/skiplist/skiplist.go | 24 - internal/skiplist/skiplist_test.go | 34 +- server/api_account.go | 2 +- server/api_leaderboard.go | 1 - server/api_leaderboard_test.go | 304 +++ server/api_test.go | 29 +- server/config.go | 2 + server/console_account.go | 2 +- server/core_account.go | 4 +- server/core_leaderboard.go | 58 +- server/core_tournament.go | 40 +- server/leaderboard_rank_cache.go | 386 ++-- server/leaderboard_rank_cache_test.go | 287 +-- server/runtime_go_nakama.go | 2 +- server/runtime_javascript_nakama.go | 2 +- server/runtime_lua_nakama.go | 2 +- server/runtime_test.go | 29 +- .../stretchr/testify/require/doc.go | 28 + .../testify/require/forward_requirements.go | 16 + .../stretchr/testify/require/require.go | 1935 +++++++++++++++++ .../stretchr/testify/require/require.go.tmpl | 6 + .../testify/require/require_forward.go | 1515 +++++++++++++ .../testify/require/require_forward.go.tmpl | 5 + .../stretchr/testify/require/requirements.go | 29 + vendor/modules.txt | 1 + 27 files changed, 4364 insertions(+), 395 deletions(-) create mode 100644 server/api_leaderboard_test.go create mode 100644 vendor/github.com/stretchr/testify/require/doc.go create mode 100644 vendor/github.com/stretchr/testify/require/forward_requirements.go create mode 100644 vendor/github.com/stretchr/testify/require/require.go create mode 100644 vendor/github.com/stretchr/testify/require/require.go.tmpl create mode 100644 vendor/github.com/stretchr/testify/require/require_forward.go create mode 100644 vendor/github.com/stretchr/testify/require/require_forward.go.tmpl create mode 100644 vendor/github.com/stretchr/testify/require/requirements.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 99cebf4691..7807d0093b 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [keep a changelog](http://keepachangelog.com) and this pr ## [Unreleased] ### Changed - Better formatting for graphed values in devconsole status view. +- Memory usage and population time improvements in leaderboard rank cache. ### Fixed - Correct cursor usage in group listings using only open/closed group state filter. diff --git a/internal/skiplist/element.go b/internal/skiplist/element.go index 754cdbe237..9c65772c9e 100644 --- a/internal/skiplist/element.go +++ b/internal/skiplist/element.go @@ -13,9 +13,8 @@ type skiplistLevel struct { } type Element struct { - Value Interface - backward *Element - level []*skiplistLevel + Value Interface + level []*skiplistLevel } // Next returns the next skiplist element or nil. @@ -23,11 +22,6 @@ func (e *Element) Next() *Element { return e.level[0].forward } -// Prev returns the previous skiplist element of nil. -func (e *Element) Prev() *Element { - return e.backward -} - // newElement returns an initialized element. func newElement(level int, v Interface) *Element { slLevels := make([]*skiplistLevel, level) @@ -36,9 +30,8 @@ func newElement(level int, v Interface) *Element { } return &Element{ - Value: v, - backward: nil, - level: slLevels, + Value: v, + level: slLevels, } } diff --git a/internal/skiplist/skiplist.go b/internal/skiplist/skiplist.go index 6b3be64977..e9da6b0077 100644 --- a/internal/skiplist/skiplist.go +++ b/internal/skiplist/skiplist.go @@ -9,7 +9,6 @@ type Interface interface { type SkipList struct { r *rand.Rand header *Element - tail *Element update []*Element rank []int length int @@ -21,7 +20,6 @@ func New() *SkipList { return &SkipList{ r: rand.New(rand.NewSource(1)), header: newElement(SKIPLIST_MAXLEVEL, nil), - tail: nil, update: make([]*Element, SKIPLIST_MAXLEVEL), rank: make([]int, SKIPLIST_MAXLEVEL), length: 0, @@ -32,7 +30,6 @@ func New() *SkipList { // Init initializes or clears skiplist sl. func (sl *SkipList) Init() *SkipList { sl.header = newElement(SKIPLIST_MAXLEVEL, nil) - sl.tail = nil sl.update = make([]*Element, SKIPLIST_MAXLEVEL) sl.rank = make([]int, SKIPLIST_MAXLEVEL) sl.length = 0 @@ -45,11 +42,6 @@ func (sl *SkipList) Front() *Element { return sl.header.level[0].forward } -// Back returns the last elements of skiplist sl or nil. -func (sl *SkipList) Back() *Element { - return sl.tail -} - // Len returns the numbler of elements of skiplist sl. func (sl *SkipList) Len() int { return sl.length @@ -99,16 +91,6 @@ func (sl *SkipList) Insert(v Interface) *Element { sl.update[i].level[i].span++ } - if sl.update[0] == sl.header { - x.backward = nil - } else { - x.backward = sl.update[0] - } - if x.level[0].forward != nil { - x.level[0].forward.backward = x - } else { - sl.tail = x - } sl.length++ return x @@ -125,12 +107,6 @@ func (sl *SkipList) deleteElement(e *Element, update []*Element) { } } - if e.level[0].forward != nil { - e.level[0].forward.backward = e.backward - } else { - sl.tail = e.backward - } - for sl.level > 1 && sl.header.level[sl.level-1].forward == nil { sl.level-- } diff --git a/internal/skiplist/skiplist_test.go b/internal/skiplist/skiplist_test.go index ef5b5f73c0..fda2ca6cc1 100644 --- a/internal/skiplist/skiplist_test.go +++ b/internal/skiplist/skiplist_test.go @@ -15,24 +15,24 @@ func (i Int) Less(other interface{}) bool { func TestInt(t *testing.T) { sl := New() - if sl.Len() != 0 || sl.Front() != nil && sl.Back() != nil { + if sl.Len() != 0 || sl.Front() != nil { t.Fatal() } testData := []Int{Int(1), Int(2), Int(3)} sl.Insert(testData[0]) - if sl.Len() != 1 || sl.Front().Value.(Int) != testData[0] || sl.Back().Value.(Int) != testData[0] { + if sl.Len() != 1 || sl.Front().Value.(Int) != testData[0] { t.Fatal() } sl.Insert(testData[2]) - if sl.Len() != 2 || sl.Front().Value.(Int) != testData[0] || sl.Back().Value.(Int) != testData[2] { + if sl.Len() != 2 || sl.Front().Value.(Int) != testData[0] { t.Fatal() } sl.Insert(testData[1]) - if sl.Len() != 3 || sl.Front().Value.(Int) != testData[0] || sl.Back().Value.(Int) != testData[2] { + if sl.Len() != 3 || sl.Front().Value.(Int) != testData[0] { t.Fatal() } @@ -74,29 +74,13 @@ func TestInt(t *testing.T) { sl.Delete(Int(1000)) expect = []Int{Int(-999), Int(-888), Int(1), Int(3), Int(999)} - ret = make([]Int, 0) - - for e := sl.Back(); e != nil; e = e.Prev() { - ret = append(ret, e.Value.(Int)) - } - - for i := 0; i < len(ret); i++ { - if ret[i] != expect[len(ret)-i-1] { - t.Fatal() - } - } if sl.Front().Value.(Int) != -999 { t.Fatal() } sl.Remove(sl.Front()) - if sl.Front().Value.(Int) != -888 || sl.Back().Value.(Int) != 999 { - t.Fatal() - } - - sl.Remove(sl.Back()) - if sl.Front().Value.(Int) != -888 || sl.Back().Value.(Int) != 3 { + if sl.Front().Value.(Int) != -888 { t.Fatal() } @@ -109,7 +93,7 @@ func TestInt(t *testing.T) { t.Fatal() } - if sl.Len() != 3 { + if sl.Len() != 4 { t.Fatal() } @@ -121,7 +105,7 @@ func TestInt(t *testing.T) { t.Fatal() } - expect = []Int{Int(2), Int(2), Int(2), Int(3)} + expect = []Int{Int(2), Int(2), Int(2), Int(3), Int(999)} ret = make([]Int, 0) for ; e != nil; e = e.Next() { ret = append(ret, e.Value.(Int)) @@ -133,8 +117,8 @@ func TestInt(t *testing.T) { } sl2 := sl.Init() - if sl.Len() != 0 || sl.Front() != nil || sl.Back() != nil || - sl2.Len() != 0 || sl2.Front() != nil || sl2.Back() != nil { + if sl.Len() != 0 || sl.Front() != nil || + sl2.Len() != 0 || sl2.Front() != nil { t.Fatal() } diff --git a/server/api_account.go b/server/api_account.go index 3b3bca8dea..10ef9ea656 100644 --- a/server/api_account.go +++ b/server/api_account.go @@ -93,7 +93,7 @@ func (s *ApiServer) DeleteAccount(ctx context.Context, in *emptypb.Empty) (*empt } } - if err := DeleteAccount(ctx, s.logger, s.db, s.config, s.leaderboardRankCache, s.sessionRegistry, s.sessionCache, s.tracker, userID, false); err != nil { + if err := DeleteAccount(ctx, s.logger, s.db, s.config, s.leaderboardCache, s.leaderboardRankCache, s.sessionRegistry, s.sessionCache, s.tracker, userID, false); err != nil { if err == ErrAccountNotFound { return nil, status.Error(codes.NotFound, "Account not found.") } diff --git a/server/api_leaderboard.go b/server/api_leaderboard.go index f27d9133ee..6fcf683f12 100644 --- a/server/api_leaderboard.go +++ b/server/api_leaderboard.go @@ -18,7 +18,6 @@ import ( "bytes" "context" "encoding/json" - "github.com/gofrs/uuid" "github.com/heroiclabs/nakama-common/api" "go.uber.org/zap" diff --git a/server/api_leaderboard_test.go b/server/api_leaderboard_test.go new file mode 100644 index 0000000000..71c0b3d6a3 --- /dev/null +++ b/server/api_leaderboard_test.go @@ -0,0 +1,304 @@ +package server + +import ( + "context" + "database/sql" + "fmt" + "syscall" + "testing" + + "github.com/gofrs/uuid" + "github.com/heroiclabs/nakama-common/api" + "github.com/heroiclabs/nakama/v3/apigrpc" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +func TestApiLeaderboard(t *testing.T) { + newId := func() uuid.UUID { return uuid.Must(uuid.NewV4()) } + type testUser struct { + id uuid.UUID + score int64 + subScore int64 + conn *grpc.ClientConn + cl apigrpc.NakamaClient + ctx context.Context + } + + newUsers := func() []*testUser { + return []*testUser{ + {score: 10, subScore: 11}, + {score: 20, subScore: 21}, + {score: 30, subScore: 31}, + {score: 40, subScore: 41}, + {score: 50, subScore: 51}, + } + } + + verifyList := func(ctx context.Context, cl apigrpc.NakamaClient, + lbId string, users []*testUser) { + + resp, err := cl.ListLeaderboardRecords(ctx, &api.ListLeaderboardRecordsRequest{ + LeaderboardId: lbId, + Limit: wrapperspb.Int32(int32(len(users))), + }) + require.NoError(t, err) + require.Len(t, resp.Records, len(users)) + + for i, u := range users { + require.Equal(t, u.id.String(), resp.Records[i].OwnerId) + require.Equal(t, u.score, resp.Records[i].Score) + require.Equal(t, u.subScore, resp.Records[i].Subscore) + require.Equal(t, int64(i+1), resp.Records[i].Rank) + } + } + + populateLb := func(users []*testUser, lbId string) { + for i := range users { + u := users[i] + conn, cl, ses, ctx := NewAuthenticatedAPIClient(t, newId().String()) + userId, err := UserIDFromSession(ses) + require.NoError(t, err) + + u.id = userId + u.conn = conn + u.cl = cl + u.ctx = ctx + + _, err = u.cl.WriteLeaderboardRecord(ctx, &api.WriteLeaderboardRecordRequest{ + LeaderboardId: lbId, + Record: &api.WriteLeaderboardRecordRequest_LeaderboardRecordWrite{ + Score: u.score, + Subscore: u.subScore, + }, + }) + require.NoError(t, err, "should write user leaderboard record") + } + } + + cleanup := func(db *sql.DB, srv *ApiServer, + conn *grpc.ClientConn, users []*testUser) { + + _ = db.Close() + srv.Stop() + _ = conn.Close() + + for _, u := range users { + if u.conn != nil { + _ = u.conn.Close() + } + } + + // Wait until the socket is closed to avoid conflicts for the following tests + WaitForSocket(syscall.ECONNREFUSED, cfg) + } + + newAPI := func(lb *Leaderboard) (*grpc.ClientConn, apigrpc.NakamaClient, *ApiServer, context.Context) { + + modules := map[string]string{ + "lb-init": fmt.Sprintf(` +local nk = require("nakama") +local reset = "" +local metadata = {} +nk.leaderboard_create(%q, %v, %q, %q, reset, metadata) +`, lb.Id, lb.Authoritative, lb.GetSortOrder(), lb.GetOperator()), + } + + runtime, _, rtData, err := runtimeWithModulesWithData(t, modules) + require.NoError(t, err) + + db := NewDB(t) + router := &DummyMessageRouter{} + tracker := &LocalTracker{} + sessionCache := NewLocalSessionCache(1000) + + pipeline := NewPipeline(logger, cfg, db, protojsonMarshaler, protojsonUnmarshaler, nil, nil, nil, nil, nil, tracker, router, runtime) + + apiServer := StartApiServer(logger, logger, db, protojsonMarshaler, + protojsonUnmarshaler, cfg, "3.0.0", nil, rtData.leaderboardCache, + rtData.leaderboardRankCache, nil, sessionCache, + nil, nil, nil, tracker, router, nil, metrics, pipeline, runtime) + + WaitForSocket(nil, cfg) + + conn, client, _, ctx := NewAuthenticatedAPIClient(t, uuid.Must(uuid.NewV4()).String()) + + return conn, client, apiServer, ctx + } + + t.Run("create and list leaderboard", func(t *testing.T) { + lbId := newId().String() + conn, cl, srv, ctx := newAPI(&Leaderboard{ + Id: lbId, + }) + defer conn.Close() + defer srv.Stop() + + resp, err := cl.ListLeaderboardRecords(ctx, &api.ListLeaderboardRecordsRequest{ + LeaderboardId: lbId, + }) + require.NoError(t, err) + + require.Empty(t, resp.Records) + }) + + t.Run("override records", func(t *testing.T) { + lbId := newId().String() + db := NewDB(t) + conn, cl, srv, ctx := newAPI(&Leaderboard{ + Id: lbId, + SortOrder: LeaderboardSortOrderDescending, + Operator: LeaderboardOperatorSet, + }) + + users := newUsers() + defer cleanup(db, srv, conn, users) + + populateLb(users, lbId) + + verifyList(ctx, cl, lbId, []*testUser{ + users[4], users[3], users[2], users[1], users[0], + }) + + // Update scores for u2 and u3 + users[2].score = 500 + users[2].subScore = 501 + _, err := users[2].cl.WriteLeaderboardRecord( + users[2].ctx, &api.WriteLeaderboardRecordRequest{ + LeaderboardId: lbId, + Record: &api.WriteLeaderboardRecordRequest_LeaderboardRecordWrite{ + Score: users[2].score, + Subscore: users[2].subScore, + }, + }) + require.NoError(t, err, "should update user leaderboard record") + + users[3].score = 200 + users[3].subScore = 201 + _, err = users[3].cl.WriteLeaderboardRecord( + users[3].ctx, &api.WriteLeaderboardRecordRequest{ + LeaderboardId: lbId, + Record: &api.WriteLeaderboardRecordRequest_LeaderboardRecordWrite{ + Score: users[3].score, + Subscore: users[3].subScore, + }, + }) + require.NoError(t, err, "should update user leaderboard record") + + // The order is now different + verifyList(ctx, cl, lbId, []*testUser{ + users[2], users[3], users[4], users[1], users[0], + }) + }) + + t.Run("delete records", func(t *testing.T) { + lbId := newId().String() + db := NewDB(t) + conn, cl, srv, ctx := newAPI(&Leaderboard{ + Id: lbId, + SortOrder: LeaderboardSortOrderDescending, + Operator: LeaderboardOperatorSet, + }) + + users := newUsers() + defer cleanup(db, srv, conn, users) + + populateLb(users, lbId) + + verifyList(ctx, cl, lbId, []*testUser{ + users[4], users[3], users[2], users[1], users[0], + }) + + // Delete scores for u2 and u3 + _, err := users[2].cl.DeleteLeaderboardRecord( + users[2].ctx, &api.DeleteLeaderboardRecordRequest{ + LeaderboardId: lbId, + }) + require.NoError(t, err, "should delete user leaderboard record") + + _, err = users[3].cl.DeleteLeaderboardRecord( + users[3].ctx, &api.DeleteLeaderboardRecordRequest{ + LeaderboardId: lbId, + }) + require.NoError(t, err, "should delete user leaderboard record") + + // u2 and u3 should be missing from the list + verifyList(ctx, cl, lbId, []*testUser{ + users[4], users[1], users[0], + }) + }) + + t.Run("list records around owner", func(t *testing.T) { + lbId := newId().String() + db := NewDB(t) + conn, cl, srv, ctx := newAPI(&Leaderboard{ + Id: lbId, + SortOrder: LeaderboardSortOrderDescending, + Operator: LeaderboardOperatorSet, + }) + + users := newUsers() + defer cleanup(db, srv, conn, users) + + populateLb(users, lbId) + + verifyList(ctx, cl, lbId, []*testUser{ + users[4], users[3], users[2], users[1], users[0], + }) + + // Fetch from the middle + resp, err := cl.ListLeaderboardRecordsAroundOwner(ctx, &api.ListLeaderboardRecordsAroundOwnerRequest{ + LeaderboardId: lbId, + Limit: wrapperspb.UInt32(3), + OwnerId: users[2].id.String(), + }) + require.NoError(t, err, "should list user leaderboard records around owner") + + require.Len(t, resp.Records, 3) + require.Equal(t, users[3].id.String(), resp.Records[0].OwnerId) + require.Equal(t, int64(2), resp.Records[0].Rank) + + require.Equal(t, users[2].id.String(), resp.Records[1].OwnerId) + require.Equal(t, int64(3), resp.Records[1].Rank) + + require.Equal(t, users[1].id.String(), resp.Records[2].OwnerId) + require.Equal(t, int64(4), resp.Records[2].Rank) + + // Fetch from the top + resp, err = cl.ListLeaderboardRecordsAroundOwner(ctx, &api.ListLeaderboardRecordsAroundOwnerRequest{ + LeaderboardId: lbId, + Limit: wrapperspb.UInt32(3), + OwnerId: users[4].id.String(), + }) + require.NoError(t, err, "should list user leaderboard records around owner") + + require.Len(t, resp.Records, 3) + require.Equal(t, users[4].id.String(), resp.Records[0].OwnerId) + require.Equal(t, int64(1), resp.Records[0].Rank) + + require.Equal(t, users[3].id.String(), resp.Records[1].OwnerId) + require.Equal(t, int64(2), resp.Records[1].Rank) + + require.Equal(t, users[2].id.String(), resp.Records[2].OwnerId) + require.Equal(t, int64(3), resp.Records[2].Rank) + + // Fetch from the bottom + resp, err = cl.ListLeaderboardRecordsAroundOwner(ctx, &api.ListLeaderboardRecordsAroundOwnerRequest{ + LeaderboardId: lbId, + Limit: wrapperspb.UInt32(3), + OwnerId: users[0].id.String(), + }) + require.NoError(t, err, "should list user leaderboard records around owner") + + require.Len(t, resp.Records, 3) + require.Equal(t, users[2].id.String(), resp.Records[0].OwnerId) + require.Equal(t, int64(3), resp.Records[0].Rank) + + require.Equal(t, users[1].id.String(), resp.Records[1].OwnerId) + require.Equal(t, int64(4), resp.Records[1].Rank) + + require.Equal(t, users[0].id.String(), resp.Records[2].OwnerId) + require.Equal(t, int64(5), resp.Records[2].Rank) + }) +} diff --git a/server/api_test.go b/server/api_test.go index ef6b8de0b5..e2cd7e39c8 100644 --- a/server/api_test.go +++ b/server/api_test.go @@ -19,9 +19,13 @@ import ( "database/sql" "encoding/base64" "encoding/json" + "errors" + "fmt" + "net" "os" "strings" "testing" + "time" "github.com/gofrs/uuid" "github.com/heroiclabs/nakama-common/api" @@ -169,12 +173,35 @@ ON CONFLICT(id) DO NOTHING`, uid, uid.String()); err != nil { } } +func WaitForSocket(expected error, cfg *config) { + ports := []int{cfg.GetSocket().Port - 1, cfg.GetSocket().Port} + + for _, port := range ports { + for { + conn, err := net.Dial("tcp", fmt.Sprintf(":%d", port)) + if conn != nil { + _ = conn.Close() + } + + if errors.Is(err, expected) { + break + } + + time.Sleep(300 * time.Millisecond) + } + } +} + func NewAPIServer(t *testing.T, runtime *Runtime) (*ApiServer, *Pipeline) { db := NewDB(t) router := &DummyMessageRouter{} tracker := &LocalTracker{} + sessionCache := NewLocalSessionCache(1000) pipeline := NewPipeline(logger, cfg, db, protojsonMarshaler, protojsonUnmarshaler, nil, nil, nil, nil, nil, tracker, router, runtime) - apiServer := StartApiServer(logger, logger, db, protojsonMarshaler, protojsonUnmarshaler, cfg, "3.0.0", nil, nil, nil, nil, nil, nil, nil, nil, tracker, router, nil, metrics, pipeline, runtime) + apiServer := StartApiServer(logger, logger, db, protojsonMarshaler, protojsonUnmarshaler, cfg, "3.0.0", nil, nil, nil, nil, sessionCache, nil, nil, nil, tracker, router, nil, metrics, pipeline, runtime) + + WaitForSocket(nil, cfg) + return apiServer, pipeline } diff --git a/server/config.go b/server/config.go index 53279c251f..358996cd07 100644 --- a/server/config.go +++ b/server/config.go @@ -958,6 +958,7 @@ type LeaderboardConfig struct { BlacklistRankCache []string `yaml:"blacklist_rank_cache" json:"blacklist_rank_cache" usage:"Disable rank cache for leaderboards with matching identifiers. To disable rank cache entirely, use '*', otherwise leave blank to enable rank cache."` CallbackQueueSize int `yaml:"callback_queue_size" json:"callback_queue_size" usage:"Size of the leaderboard and tournament callback queue that sequences expiry/reset/end invocations. Default 65536."` CallbackQueueWorkers int `yaml:"callback_queue_workers" json:"callback_queue_workers" usage:"Number of workers to use for concurrent processing of leaderboard and tournament callbacks. Default 8."` + RankCacheWorkers int `yaml:"rank_cache_workers" json:"rank_cache_workers" usage:"The number of parallel workers to use while populating leaderboard rank cache from the database. Higher number of workers usually makes the process faster but at the cost of increased database load. Default 1."` } func NewLeaderboardConfig() *LeaderboardConfig { @@ -965,6 +966,7 @@ func NewLeaderboardConfig() *LeaderboardConfig { BlacklistRankCache: []string{}, CallbackQueueSize: 65536, CallbackQueueWorkers: 8, + RankCacheWorkers: 1, } } diff --git a/server/console_account.go b/server/console_account.go index 86791f8f1a..a8be681746 100644 --- a/server/console_account.go +++ b/server/console_account.go @@ -86,7 +86,7 @@ func (s *ConsoleServer) DeleteAccount(ctx context.Context, in *console.AccountDe return nil, status.Error(codes.InvalidArgument, "Cannot delete the system user.") } - if err = DeleteAccount(ctx, s.logger, s.db, s.config, s.leaderboardRankCache, s.sessionRegistry, s.sessionCache, s.tracker, userID, in.RecordDeletion != nil && in.RecordDeletion.Value); err != nil { + if err = DeleteAccount(ctx, s.logger, s.db, s.config, s.leaderboardCache, s.leaderboardRankCache, s.sessionRegistry, s.sessionCache, s.tracker, userID, in.RecordDeletion != nil && in.RecordDeletion.Value); err != nil { // Error already logged in function above. return nil, status.Error(codes.Internal, "An error occurred while trying to delete the user.") } diff --git a/server/core_account.go b/server/core_account.go index 25d08eabcb..db77eff8dd 100644 --- a/server/core_account.go +++ b/server/core_account.go @@ -470,7 +470,7 @@ func ExportAccount(ctx context.Context, logger *zap.Logger, db *sql.DB, userID u return export, nil } -func DeleteAccount(ctx context.Context, logger *zap.Logger, db *sql.DB, config Config, leaderboardRankCache LeaderboardRankCache, sessionRegistry SessionRegistry, sessionCache SessionCache, tracker Tracker, userID uuid.UUID, recorded bool) error { +func DeleteAccount(ctx context.Context, logger *zap.Logger, db *sql.DB, config Config, leaderboardCache LeaderboardCache, leaderboardRankCache LeaderboardRankCache, sessionRegistry SessionRegistry, sessionCache SessionCache, tracker Tracker, userID uuid.UUID, recorded bool) error { ts := time.Now().UTC().Unix() tx, err := db.BeginTx(ctx, nil) @@ -490,7 +490,7 @@ func DeleteAccount(ctx context.Context, logger *zap.Logger, db *sql.DB, config C return nil } - err = LeaderboardRecordsDeleteAll(ctx, logger, leaderboardRankCache, tx, userID, ts) + err = LeaderboardRecordsDeleteAll(ctx, logger, leaderboardCache, leaderboardRankCache, tx, userID, ts) if err != nil { logger.Debug("Could not delete leaderboard records.", zap.Error(err), zap.String("user_id", userID.String())) return err diff --git a/server/core_leaderboard.go b/server/core_leaderboard.go index 5c29d43626..66b663a02e 100644 --- a/server/core_leaderboard.go +++ b/server/core_leaderboard.go @@ -340,7 +340,7 @@ func LeaderboardRecordsList(ctx context.Context, logger *zap.Logger, db *sql.DB, } // Bulk fill in the ranks of any owner records requested. - rankCache.Fill(leaderboardId, expiryTime, ownerRecords) + rankCache.Fill(leaderboardId, leaderboard.SortOrder, expiryTime, ownerRecords) return &api.LeaderboardRecordList{ Records: records, @@ -464,7 +464,8 @@ func LeaderboardRecordWrite(ctx context.Context, logger *zap.Logger, db *sql.DB, VALUES ($1, $2, $3, $4, $5, COALESCE($6, '{}'::JSONB), $7) ON CONFLICT (owner_id, leaderboard_id, expiry_time) DO UPDATE SET ` + opSQL + `, num_score = leaderboard_record.num_score + 1, metadata = COALESCE($6, leaderboard_record.metadata), username = COALESCE($3, leaderboard_record.username), update_time = now()` + filterSQL + ` - RETURNING username, score, subscore, num_score, max_num_score, metadata, create_time, update_time` + RETURNING username, score, subscore, num_score, max_num_score, metadata, create_time, update_time, (SELECT score FROM leaderboard_record WHERE leaderboard_id=$1 AND owner_id=$2 AND expiry_time=$7), (SELECT subscore FROM leaderboard_record WHERE leaderboard_id=$1 AND owner_id=$2 AND expiry_time=$7)` + params := make([]interface{}, 0, 9) params = append(params, leaderboardId, ownerID) if username == "" { @@ -489,13 +490,15 @@ func LeaderboardRecordWrite(ctx context.Context, logger *zap.Logger, db *sql.DB, var dbUsername sql.NullString var dbScore int64 var dbSubscore int64 + var dbOldScore sql.NullInt64 + var dbOldSubscore sql.NullInt64 var dbNumScore int32 var dbMaxNumScore int32 var dbMetadata string var dbCreateTime pgtype.Timestamptz var dbUpdateTime pgtype.Timestamptz - if err := db.QueryRowContext(ctx, query, params...).Scan(&dbUsername, &dbScore, &dbSubscore, &dbNumScore, &dbMaxNumScore, &dbMetadata, &dbCreateTime, &dbUpdateTime); err != nil { + if err := db.QueryRowContext(ctx, query, params...).Scan(&dbUsername, &dbScore, &dbSubscore, &dbNumScore, &dbMaxNumScore, &dbMetadata, &dbCreateTime, &dbUpdateTime, &dbOldScore, &dbOldSubscore); err != nil { var pgErr *pgconn.PgError if err != sql.ErrNoRows && !(errors.As(err, &pgErr) && pgErr.Code == dbErrorUniqueViolation && strings.Contains(pgErr.Message, "leaderboard_record_pkey")) { logger.Error("Error writing leaderboard record", zap.Error(err)) @@ -518,10 +521,20 @@ func LeaderboardRecordWrite(ctx context.Context, logger *zap.Logger, db *sql.DB, var rank int64 if unchanged { - rank = rankCache.Get(leaderboardId, expiryTime, uuid.Must(uuid.FromString(ownerID))) + rank = rankCache.Get(leaderboardId, leaderboard.SortOrder, dbScore, dbSubscore, expiryTime, uuid.Must(uuid.FromString(ownerID))) } else { + var oldScore *int64 + var oldSubscore *int64 + + if dbOldScore.Valid { + oldScore = &dbOldScore.Int64 + } + if dbOldSubscore.Valid { + oldSubscore = &dbOldSubscore.Int64 + } + // Ensure we have the latest dbscore, dbsubscore if there was an update. - rank = rankCache.Insert(leaderboardId, expiryTime, leaderboard.SortOrder, uuid.Must(uuid.FromString(ownerID)), dbScore, dbSubscore) + rank = rankCache.Insert(leaderboardId, leaderboard.SortOrder, dbScore, dbSubscore, oldScore, oldSubscore, expiryTime, uuid.Must(uuid.FromString(ownerID))) } record := &api.LeaderboardRecord{ @@ -561,14 +574,23 @@ func LeaderboardRecordDelete(ctx context.Context, logger *zap.Logger, db *sql.DB expiryTime = leaderboard.ResetSchedule.Next(time.Now().UTC()).UTC().Unix() } - query := "DELETE FROM leaderboard_record WHERE leaderboard_id = $1 AND owner_id = $2 AND expiry_time = $3" - _, err := db.ExecContext(ctx, query, leaderboardId, ownerID, time.Unix(expiryTime, 0).UTC()) - if err != nil { + var score sql.NullInt64 + var subscore sql.NullInt64 + + query := "DELETE FROM leaderboard_record WHERE leaderboard_id = $1 AND owner_id = $2 AND expiry_time = $3 RETURNING score, subscore" + err := db.QueryRowContext( + ctx, query, leaderboardId, ownerID, time.Unix(expiryTime, 0).UTC()). + Scan(&score, &subscore) + if err != nil && !errors.Is(err, sql.ErrNoRows) { logger.Error("Error deleting leaderboard record", zap.Error(err)) return err } - rankCache.Delete(leaderboardId, expiryTime, uuid.Must(uuid.FromString(ownerID))) + if score.Valid && subscore.Valid { + rankCache.Delete(leaderboardId, leaderboard.SortOrder, + score.Int64, subscore.Int64, expiryTime, uuid.Must(uuid.FromString(ownerID))) + } + return nil } @@ -584,8 +606,8 @@ func LeaderboardRecordReadAll(ctx context.Context, logger *zap.Logger, db *sql.D return parseLeaderboardRecords(logger, rows) } -func LeaderboardRecordsDeleteAll(ctx context.Context, logger *zap.Logger, leaderboardRankCache LeaderboardRankCache, tx *sql.Tx, userID uuid.UUID, currentTime int64) error { - query := "DELETE FROM leaderboard_record WHERE owner_id = $1 RETURNING leaderboard_id, expiry_time" +func LeaderboardRecordsDeleteAll(ctx context.Context, logger *zap.Logger, leaderboardCache LeaderboardCache, leaderboardRankCache LeaderboardRankCache, tx *sql.Tx, userID uuid.UUID, currentTime int64) error { + query := "DELETE FROM leaderboard_record WHERE owner_id = $1 RETURNING leaderboard_id, expiry_time, score, subscore" rows, err := tx.QueryContext(ctx, query, userID.String()) if err != nil { logger.Error("Error deleting all leaderboard records for user", zap.String("user_id", userID.String()), zap.Error(err)) @@ -594,8 +616,10 @@ func LeaderboardRecordsDeleteAll(ctx context.Context, logger *zap.Logger, leader var leaderboardId string var expiryTime pgtype.Timestamptz + var score int64 + var subscore int64 for rows.Next() { - if err := rows.Scan(&leaderboardId, &expiryTime); err != nil { + if err := rows.Scan(&leaderboardId, &expiryTime, &score, &subscore); err != nil { _ = rows.Close() logger.Error("Error deleting all leaderboard records for user, failed to scan", zap.String("user_id", userID.String()), zap.Error(err)) return err @@ -607,7 +631,13 @@ func LeaderboardRecordsDeleteAll(ctx context.Context, logger *zap.Logger, leader continue } - leaderboardRankCache.Delete(leaderboardId, expiryUnix, userID) + leaderboard := leaderboardCache.Get(leaderboardId) + if leaderboard == nil { + continue + } + + leaderboardRankCache.Delete( + leaderboardId, leaderboard.SortOrder, score, subscore, expiryUnix, userID) } _ = rows.Close() @@ -853,7 +883,7 @@ func getLeaderboardRecordsHaystack(ctx context.Context, logger *zap.Logger, db * } records = records[start:end] - rankCache.Fill(leaderboardId, expiryTime.Unix(), records) + rankCache.Fill(leaderboardId, sortOrder, expiryTime.Unix(), records) var prevCursorStr string if setPrevCursor { diff --git a/server/core_tournament.go b/server/core_tournament.go index a29fa5931c..b1685539a0 100644 --- a/server/core_tournament.go +++ b/server/core_tournament.go @@ -184,7 +184,7 @@ ON CONFLICT(owner_id, leaderboard_id, expiry_time) DO NOTHING` // Ensure new tournament joiner is included in the rank cache. if isNewJoin { - _ = rankCache.Insert(leaderboard.Id, expiryTime, leaderboard.SortOrder, ownerID, 0, 0) + _ = rankCache.Insert(leaderboard.Id, leaderboard.SortOrder, 0, 0, nil, nil, expiryTime, ownerID) } logger.Info("Joined tournament.", zap.String("tournament_id", tournamentId), zap.String("owner", ownerID.String()), zap.String("username", username)) @@ -529,11 +529,12 @@ func TournamentRecordWrite(ctx context.Context, logger *zap.Logger, db *sql.DB, params = append(params, metadata) } + var dbOldScore, dbOldSubscore sql.NullInt64 + if leaderboard.JoinRequired { // If join is required then the user must already have a record to update. // There's also no need to increment the number of records tracked for this tournament. - var exists int - err := db.QueryRowContext(ctx, "SELECT 1 FROM leaderboard_record WHERE leaderboard_id = $1 AND owner_id = $2 AND expiry_time = $3", leaderboard.Id, ownerId, expiryTime).Scan(&exists) + err := db.QueryRowContext(ctx, "SELECT score, subscore FROM leaderboard_record WHERE leaderboard_id = $1 AND owner_id = $2 AND expiry_time = $3", leaderboard.Id, ownerId, expiryTime).Scan(&dbOldScore, &dbOldSubscore) if err != nil { if err == sql.ErrNoRows { // Tournament required join but no row was found to update. @@ -557,7 +558,7 @@ func TournamentRecordWrite(ctx context.Context, logger *zap.Logger, db *sql.DB, query := `INSERT INTO leaderboard_record (leaderboard_id, owner_id, username, score, subscore, metadata, expiry_time, max_num_score) VALUES ($1, $2, $3, $9, $10, COALESCE($7, '{}'::JSONB), $4, $8) ON CONFLICT (owner_id, leaderboard_id, expiry_time) - DO UPDATE SET ` + opSQL + `, num_score = leaderboard_record.num_score + 1, metadata = COALESCE($7, leaderboard_record.metadata), username = COALESCE($3, leaderboard_record.username), update_time = now()` + filterSQL + DO UPDATE SET ` + opSQL + `, num_score = leaderboard_record.num_score + 1, metadata = COALESCE($7, leaderboard_record.metadata), username = COALESCE($3, leaderboard_record.username), update_time = now()` + filterSQL + ` RETURNING (SELECT score FROM leaderboard_record WHERE leaderboard_id=$1 AND owner_id=$2 AND expiry_time=$4), (SELECT subscore FROM leaderboard_record WHERE leaderboard_id=$1 AND owner_id=$2 AND expiry_time=$4)` params = append(params, leaderboard.MaxNumScore, scoreAbs, subscoreAbs) tx, err := db.BeginTx(ctx, nil) @@ -567,8 +568,8 @@ func TournamentRecordWrite(ctx context.Context, logger *zap.Logger, db *sql.DB, } if err := ExecuteInTx(ctx, tx, func() error { - recordQueryResult, err := tx.ExecContext(ctx, query, params...) - if err != nil { + err := tx.QueryRowContext(ctx, query, params...).Scan(&dbOldScore, &dbOldSubscore) + if err != nil && err != sql.ErrNoRows { var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == dbErrorUniqueViolation && strings.Contains(pgErr.Message, "leaderboard_record_pkey") { return errTournamentWriteNoop @@ -577,7 +578,7 @@ func TournamentRecordWrite(ctx context.Context, logger *zap.Logger, db *sql.DB, } // A record was inserted or updated - if rowsAffected, _ := recordQueryResult.RowsAffected(); rowsAffected > 0 { + if err == nil { var dbNumScore int var dbMaxNumScore int @@ -652,8 +653,16 @@ func TournamentRecordWrite(ctx context.Context, logger *zap.Logger, db *sql.DB, record.ExpiryTime = ×tamppb.Timestamp{Seconds: u} } + var oldScore, oldSubscore *int64 + if dbOldScore.Valid { + oldScore = &dbOldScore.Int64 + } + if dbOldSubscore.Valid { + oldSubscore = &dbOldSubscore.Int64 + } + // Enrich the return record with rank data. - record.Rank = rankCache.Insert(leaderboard.Id, expiryUnix, leaderboard.SortOrder, ownerId, record.Score, record.Subscore) + record.Rank = rankCache.Insert(leaderboard.Id, leaderboard.SortOrder, record.Score, record.Subscore, oldScore, oldSubscore, expiryUnix, ownerId) return record, nil } @@ -672,14 +681,23 @@ func TournamentRecordDelete(ctx context.Context, logger *zap.Logger, db *sql.DB, now := time.Now().UTC() _, _, expiryUnix := calculateTournamentDeadlines(tournament.StartTime, tournament.EndTime, int64(tournament.Duration), tournament.ResetSchedule, now) - query := "DELETE FROM leaderboard_record WHERE leaderboard_id = $1 AND owner_id = $2 AND expiry_time = $3" - _, err := db.ExecContext(ctx, query, tournamentID, ownerID, time.Unix(expiryUnix, 0).UTC()) + query := "DELETE FROM leaderboard_record WHERE leaderboard_id = $1 AND owner_id = $2 AND expiry_time = $3 RETURNING score, subscore" + + var score sql.NullInt64 + var subscore sql.NullInt64 + + err := db.QueryRowContext( + ctx, query, tournamentID, ownerID, time.Unix(expiryUnix, 0).UTC()). + Scan(&score, &subscore) if err != nil { logger.Error("Error deleting tournament record", zap.Error(err)) return err } - rankCache.Delete(tournamentID, expiryUnix, uuid.Must(uuid.FromString(ownerID))) + if score.Valid && subscore.Valid { + rankCache.Delete(tournamentID, tournament.SortOrder, score.Int64, + subscore.Int64, expiryUnix, uuid.Must(uuid.FromString(ownerID))) + } return nil } diff --git a/server/leaderboard_rank_cache.go b/server/leaderboard_rank_cache.go index 17fd75e741..4830fcf5b1 100644 --- a/server/leaderboard_rank_cache.go +++ b/server/leaderboard_rank_cache.go @@ -17,7 +17,10 @@ package server import ( "context" "database/sql" + "fmt" + "runtime" "sync" + "sync/atomic" "time" "github.com/gofrs/uuid" @@ -27,10 +30,10 @@ import ( ) type LeaderboardRankCache interface { - Get(leaderboardId string, expiryUnix int64, ownerID uuid.UUID) int64 - Fill(leaderboardId string, expiryUnix int64, records []*api.LeaderboardRecord) - Insert(leaderboardId string, expiryUnix int64, sortOrder int, ownerID uuid.UUID, score, subscore int64) int64 - Delete(leaderboardId string, expiryUnix int64, ownerID uuid.UUID) bool + Get(leaderboardId string, sortOrder int, score, subscore, expiryUnix int64, ownerID uuid.UUID) int64 + Fill(leaderboardId string, sortOrder int, expiryUnix int64, records []*api.LeaderboardRecord) + Insert(leaderboardId string, sortOrder int, score, subscore int64, oldScore, oldSubscore *int64, expiryUnix int64, ownerID uuid.UUID) int64 + Delete(leaderboardId string, sortOrder int, score, subscore, expiryUnix int64, ownerID uuid.UUID) bool DeleteLeaderboard(leaderboardId string, expiryUnix int64) bool TrimExpired(nowUnix int64) bool } @@ -46,8 +49,8 @@ type RankAsc struct { Subscore int64 } -func (r *RankAsc) Less(other interface{}) bool { - ro := other.(*RankAsc) +func (r RankAsc) Less(other interface{}) bool { + ro := other.(RankAsc) if r.Score < ro.Score { return true } @@ -60,6 +63,7 @@ func (r *RankAsc) Less(other interface{}) bool { if r.Subscore > ro.Subscore { return false } + return r.OwnerId.String() < ro.OwnerId.String() } @@ -69,8 +73,8 @@ type RankDesc struct { Subscore int64 } -func (r *RankDesc) Less(other interface{}) bool { - ro := other.(*RankDesc) +func (r RankDesc) Less(other interface{}) bool { + ro := other.(RankDesc) if ro.Score < r.Score { return true } @@ -88,8 +92,7 @@ func (r *RankDesc) Less(other interface{}) bool { type RankCache struct { sync.RWMutex - owners map[uuid.UUID]skiplist.Interface - cache *skiplist.SkipList + cache *skiplist.SkipList } type LocalLeaderboardRankCache struct { @@ -122,137 +125,44 @@ func NewLocalLeaderboardRankCache(ctx context.Context, startupLogger *zap.Logger skippedLeaderboards := make([]string, 0, 10) leaderboards := leaderboardCache.GetAllLeaderboards() cachedLeaderboards := make([]string, 0, len(leaderboards)) - for _, leaderboard := range leaderboards { - if _, ok := cache.blacklistIds[leaderboard.Id]; ok { - startupLogger.Debug("Skip caching leaderboard ranks", zap.String("leaderboard_id", leaderboard.Id)) - skippedLeaderboards = append(skippedLeaderboards, leaderboard.Id) - continue - } - - cachedLeaderboards = append(cachedLeaderboards, leaderboard.Id) - startupLogger.Debug("Caching leaderboard ranks", zap.String("leaderboard_id", leaderboard.Id)) - - // Current expiry for this leaderboard. - // This matches calculateTournamentDeadlines - var expiryUnix int64 - if leaderboard.ResetSchedule != nil { - expiryUnix = leaderboard.ResetSchedule.Next(nowTime).UTC().Unix() - if leaderboard.EndTime > 0 && expiryUnix > leaderboard.EndTime { - expiryUnix = leaderboard.EndTime - } - } else { - expiryUnix = leaderboard.EndTime - } - - if expiryUnix != 0 && expiryUnix <= nowTime.Unix() { - // Last scores for this leaderboard have expired, do not cache them. - continue - } - - // Prepare structure to receive rank data. - key := LeaderboardWithExpiry{LeaderboardId: leaderboard.Id, Expiry: expiryUnix} - cache.Lock() - rankCache, found := cache.cache[key] - if !found { - rankCache = &RankCache{ - owners: make(map[uuid.UUID]skiplist.Interface), - cache: skiplist.New(), - } - cache.cache[key] = rankCache - } - cache.Unlock() - - expiryTime := time.Unix(expiryUnix, 0).UTC() - - // Look up all active records for this leaderboard. - var score int64 - var subscore int64 - var ownerIDStr string - for { - ranks := make(map[uuid.UUID]skiplist.Interface, 10_000) - - query := "SELECT owner_id, score, subscore FROM leaderboard_record WHERE leaderboard_id = $1 AND expiry_time = $2" - params := []interface{}{leaderboard.Id, expiryTime} - if ownerIDStr != "" { - query += " AND (leaderboard_id, expiry_time, score, subscore, owner_id) > ($1, $2, $3, $4, $5)" - params = append(params, score, subscore, ownerIDStr) - } - // Does not need to be in leaderboard order, sorting is done in the rank cache structure anyway. - query += " ORDER BY leaderboard_id ASC, expiry_time ASC, score ASC, subscore ASC, owner_id ASC LIMIT 10000" - rows, err := db.QueryContext(ctx, query, params...) - if err != nil { - startupLogger.Error("Failed to cache leaderboard ranks", zap.String("leaderboard_id", leaderboard.Id), zap.Error(err)) - if err == context.Canceled { - // All further queries will fail, no need to continue looping through leaderboards. - return - } - break - } - - // Read score information. - for rows.Next() { - if err = rows.Scan(&ownerIDStr, &score, &subscore); err != nil { - _ = rows.Close() - startupLogger.Error("Failed to scan leaderboard rank data", zap.String("leaderboard_id", leaderboard.Id), zap.Error(err)) - break - } - ownerID, err := uuid.FromString(ownerIDStr) - if err != nil { - _ = rows.Close() - startupLogger.Error("Failed to parse scanned leaderboard rank data", zap.String("leaderboard_id", leaderboard.Id), zap.String("owner_id", ownerIDStr), zap.Error(err)) - break - } - - // Prepare new rank data for this leaderboard entry. - var rankData skiplist.Interface - if leaderboard.SortOrder == LeaderboardSortOrderDescending { - rankData = &RankDesc{ - OwnerId: ownerID, - Score: score, - Subscore: subscore, - } - } else { - rankData = &RankAsc{ - OwnerId: ownerID, - Score: score, - Subscore: subscore, - } - } - ranks[ownerID] = rankData - } - _ = rows.Close() - - rankCount := len(ranks) - if rankCount == 0 { - // Empty batch of results, end pagination for this leaderboard. - break - } - // Insert into rank cache in batches. - rankCache.Lock() - for ownerID, rankData := range ranks { - if _, alreadyInserted := rankCache.owners[ownerID]; alreadyInserted { - continue - } - rankCache.owners[ownerID] = rankData - rankCache.cache.Insert(rankData) - } - rankCache.Unlock() + wg := &sync.WaitGroup{} + wg.Add(config.RankCacheWorkers) + lbChan := make(chan *Leaderboard, config.RankCacheWorkers) + mu := &sync.Mutex{} + iter := uint64(0) + + // Start workers + for i := 0; i < config.RankCacheWorkers; i++ { + go leaderboardCacheInitWorker( + ctx, + wg, + &iter, + cache, + db, + startupLogger, + lbChan, + nowTime, + &skippedLeaderboards, + &cachedLeaderboards, + mu) + } - // Stop pagination when reaching the last (incomplete) page. - if rankCount < 10_000 { - break - } - } + for _, leaderboard := range leaderboards { + lbChan <- leaderboard } - startupLogger.Info("Leaderboard rank cache initialization completed successfully", zap.Strings("cached", cachedLeaderboards), zap.Strings("skipped", skippedLeaderboards)) + close(lbChan) + // Wait for workers + wg.Wait() + + startupLogger.Info("Leaderboard rank cache initialization completed successfully", zap.Strings("cached", cachedLeaderboards), zap.Strings("skipped", skippedLeaderboards), zap.Duration("duration", time.Since(nowTime))) }() return cache } -func (l *LocalLeaderboardRankCache) Get(leaderboardId string, expiryUnix int64, ownerID uuid.UUID) int64 { +func (l *LocalLeaderboardRankCache) Get(leaderboardId string, sortOrder int, score, subscore, expiryUnix int64, ownerID uuid.UUID) int64 { if l.blacklistAll { // If all rank caching is disabled. return 0 @@ -272,19 +182,15 @@ func (l *LocalLeaderboardRankCache) Get(leaderboardId string, expiryUnix int64, } // Find rank data for this owner. + rankData := newRank(sortOrder, score, subscore, ownerID) rankCache.RLock() - rankData, ok := rankCache.owners[ownerID] - if !ok { - rankCache.RUnlock() - return 0 - } rank := rankCache.cache.GetRank(rankData) rankCache.RUnlock() return int64(rank) } -func (l *LocalLeaderboardRankCache) Fill(leaderboardId string, expiryUnix int64, records []*api.LeaderboardRecord) { +func (l *LocalLeaderboardRankCache) Fill(leaderboardId string, sortOrder int, expiryUnix int64, records []*api.LeaderboardRecord) { if l.blacklistAll { // If all rank caching is disabled. return @@ -315,16 +221,13 @@ func (l *LocalLeaderboardRankCache) Fill(leaderboardId string, expiryUnix int64, if err != nil { continue } - rankData, ok := rankCache.owners[ownerID] - if !ok { - continue - } + rankData := newRank(sortOrder, record.Score, record.Subscore, ownerID) record.Rank = int64(rankCache.cache.GetRank(rankData)) } rankCache.RUnlock() } -func (l *LocalLeaderboardRankCache) Insert(leaderboardId string, expiryUnix int64, sortOrder int, ownerID uuid.UUID, score, subscore int64) int64 { +func (l *LocalLeaderboardRankCache) Insert(leaderboardId string, sortOrder int, score, subscore int64, oldScore, oldSubscore *int64, expiryUnix int64, ownerID uuid.UUID) int64 { if l.blacklistAll { // If all rank caching is disabled. return 0 @@ -341,8 +244,7 @@ func (l *LocalLeaderboardRankCache) Insert(leaderboardId string, expiryUnix int6 l.RUnlock() if !ok { newRankCache := &RankCache{ - owners: make(map[uuid.UUID]skiplist.Interface), - cache: skiplist.New(), + cache: skiplist.New(), } l.Lock() // Last check if rank map was created by another writer just after last read. @@ -355,27 +257,19 @@ func (l *LocalLeaderboardRankCache) Insert(leaderboardId string, expiryUnix int6 } // Prepare new rank data for this leaderboard entry. - var rankData skiplist.Interface - if sortOrder == LeaderboardSortOrderDescending { - rankData = &RankDesc{ - OwnerId: ownerID, - Score: score, - Subscore: subscore, - } - } else { - rankData = &RankAsc{ - OwnerId: ownerID, - Score: score, - Subscore: subscore, - } + rankData := newRank(sortOrder, score, subscore, ownerID) + var oldRankData skiplist.Interface + + // We need old scores in order to locate and the remove the existing record + if oldScore != nil && oldSubscore != nil { + oldRankData = newRank(sortOrder, *oldScore, *oldSubscore, ownerID) } // Check for and remove any previous rank entry, then insert the new rank data and get its rank. rankCache.Lock() - if oldRankData, ok := rankCache.owners[ownerID]; ok { + if oldRankData != nil { rankCache.cache.Delete(oldRankData) } - rankCache.owners[ownerID] = rankData rankCache.cache.Insert(rankData) rank := rankCache.cache.GetRank(rankData) rankCache.Unlock() @@ -383,7 +277,7 @@ func (l *LocalLeaderboardRankCache) Insert(leaderboardId string, expiryUnix int6 return int64(rank) } -func (l *LocalLeaderboardRankCache) Delete(leaderboardId string, expiryUnix int64, ownerID uuid.UUID) bool { +func (l *LocalLeaderboardRankCache) Delete(leaderboardId string, sortOrder int, score, subscore, expiryUnix int64, ownerID uuid.UUID) bool { if l.blacklistAll { // If all rank caching is disabled. return false @@ -405,13 +299,8 @@ func (l *LocalLeaderboardRankCache) Delete(leaderboardId string, expiryUnix int6 } // Remove any existing rank entry. + rankData := newRank(sortOrder, score, subscore, ownerID) rankCache.Lock() - rankData, ok := rankCache.owners[ownerID] - if !ok { - rankCache.Unlock() - return true - } - delete(rankCache.owners, ownerID) rankCache.cache.Delete(rankData) rankCache.Unlock() @@ -455,3 +344,166 @@ func (l *LocalLeaderboardRankCache) TrimExpired(nowUnix int64) bool { return true } + +func leaderboardCacheInitWorker( + ctx context.Context, + wg *sync.WaitGroup, + iter *uint64, + cache *LocalLeaderboardRankCache, + db *sql.DB, + startupLogger *zap.Logger, + ch <-chan *Leaderboard, + nowTime time.Time, + skippedLeaderboards *[]string, + cachedLeaderboards *[]string, + mu *sync.Mutex) { + + defer wg.Done() + + batchSize := uint64(10_000) + batchSizeStr := fmt.Sprintf("%d", batchSize) + + // Look up all active records for this leaderboard. + for leaderboard := range ch { + var score int64 + var subscore int64 + var ownerIDStr string + + if _, ok := cache.blacklistIds[leaderboard.Id]; ok { + startupLogger.Debug("Skip caching leaderboard ranks", zap.String("leaderboard_id", leaderboard.Id)) + + mu.Lock() + *skippedLeaderboards = append(*skippedLeaderboards, leaderboard.Id) + mu.Unlock() + + continue + } + + mu.Lock() + *cachedLeaderboards = append(*cachedLeaderboards, leaderboard.Id) + mu.Unlock() + + // Current expiry for this leaderboard. + // This matches calculateTournamentDeadlines + var expiryUnix int64 + if leaderboard.ResetSchedule != nil { + expiryUnix = leaderboard.ResetSchedule.Next(nowTime).UTC().Unix() + if leaderboard.EndTime > 0 && expiryUnix > leaderboard.EndTime { + expiryUnix = leaderboard.EndTime + } + } else { + expiryUnix = leaderboard.EndTime + } + + if expiryUnix != 0 && expiryUnix <= nowTime.Unix() { + // Last scores for this leaderboard have expired, do not cache them. + continue + } + + // Prepare structure to receive rank data. + key := LeaderboardWithExpiry{LeaderboardId: leaderboard.Id, Expiry: expiryUnix} + cache.Lock() + rankCache, found := cache.cache[key] + if !found { + rankCache = &RankCache{ + cache: skiplist.New(), + } + cache.cache[key] = rankCache + } + cache.Unlock() + + expiryTime := time.Unix(expiryUnix, 0).UTC() + startupLogger.Debug("Caching leaderboard ranks", + zap.String("leaderboard_id", leaderboard.Id)) + + for { + ranks := make(map[uuid.UUID]skiplist.Interface, batchSize) + + query := "SELECT owner_id, score, subscore FROM leaderboard_record WHERE leaderboard_id = $1 AND expiry_time = $2" + params := []interface{}{leaderboard.Id, expiryTime} + if ownerIDStr != "" { + query += " AND (leaderboard_id, expiry_time, score, subscore, owner_id) > ($1, $2, $3, $4, $5)" + params = append(params, score, subscore, ownerIDStr) + } + // Does not need to be in leaderboard order, sorting is done in the rank cache structure anyway. + query += " ORDER BY leaderboard_id ASC, expiry_time ASC, score ASC, subscore ASC, owner_id ASC LIMIT " + batchSizeStr + + rows, err := db.QueryContext(ctx, query, params...) + + if err != nil { + startupLogger.Error("Failed to cache leaderboard ranks", zap.String("leaderboard_id", leaderboard.Id), zap.Error(err)) + if err == context.Canceled { + // All further queries will fail, no need to continue looping through leaderboards. + return + } + + break + } + + // Read score information. + for rows.Next() { + if err = rows.Scan(&ownerIDStr, &score, &subscore); err != nil { + startupLogger.Error("Failed to scan leaderboard rank data", zap.String("leaderboard_id", leaderboard.Id), zap.Error(err)) + break + } + + ownerID, err := uuid.FromString(ownerIDStr) + if err != nil { + startupLogger.Error("Failed to parse scanned leaderboard rank data", zap.String("leaderboard_id", leaderboard.Id), zap.String("owner_id", ownerIDStr), zap.Error(err)) + break + } + + // Prepare new rank data for this leaderboard entry. + rankData := newRank(leaderboard.SortOrder, score, subscore, ownerID) + ranks[ownerID] = rankData + } + _ = rows.Close() + + // Explicitly run GC every N batches to minimize accumulating + // temporary cruft from db reading, parsing etc. + // Doing this more often can result in a smoother inuse memory curve + // but at the cost of increased CPU usage. + if atomic.AddUint64(iter, batchSize)%(batchSize*10) == 0 { + runtime.GC() + } + + rankCount := uint64(len(ranks)) + if rankCount == 0 { + // Empty batch of results, end pagination for this leaderboard. + break + } + + // Insert into rank cache in batches. + rankCache.Lock() + for _, rankData := range ranks { + if oldRankData := rankCache.cache.Find(rankData); oldRankData != nil { + continue + } + rankCache.cache.Insert(rankData) + } + rankCache.Unlock() + + // Stop pagination when reaching the last (incomplete) page. + if rankCount < batchSize { + break + } + } + } +} + +func newRank(sortOrder int, score, subscore int64, ownerID uuid.UUID) skiplist.Interface { + + if sortOrder == LeaderboardSortOrderDescending { + return RankDesc{ + OwnerId: ownerID, + Score: score, + Subscore: subscore, + } + } else { + return RankAsc{ + OwnerId: ownerID, + Score: score, + Subscore: subscore, + } + } +} diff --git a/server/leaderboard_rank_cache_test.go b/server/leaderboard_rank_cache_test.go index 4baa58496c..df92e969ee 100644 --- a/server/leaderboard_rank_cache_test.go +++ b/server/leaderboard_rank_cache_test.go @@ -35,17 +35,19 @@ func TestLocalLeaderboardRankCache_Insert_Ascending(t *testing.T) { u4 := uuid.Must(uuid.NewV4()) u5 := uuid.Must(uuid.NewV4()) - cache.Insert("lid", 0, LeaderboardSortOrderAscending, u3, 33, 34) - cache.Insert("lid", 0, LeaderboardSortOrderAscending, u2, 22, 23) - cache.Insert("lid", 0, LeaderboardSortOrderAscending, u4, 44, 45) - cache.Insert("lid", 0, LeaderboardSortOrderAscending, u1, 11, 12) - cache.Insert("lid", 0, LeaderboardSortOrderAscending, u5, 55, 56) - - assert.EqualValues(t, 1, cache.Get("lid", 0, u1)) - assert.EqualValues(t, 2, cache.Get("lid", 0, u2)) - assert.EqualValues(t, 3, cache.Get("lid", 0, u3)) - assert.EqualValues(t, 4, cache.Get("lid", 0, u4)) - assert.EqualValues(t, 5, cache.Get("lid", 0, u5)) + order := LeaderboardSortOrderAscending + + cache.Insert("lid", order, 33, 34, nil, nil, 0, u3) + cache.Insert("lid", order, 22, 23, nil, nil, 0, u2) + cache.Insert("lid", order, 44, 45, nil, nil, 0, u4) + cache.Insert("lid", order, 11, 12, nil, nil, 0, u1) + cache.Insert("lid", order, 55, 56, nil, nil, 0, u5) + + assert.EqualValues(t, 1, cache.Get("lid", order, 11, 12, 0, u1)) + assert.EqualValues(t, 2, cache.Get("lid", order, 22, 23, 0, u2)) + assert.EqualValues(t, 3, cache.Get("lid", order, 33, 34, 0, u3)) + assert.EqualValues(t, 4, cache.Get("lid", order, 44, 45, 0, u4)) + assert.EqualValues(t, 5, cache.Get("lid", order, 55, 56, 0, u5)) } func TestLocalLeaderboardRankCache_Insert_Descending(t *testing.T) { @@ -61,17 +63,19 @@ func TestLocalLeaderboardRankCache_Insert_Descending(t *testing.T) { u4 := uuid.Must(uuid.NewV4()) u5 := uuid.Must(uuid.NewV4()) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u3, 33, 34) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u2, 22, 23) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u4, 44, 45) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u1, 11, 12) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u5, 55, 56) - - assert.EqualValues(t, 1, cache.Get("lid", 0, u5)) - assert.EqualValues(t, 2, cache.Get("lid", 0, u4)) - assert.EqualValues(t, 3, cache.Get("lid", 0, u3)) - assert.EqualValues(t, 4, cache.Get("lid", 0, u2)) - assert.EqualValues(t, 5, cache.Get("lid", 0, u1)) + order := LeaderboardSortOrderDescending + + cache.Insert("lid", order, 33, 34, nil, nil, 0, u3) + cache.Insert("lid", order, 22, 23, nil, nil, 0, u2) + cache.Insert("lid", order, 44, 45, nil, nil, 0, u4) + cache.Insert("lid", order, 11, 12, nil, nil, 0, u1) + cache.Insert("lid", order, 55, 56, nil, nil, 0, u5) + + assert.EqualValues(t, 1, cache.Get("lid", order, 55, 56, 0, u5)) + assert.EqualValues(t, 2, cache.Get("lid", order, 44, 45, 0, u4)) + assert.EqualValues(t, 3, cache.Get("lid", order, 33, 34, 0, u3)) + assert.EqualValues(t, 4, cache.Get("lid", order, 22, 23, 0, u2)) + assert.EqualValues(t, 5, cache.Get("lid", order, 11, 12, 0, u1)) } func TestLocalLeaderboardRankCache_Insert_Existing(t *testing.T) { @@ -87,18 +91,23 @@ func TestLocalLeaderboardRankCache_Insert_Existing(t *testing.T) { u4 := uuid.Must(uuid.NewV4()) u5 := uuid.Must(uuid.NewV4()) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u3, 33, 34) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u2, 22, 23) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u4, 44, 45) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u1, 11, 12) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u5, 55, 56) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u2, 55, 57) - - assert.EqualValues(t, 1, cache.Get("lid", 0, u2)) - assert.EqualValues(t, 2, cache.Get("lid", 0, u5)) - assert.EqualValues(t, 3, cache.Get("lid", 0, u4)) - assert.EqualValues(t, 4, cache.Get("lid", 0, u3)) - assert.EqualValues(t, 5, cache.Get("lid", 0, u1)) + order := LeaderboardSortOrderDescending + + origScore, origSubscore := int64(22), int64(23) + overrideScore, overrideSubscore := int64(55), int64(57) + + cache.Insert("lid", order, 33, 34, nil, nil, 0, u3) + cache.Insert("lid", order, origScore, origSubscore, nil, nil, 0, u2) + cache.Insert("lid", order, 44, 45, nil, nil, 0, u4) + cache.Insert("lid", order, 11, 12, nil, nil, 0, u1) + cache.Insert("lid", order, 55, 56, nil, nil, 0, u5) + cache.Insert("lid", order, overrideScore, overrideSubscore, &origScore, &origSubscore, 0, u2) + + assert.EqualValues(t, 1, cache.Get("lid", order, overrideScore, overrideSubscore, 0, u2)) + assert.EqualValues(t, 2, cache.Get("lid", order, 55, 56, 0, u5)) + assert.EqualValues(t, 3, cache.Get("lid", order, 44, 45, 0, u4)) + assert.EqualValues(t, 4, cache.Get("lid", order, 33, 34, 0, u3)) + assert.EqualValues(t, 5, cache.Get("lid", order, 11, 12, 0, u1)) } func TestLocalLeaderboardRankCache_TrimExpired(t *testing.T) { @@ -114,25 +123,27 @@ func TestLocalLeaderboardRankCache_TrimExpired(t *testing.T) { u4 := uuid.Must(uuid.NewV4()) u5 := uuid.Must(uuid.NewV4()) - cache.Insert("lid", 1, LeaderboardSortOrderDescending, u3, 33, 34) - cache.Insert("lid", 1, LeaderboardSortOrderDescending, u2, 22, 23) - cache.Insert("lid", 1, LeaderboardSortOrderDescending, u4, 44, 45) - cache.Insert("lid", 1, LeaderboardSortOrderDescending, u1, 11, 12) - cache.Insert("lid", 1, LeaderboardSortOrderDescending, u5, 55, 56) + order := LeaderboardSortOrderDescending + + cache.Insert("lid", order, 33, 34, nil, nil, 1, u3) + cache.Insert("lid", order, 22, 23, nil, nil, 1, u2) + cache.Insert("lid", order, 44, 45, nil, nil, 1, u4) + cache.Insert("lid", order, 11, 12, nil, nil, 1, u1) + cache.Insert("lid", order, 55, 56, nil, nil, 1, u5) - assert.EqualValues(t, 1, cache.Get("lid", 1, u5)) - assert.EqualValues(t, 2, cache.Get("lid", 1, u4)) - assert.EqualValues(t, 3, cache.Get("lid", 1, u3)) - assert.EqualValues(t, 4, cache.Get("lid", 1, u2)) - assert.EqualValues(t, 5, cache.Get("lid", 1, u1)) + assert.EqualValues(t, 1, cache.Get("lid", order, 55, 56, 1, u5)) + assert.EqualValues(t, 2, cache.Get("lid", order, 44, 45, 1, u4)) + assert.EqualValues(t, 3, cache.Get("lid", order, 33, 34, 1, u3)) + assert.EqualValues(t, 4, cache.Get("lid", order, 22, 23, 1, u2)) + assert.EqualValues(t, 5, cache.Get("lid", order, 11, 12, 1, u1)) cache.TrimExpired(1) - assert.EqualValues(t, 0, cache.Get("lid", 1, u5)) - assert.EqualValues(t, 0, cache.Get("lid", 1, u4)) - assert.EqualValues(t, 0, cache.Get("lid", 1, u3)) - assert.EqualValues(t, 0, cache.Get("lid", 1, u2)) - assert.EqualValues(t, 0, cache.Get("lid", 1, u1)) + assert.EqualValues(t, 0, cache.Get("lid", order, 55, 56, 1, u5)) + assert.EqualValues(t, 0, cache.Get("lid", order, 44, 45, 1, u4)) + assert.EqualValues(t, 0, cache.Get("lid", order, 33, 34, 1, u3)) + assert.EqualValues(t, 0, cache.Get("lid", order, 22, 23, 1, u2)) + assert.EqualValues(t, 0, cache.Get("lid", order, 11, 12, 1, u1)) } func TestLocalLeaderboardRankCache_ExpirySeparation(t *testing.T) { @@ -148,23 +159,25 @@ func TestLocalLeaderboardRankCache_ExpirySeparation(t *testing.T) { u4 := uuid.Must(uuid.NewV4()) u5 := uuid.Must(uuid.NewV4()) - cache.Insert("lid", 1, LeaderboardSortOrderDescending, u3, 33, 34) - cache.Insert("lid", 1, LeaderboardSortOrderDescending, u2, 22, 23) - cache.Insert("lid", 1, LeaderboardSortOrderDescending, u4, 44, 45) - cache.Insert("lid", 1, LeaderboardSortOrderDescending, u1, 11, 12) - cache.Insert("lid", 1, LeaderboardSortOrderDescending, u5, 55, 56) - - assert.EqualValues(t, 1, cache.Get("lid", 1, u5)) - assert.EqualValues(t, 2, cache.Get("lid", 1, u4)) - assert.EqualValues(t, 3, cache.Get("lid", 1, u3)) - assert.EqualValues(t, 4, cache.Get("lid", 1, u2)) - assert.EqualValues(t, 5, cache.Get("lid", 1, u1)) - - assert.EqualValues(t, 0, cache.Get("lid", 2, u5)) - assert.EqualValues(t, 0, cache.Get("lid", 2, u4)) - assert.EqualValues(t, 0, cache.Get("lid", 2, u3)) - assert.EqualValues(t, 0, cache.Get("lid", 2, u2)) - assert.EqualValues(t, 0, cache.Get("lid", 2, u1)) + order := LeaderboardSortOrderDescending + + cache.Insert("lid", order, 33, 34, nil, nil, 1, u3) + cache.Insert("lid", order, 22, 23, nil, nil, 1, u2) + cache.Insert("lid", order, 44, 45, nil, nil, 1, u4) + cache.Insert("lid", order, 11, 12, nil, nil, 1, u1) + cache.Insert("lid", order, 55, 56, nil, nil, 1, u5) + + assert.EqualValues(t, 1, cache.Get("lid", order, 55, 56, 1, u5)) + assert.EqualValues(t, 2, cache.Get("lid", order, 44, 45, 1, u4)) + assert.EqualValues(t, 3, cache.Get("lid", order, 33, 34, 1, u3)) + assert.EqualValues(t, 4, cache.Get("lid", order, 22, 23, 1, u2)) + assert.EqualValues(t, 5, cache.Get("lid", order, 11, 12, 1, u1)) + + assert.EqualValues(t, 0, cache.Get("lid", order, 55, 56, 2, u5)) + assert.EqualValues(t, 0, cache.Get("lid", order, 44, 45, 2, u4)) + assert.EqualValues(t, 0, cache.Get("lid", order, 33, 34, 2, u3)) + assert.EqualValues(t, 0, cache.Get("lid", order, 22, 23, 2, u2)) + assert.EqualValues(t, 0, cache.Get("lid", order, 11, 12, 2, u1)) } func TestLocalLeaderboardRankCache_LeaderboardSeparation(t *testing.T) { @@ -180,23 +193,25 @@ func TestLocalLeaderboardRankCache_LeaderboardSeparation(t *testing.T) { u4 := uuid.Must(uuid.NewV4()) u5 := uuid.Must(uuid.NewV4()) - cache.Insert("lid", 1, LeaderboardSortOrderDescending, u3, 33, 34) - cache.Insert("lid", 1, LeaderboardSortOrderDescending, u2, 22, 23) - cache.Insert("lid", 1, LeaderboardSortOrderDescending, u4, 44, 45) - cache.Insert("lid", 1, LeaderboardSortOrderDescending, u1, 11, 12) - cache.Insert("lid", 1, LeaderboardSortOrderDescending, u5, 55, 56) - - assert.EqualValues(t, 1, cache.Get("lid", 1, u5)) - assert.EqualValues(t, 2, cache.Get("lid", 1, u4)) - assert.EqualValues(t, 3, cache.Get("lid", 1, u3)) - assert.EqualValues(t, 4, cache.Get("lid", 1, u2)) - assert.EqualValues(t, 5, cache.Get("lid", 1, u1)) - - assert.EqualValues(t, 0, cache.Get("foo", 1, u5)) - assert.EqualValues(t, 0, cache.Get("foo", 1, u4)) - assert.EqualValues(t, 0, cache.Get("foo", 1, u3)) - assert.EqualValues(t, 0, cache.Get("foo", 1, u2)) - assert.EqualValues(t, 0, cache.Get("foo", 1, u1)) + order := LeaderboardSortOrderDescending + + cache.Insert("lid", order, 33, 34, nil, nil, 1, u3) + cache.Insert("lid", order, 22, 23, nil, nil, 1, u2) + cache.Insert("lid", order, 44, 45, nil, nil, 1, u4) + cache.Insert("lid", order, 11, 12, nil, nil, 1, u1) + cache.Insert("lid", order, 55, 56, nil, nil, 1, u5) + + assert.EqualValues(t, 1, cache.Get("lid", order, 55, 56, 1, u5)) + assert.EqualValues(t, 2, cache.Get("lid", order, 44, 45, 1, u4)) + assert.EqualValues(t, 3, cache.Get("lid", order, 33, 34, 1, u3)) + assert.EqualValues(t, 4, cache.Get("lid", order, 22, 23, 1, u2)) + assert.EqualValues(t, 5, cache.Get("lid", order, 11, 12, 1, u1)) + + assert.EqualValues(t, 0, cache.Get("foo", order, 55, 56, 1, u5)) + assert.EqualValues(t, 0, cache.Get("foo", order, 44, 45, 1, u4)) + assert.EqualValues(t, 0, cache.Get("foo", order, 33, 34, 1, u3)) + assert.EqualValues(t, 0, cache.Get("foo", order, 22, 23, 1, u2)) + assert.EqualValues(t, 0, cache.Get("foo", order, 11, 12, 1, u1)) } func TestLocalLeaderboardRankCache_Delete(t *testing.T) { @@ -212,25 +227,27 @@ func TestLocalLeaderboardRankCache_Delete(t *testing.T) { u4 := uuid.Must(uuid.NewV4()) u5 := uuid.Must(uuid.NewV4()) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u3, 33, 34) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u2, 22, 23) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u4, 44, 45) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u1, 11, 12) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u5, 55, 56) - - assert.EqualValues(t, 1, cache.Get("lid", 0, u5)) - assert.EqualValues(t, 2, cache.Get("lid", 0, u4)) - assert.EqualValues(t, 3, cache.Get("lid", 0, u3)) - assert.EqualValues(t, 4, cache.Get("lid", 0, u2)) - assert.EqualValues(t, 5, cache.Get("lid", 0, u1)) - - cache.Delete("lid", 0, u4) - - assert.EqualValues(t, 1, cache.Get("lid", 0, u5)) - assert.EqualValues(t, 0, cache.Get("lid", 0, u4)) - assert.EqualValues(t, 2, cache.Get("lid", 0, u3)) - assert.EqualValues(t, 3, cache.Get("lid", 0, u2)) - assert.EqualValues(t, 4, cache.Get("lid", 0, u1)) + order := LeaderboardSortOrderDescending + + cache.Insert("lid", order, 33, 34, nil, nil, 0, u3) + cache.Insert("lid", order, 22, 23, nil, nil, 0, u2) + cache.Insert("lid", order, 44, 45, nil, nil, 0, u4) + cache.Insert("lid", order, 11, 12, nil, nil, 0, u1) + cache.Insert("lid", order, 55, 56, nil, nil, 0, u5) + + assert.EqualValues(t, 1, cache.Get("lid", order, 55, 56, 0, u5)) + assert.EqualValues(t, 2, cache.Get("lid", order, 44, 45, 0, u4)) + assert.EqualValues(t, 3, cache.Get("lid", order, 33, 34, 0, u3)) + assert.EqualValues(t, 4, cache.Get("lid", order, 22, 23, 0, u2)) + assert.EqualValues(t, 5, cache.Get("lid", order, 11, 12, 0, u1)) + + cache.Delete("lid", order, 44, 45, 0, u4) + + assert.EqualValues(t, 1, cache.Get("lid", order, 55, 56, 0, u5)) + assert.EqualValues(t, 0, cache.Get("lid", order, 44, 45, 0, u4)) + assert.EqualValues(t, 2, cache.Get("lid", order, 33, 34, 0, u3)) + assert.EqualValues(t, 3, cache.Get("lid", order, 22, 23, 0, u2)) + assert.EqualValues(t, 4, cache.Get("lid", order, 11, 12, 0, u1)) } func TestLocalLeaderboardRankCache_DeleteLeaderboard(t *testing.T) { @@ -246,25 +263,27 @@ func TestLocalLeaderboardRankCache_DeleteLeaderboard(t *testing.T) { u4 := uuid.Must(uuid.NewV4()) u5 := uuid.Must(uuid.NewV4()) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u3, 33, 34) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u2, 22, 23) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u4, 44, 45) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u1, 11, 12) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u5, 55, 56) + order := LeaderboardSortOrderDescending + + cache.Insert("lid", order, 33, 34, nil, nil, 0, u3) + cache.Insert("lid", order, 22, 23, nil, nil, 0, u2) + cache.Insert("lid", order, 44, 45, nil, nil, 0, u4) + cache.Insert("lid", order, 11, 12, nil, nil, 0, u1) + cache.Insert("lid", order, 55, 56, nil, nil, 0, u5) - assert.EqualValues(t, 1, cache.Get("lid", 0, u5)) - assert.EqualValues(t, 2, cache.Get("lid", 0, u4)) - assert.EqualValues(t, 3, cache.Get("lid", 0, u3)) - assert.EqualValues(t, 4, cache.Get("lid", 0, u2)) - assert.EqualValues(t, 5, cache.Get("lid", 0, u1)) + assert.EqualValues(t, 1, cache.Get("lid", order, 55, 56, 0, u5)) + assert.EqualValues(t, 2, cache.Get("lid", order, 44, 45, 0, u4)) + assert.EqualValues(t, 3, cache.Get("lid", order, 33, 34, 0, u3)) + assert.EqualValues(t, 4, cache.Get("lid", order, 22, 23, 0, u2)) + assert.EqualValues(t, 5, cache.Get("lid", order, 11, 12, 0, u1)) cache.DeleteLeaderboard("lid", 0) - assert.EqualValues(t, 0, cache.Get("lid", 0, u5)) - assert.EqualValues(t, 0, cache.Get("lid", 0, u4)) - assert.EqualValues(t, 0, cache.Get("lid", 0, u3)) - assert.EqualValues(t, 0, cache.Get("lid", 0, u2)) - assert.EqualValues(t, 0, cache.Get("lid", 0, u1)) + assert.EqualValues(t, 0, cache.Get("lid", order, 55, 56, 0, u5)) + assert.EqualValues(t, 0, cache.Get("lid", order, 44, 45, 0, u4)) + assert.EqualValues(t, 0, cache.Get("lid", order, 33, 34, 0, u3)) + assert.EqualValues(t, 0, cache.Get("lid", order, 22, 23, 0, u2)) + assert.EqualValues(t, 0, cache.Get("lid", order, 11, 12, 0, u1)) } func TestLocalLeaderboardRankCache_Fill(t *testing.T) { @@ -280,27 +299,29 @@ func TestLocalLeaderboardRankCache_Fill(t *testing.T) { u4 := uuid.Must(uuid.NewV4()) u5 := uuid.Must(uuid.NewV4()) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u3, 33, 34) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u2, 22, 23) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u4, 44, 45) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u1, 11, 12) - cache.Insert("lid", 0, LeaderboardSortOrderDescending, u5, 55, 56) + order := LeaderboardSortOrderDescending + + cache.Insert("lid", order, 33, 34, nil, nil, 0, u3) + cache.Insert("lid", order, 22, 23, nil, nil, 0, u2) + cache.Insert("lid", order, 44, 45, nil, nil, 0, u4) + cache.Insert("lid", order, 11, 12, nil, nil, 0, u1) + cache.Insert("lid", order, 55, 56, nil, nil, 0, u5) - assert.EqualValues(t, 1, cache.Get("lid", 0, u5)) - assert.EqualValues(t, 2, cache.Get("lid", 0, u4)) - assert.EqualValues(t, 3, cache.Get("lid", 0, u3)) - assert.EqualValues(t, 4, cache.Get("lid", 0, u2)) - assert.EqualValues(t, 5, cache.Get("lid", 0, u1)) + assert.EqualValues(t, 1, cache.Get("lid", order, 55, 56, 0, u5)) + assert.EqualValues(t, 2, cache.Get("lid", order, 44, 45, 0, u4)) + assert.EqualValues(t, 3, cache.Get("lid", order, 33, 34, 0, u3)) + assert.EqualValues(t, 4, cache.Get("lid", order, 22, 23, 0, u2)) + assert.EqualValues(t, 5, cache.Get("lid", order, 11, 12, 0, u1)) records := []*api.LeaderboardRecord{ - {OwnerId: u3.String()}, - {OwnerId: u1.String()}, - {OwnerId: u5.String()}, - {OwnerId: u2.String()}, - {OwnerId: u4.String()}, + {OwnerId: u3.String(), Score: 33, Subscore: 34}, + {OwnerId: u1.String(), Score: 11, Subscore: 12}, + {OwnerId: u5.String(), Score: 55, Subscore: 56}, + {OwnerId: u2.String(), Score: 22, Subscore: 23}, + {OwnerId: u4.String(), Score: 44, Subscore: 45}, } - cache.Fill("lid", 0, records) + cache.Fill("lid", order, 0, records) assert.EqualValues(t, 3, records[0].Rank) assert.EqualValues(t, 5, records[1].Rank) diff --git a/server/runtime_go_nakama.go b/server/runtime_go_nakama.go index 966e85f37c..eaad801380 100644 --- a/server/runtime_go_nakama.go +++ b/server/runtime_go_nakama.go @@ -540,7 +540,7 @@ func (n *RuntimeGoNakamaModule) AccountDeleteId(ctx context.Context, userID stri return errors.New("expects user ID to be a valid identifier") } - return DeleteAccount(ctx, n.logger, n.db, n.config, n.leaderboardRankCache, n.sessionRegistry, n.sessionCache, n.tracker, u, recorded) + return DeleteAccount(ctx, n.logger, n.db, n.config, n.leaderboardCache, n.leaderboardRankCache, n.sessionRegistry, n.sessionCache, n.tracker, u, recorded) } // @group accounts diff --git a/server/runtime_javascript_nakama.go b/server/runtime_javascript_nakama.go index 5c31ee1628..e2a8149298 100644 --- a/server/runtime_javascript_nakama.go +++ b/server/runtime_javascript_nakama.go @@ -1924,7 +1924,7 @@ func (n *runtimeJavascriptNakamaModule) accountDeleteId(r *goja.Runtime) func(go recorded = getJsBool(r, f.Argument(1)) } - if err := DeleteAccount(n.ctx, n.logger, n.db, n.config, n.rankCache, n.sessionRegistry, n.sessionCache, n.tracker, userID, recorded); err != nil { + if err := DeleteAccount(n.ctx, n.logger, n.db, n.config, n.leaderboardCache, n.rankCache, n.sessionRegistry, n.sessionCache, n.tracker, userID, recorded); err != nil { panic(r.NewGoError(fmt.Errorf("error while trying to delete account: %v", err.Error()))) } diff --git a/server/runtime_lua_nakama.go b/server/runtime_lua_nakama.go index 40c96e01b6..081bd5de6c 100644 --- a/server/runtime_lua_nakama.go +++ b/server/runtime_lua_nakama.go @@ -8996,7 +8996,7 @@ func (n *RuntimeLuaNakamaModule) accountDeleteId(l *lua.LState) int { recorded := l.OptBool(2, false) - if err := DeleteAccount(l.Context(), n.logger, n.db, n.config, n.rankCache, n.sessionRegistry, n.sessionCache, n.tracker, userID, recorded); err != nil { + if err := DeleteAccount(l.Context(), n.logger, n.db, n.config, n.leaderboardCache, n.rankCache, n.sessionRegistry, n.sessionCache, n.tracker, userID, recorded); err != nil { l.RaiseError("error while trying to delete account: %v", err.Error()) } diff --git a/server/runtime_test.go b/server/runtime_test.go index e3eded2762..1f074c78c7 100644 --- a/server/runtime_test.go +++ b/server/runtime_test.go @@ -61,7 +61,18 @@ print("Test Module Loaded") return test` ) +type testRuntimeData struct { + leaderboardCache LeaderboardCache + leaderboardRankCache LeaderboardRankCache +} + func runtimeWithModules(t *testing.T, modules map[string]string) (*Runtime, *RuntimeInfo, error) { + rt, info, _, err := runtimeWithModulesWithData(t, modules) + + return rt, info, err +} + +func runtimeWithModulesWithData(t *testing.T, modules map[string]string) (*Runtime, *RuntimeInfo, *testRuntimeData, error) { dir, err := os.MkdirTemp("", fmt.Sprintf("nakama_runtime_lua_test_%v", uuid.Must(uuid.NewV4()).String())) if err != nil { t.Fatalf("Failed initializing runtime modules tempdir: %s", err.Error()) @@ -77,7 +88,21 @@ func runtimeWithModules(t *testing.T, modules map[string]string) (*Runtime, *Run cfg := NewConfig(logger) cfg.Runtime.Path = dir - return NewRuntime(context.Background(), logger, logger, NewDB(t), protojsonMarshaler, protojsonUnmarshaler, cfg, "", nil, nil, nil, nil, nil, nil, nil, nil, nil, metrics, nil, &DummyMessageRouter{}) + ctx := context.Background() + db := NewDB(t) + lbCache := NewLocalLeaderboardCache(logger, logger, db) + lbRankCache := NewLocalLeaderboardRankCache( + ctx, logger, db, cfg.Leaderboard, lbCache) + lbSched := NewLocalLeaderboardScheduler(logger, db, cfg, lbCache, lbRankCache) + + data := &testRuntimeData{ + leaderboardCache: lbCache, + leaderboardRankCache: lbRankCache, + } + + rt, rtInfo, err := NewRuntime(ctx, logger, logger, db, protojsonMarshaler, protojsonUnmarshaler, cfg, "", nil, lbCache, lbRankCache, lbSched, nil, nil, nil, nil, nil, metrics, nil, &DummyMessageRouter{}) + + return rt, rtInfo, data, err } func TestRuntimeSampleScript(t *testing.T) { @@ -358,6 +383,8 @@ nakama.register_rpc(test.printWorld, "helloworld")`, apiServer := StartApiServer(logger, logger, db, protojsonMarshaler, protojsonUnmarshaler, cfg, "", nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, metrics, pipeline, runtime) defer apiServer.Stop() + WaitForSocket(nil, cfg) + payload := "\"Hello World\"" client := &http.Client{} request, _ := http.NewRequest("POST", "http://localhost:7350/v2/rpc/helloworld?http_key=defaulthttpkey", strings.NewReader(payload)) diff --git a/vendor/github.com/stretchr/testify/require/doc.go b/vendor/github.com/stretchr/testify/require/doc.go new file mode 100644 index 0000000000..169de39221 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/doc.go @@ -0,0 +1,28 @@ +// Package require implements the same assertions as the `assert` package but +// stops test execution when a test fails. +// +// Example Usage +// +// The following is a complete example using require in a standard test function: +// import ( +// "testing" +// "github.com/stretchr/testify/require" +// ) +// +// func TestSomething(t *testing.T) { +// +// var a string = "Hello" +// var b string = "Hello" +// +// require.Equal(t, a, b, "The two words should be the same.") +// +// } +// +// Assertions +// +// The `require` package have same global functions as in the `assert` package, +// but instead of returning a boolean result they call `t.FailNow()`. +// +// Every assertion function also takes an optional string message as the final argument, +// allowing custom error messages to be appended to the message the assertion method outputs. +package require diff --git a/vendor/github.com/stretchr/testify/require/forward_requirements.go b/vendor/github.com/stretchr/testify/require/forward_requirements.go new file mode 100644 index 0000000000..1dcb2338c4 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/forward_requirements.go @@ -0,0 +1,16 @@ +package require + +// Assertions provides assertion methods around the +// TestingT interface. +type Assertions struct { + t TestingT +} + +// New makes a new Assertions object for the specified TestingT. +func New(t TestingT) *Assertions { + return &Assertions{ + t: t, + } +} + +//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require_forward.go.tmpl -include-format-funcs" diff --git a/vendor/github.com/stretchr/testify/require/require.go b/vendor/github.com/stretchr/testify/require/require.go new file mode 100644 index 0000000000..880853f5a2 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require.go @@ -0,0 +1,1935 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND + */ + +package require + +import ( + assert "github.com/stretchr/testify/assert" + http "net/http" + url "net/url" + time "time" +) + +// Condition uses a Comparison to assert a complex condition. +func Condition(t TestingT, comp assert.Comparison, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Condition(t, comp, msgAndArgs...) { + return + } + t.FailNow() +} + +// Conditionf uses a Comparison to assert a complex condition. +func Conditionf(t TestingT, comp assert.Comparison, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Conditionf(t, comp, msg, args...) { + return + } + t.FailNow() +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Contains(t, "Hello World", "World") +// assert.Contains(t, ["Hello", "World"], "World") +// assert.Contains(t, {"Hello": "World"}, "Hello") +func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Contains(t, s, contains, msgAndArgs...) { + return + } + t.FailNow() +} + +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") +// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") +// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") +func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Containsf(t, s, contains, msg, args...) { + return + } + t.FailNow() +} + +// DirExists checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func DirExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.DirExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// DirExistsf checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func DirExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.DirExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) +func ElementsMatch(t TestingT, listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ElementsMatch(t, listA, listB, msgAndArgs...) { + return + } + t.FailNow() +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ElementsMatchf(t, listA, listB, msg, args...) { + return + } + t.FailNow() +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Empty(t, obj) +func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Empty(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Emptyf(t, obj, "error message %s", "formatted") +func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Emptyf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// Equal asserts that two objects are equal. +// +// assert.Equal(t, 123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equal(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Equal(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// assert.EqualError(t, err, expectedErrorString) +func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualError(t, theError, errString, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") +func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualErrorf(t, theError, errString, msg, args...) { + return + } + t.FailNow() +} + +// EqualValues asserts that two objects are equal or convertable to the same types +// and equal. +// +// assert.EqualValues(t, uint32(123), int32(123)) +func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualValues(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualValuesf asserts that two objects are equal or convertable to the same types +// and equal. +// +// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") +func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualValuesf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Equalf asserts that two objects are equal. +// +// assert.Equalf(t, 123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Equalf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err) { +// assert.Equal(t, expectedError, err) +// } +func Error(t TestingT, err error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Error(t, err, msgAndArgs...) { + return + } + t.FailNow() +} + +// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorAs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorAsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + +// ErrorContains asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// assert.ErrorContains(t, err, expectedErrorSubString) +func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorContains(t, theError, contains, msgAndArgs...) { + return + } + t.FailNow() +} + +// ErrorContainsf asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted") +func ErrorContainsf(t TestingT, theError error, contains string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorContainsf(t, theError, contains, msg, args...) { + return + } + t.FailNow() +} + +// ErrorIs asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func ErrorIs(t TestingT, err error, target error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorIs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// ErrorIsf asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorIsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Errorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func Errorf(t TestingT, err error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Errorf(t, err, msg, args...) { + return + } + t.FailNow() +} + +// Eventually asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) +func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Eventually(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// Eventuallyf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Eventuallyf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + +// Exactly asserts that two objects are equal in value and type. +// +// assert.Exactly(t, int32(123), int64(123)) +func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Exactly(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// Exactlyf asserts that two objects are equal in value and type. +// +// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted") +func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Exactlyf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Fail reports a failure through +func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Fail(t, failureMessage, msgAndArgs...) { + return + } + t.FailNow() +} + +// FailNow fails test +func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FailNow(t, failureMessage, msgAndArgs...) { + return + } + t.FailNow() +} + +// FailNowf fails test +func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FailNowf(t, failureMessage, msg, args...) { + return + } + t.FailNow() +} + +// Failf reports a failure through +func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Failf(t, failureMessage, msg, args...) { + return + } + t.FailNow() +} + +// False asserts that the specified value is false. +// +// assert.False(t, myBool) +func False(t TestingT, value bool, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.False(t, value, msgAndArgs...) { + return + } + t.FailNow() +} + +// Falsef asserts that the specified value is false. +// +// assert.Falsef(t, myBool, "error message %s", "formatted") +func Falsef(t TestingT, value bool, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Falsef(t, value, msg, args...) { + return + } + t.FailNow() +} + +// FileExists checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func FileExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FileExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// FileExistsf checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func FileExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FileExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// Greater asserts that the first element is greater than the second +// +// assert.Greater(t, 2, 1) +// assert.Greater(t, float64(2), float64(1)) +// assert.Greater(t, "b", "a") +func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Greater(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// GreaterOrEqual asserts that the first element is greater than or equal to the second +// +// assert.GreaterOrEqual(t, 2, 1) +// assert.GreaterOrEqual(t, 2, 2) +// assert.GreaterOrEqual(t, "b", "a") +// assert.GreaterOrEqual(t, "b", "b") +func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.GreaterOrEqual(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// GreaterOrEqualf asserts that the first element is greater than or equal to the second +// +// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted") +// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted") +// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted") +// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted") +func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.GreaterOrEqualf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// Greaterf asserts that the first element is greater than the second +// +// assert.Greaterf(t, 2, 1, "error message %s", "formatted") +// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted") +// assert.Greaterf(t, "b", "a", "error message %s", "formatted") +func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Greaterf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyContains(t, handler, method, url, values, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyContainsf(t, handler, method, url, values, str, msg, args...) { + return + } + t.FailNow() +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyNotContains(t, handler, method, url, values, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyNotContainsf(t, handler, method, url, values, str, msg, args...) { + return + } + t.FailNow() +} + +// HTTPError asserts that a specified handler returns an error status code. +// +// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPError(t, handler, method, url, values, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPErrorf(t, handler, method, url, values, msg, args...) { + return + } + t.FailNow() +} + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPRedirect(t, handler, method, url, values, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPRedirectf(t, handler, method, url, values, msg, args...) { + return + } + t.FailNow() +} + +// HTTPStatusCode asserts that a specified handler returns a specified status code. +// +// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501) +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPStatusCode(t, handler, method, url, values, statuscode, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPStatusCodef asserts that a specified handler returns a specified status code. +// +// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPStatusCodef(t, handler, method, url, values, statuscode, msg, args...) { + return + } + t.FailNow() +} + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPSuccess(t, handler, method, url, values, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPSuccessf(t, handler, method, url, values, msg, args...) { + return + } + t.FailNow() +} + +// Implements asserts that an object is implemented by the specified interface. +// +// assert.Implements(t, (*MyInterface)(nil), new(MyObject)) +func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Implements(t, interfaceObject, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// Implementsf asserts that an object is implemented by the specified interface. +// +// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Implementsf(t, interfaceObject, object, msg, args...) { + return + } + t.FailNow() +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// assert.InDelta(t, math.Pi, 22/7.0, 0.01) +func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDelta(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValues(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaMapValues(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaMapValuesf(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func InDeltaSlice(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaSlice(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaSlicef(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaf(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +func InEpsilon(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilon(t, expected, actual, epsilon, msgAndArgs...) { + return + } + t.FailNow() +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlice(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilonSlice(t, expected, actual, epsilon, msgAndArgs...) { + return + } + t.FailNow() +} + +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilonSlicef(t, expected, actual, epsilon, msg, args...) { + return + } + t.FailNow() +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilonf(t, expected, actual, epsilon, msg, args...) { + return + } + t.FailNow() +} + +// IsDecreasing asserts that the collection is decreasing +// +// assert.IsDecreasing(t, []int{2, 1, 0}) +// assert.IsDecreasing(t, []float{2, 1}) +// assert.IsDecreasing(t, []string{"b", "a"}) +func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsDecreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsDecreasingf asserts that the collection is decreasing +// +// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted") +// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted") +// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsDecreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// IsIncreasing asserts that the collection is increasing +// +// assert.IsIncreasing(t, []int{1, 2, 3}) +// assert.IsIncreasing(t, []float{1, 2}) +// assert.IsIncreasing(t, []string{"a", "b"}) +func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsIncreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsIncreasingf asserts that the collection is increasing +// +// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted") +// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted") +// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsIncreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// IsNonDecreasing asserts that the collection is not decreasing +// +// assert.IsNonDecreasing(t, []int{1, 1, 2}) +// assert.IsNonDecreasing(t, []float{1, 2}) +// assert.IsNonDecreasing(t, []string{"a", "b"}) +func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonDecreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsNonDecreasingf asserts that the collection is not decreasing +// +// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted") +// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted") +// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonDecreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// IsNonIncreasing asserts that the collection is not increasing +// +// assert.IsNonIncreasing(t, []int{2, 1, 1}) +// assert.IsNonIncreasing(t, []float{2, 1}) +// assert.IsNonIncreasing(t, []string{"b", "a"}) +func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonIncreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsNonIncreasingf asserts that the collection is not increasing +// +// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted") +// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted") +// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonIncreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// IsType asserts that the specified objects are of the same type. +func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsType(t, expectedType, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsTypef asserts that the specified objects are of the same type. +func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsTypef(t, expectedType, object, msg, args...) { + return + } + t.FailNow() +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.JSONEq(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// JSONEqf asserts that two JSON strings are equivalent. +// +// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.JSONEqf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// assert.Len(t, mySlice, 3) +func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Len(t, object, length, msgAndArgs...) { + return + } + t.FailNow() +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// assert.Lenf(t, mySlice, 3, "error message %s", "formatted") +func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Lenf(t, object, length, msg, args...) { + return + } + t.FailNow() +} + +// Less asserts that the first element is less than the second +// +// assert.Less(t, 1, 2) +// assert.Less(t, float64(1), float64(2)) +// assert.Less(t, "a", "b") +func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Less(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// LessOrEqual asserts that the first element is less than or equal to the second +// +// assert.LessOrEqual(t, 1, 2) +// assert.LessOrEqual(t, 2, 2) +// assert.LessOrEqual(t, "a", "b") +// assert.LessOrEqual(t, "b", "b") +func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.LessOrEqual(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// LessOrEqualf asserts that the first element is less than or equal to the second +// +// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted") +// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted") +// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted") +// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted") +func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.LessOrEqualf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// Lessf asserts that the first element is less than the second +// +// assert.Lessf(t, 1, 2, "error message %s", "formatted") +// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted") +// assert.Lessf(t, "a", "b", "error message %s", "formatted") +func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Lessf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// Negative asserts that the specified element is negative +// +// assert.Negative(t, -1) +// assert.Negative(t, -1.23) +func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Negative(t, e, msgAndArgs...) { + return + } + t.FailNow() +} + +// Negativef asserts that the specified element is negative +// +// assert.Negativef(t, -1, "error message %s", "formatted") +// assert.Negativef(t, -1.23, "error message %s", "formatted") +func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Negativef(t, e, msg, args...) { + return + } + t.FailNow() +} + +// Never asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// assert.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond) +func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Never(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// Neverf asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Neverf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + +// Nil asserts that the specified object is nil. +// +// assert.Nil(t, err) +func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Nil(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// Nilf asserts that the specified object is nil. +// +// assert.Nilf(t, err, "error message %s", "formatted") +func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Nilf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// NoDirExists checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func NoDirExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoDirExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// NoDirExistsf checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoDirExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoError(t, err) { +// assert.Equal(t, expectedObj, actualObj) +// } +func NoError(t TestingT, err error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoError(t, err, msgAndArgs...) { + return + } + t.FailNow() +} + +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoErrorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func NoErrorf(t TestingT, err error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoErrorf(t, err, msg, args...) { + return + } + t.FailNow() +} + +// NoFileExists checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func NoFileExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoFileExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// NoFileExistsf checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoFileExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContains(t, "Hello World", "Earth") +// assert.NotContains(t, ["Hello", "World"], "Earth") +// assert.NotContains(t, {"Hello": "World"}, "Earth") +func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotContains(t, s, contains, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") +func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotContainsf(t, s, contains, msg, args...) { + return + } + t.FailNow() +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmpty(t, obj) { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEmpty(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEmptyf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// NotEqual asserts that the specified values are NOT equal. +// +// assert.NotEqual(t, obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqual(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotEqualValues asserts that two objects are not equal even when converted to the same type +// +// assert.NotEqualValues(t, obj1, obj2) +func NotEqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqualValues(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotEqualValuesf asserts that two objects are not equal even when converted to the same type +// +// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted") +func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqualValuesf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqualf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// NotErrorIs asserts that at none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func NotErrorIs(t TestingT, err error, target error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorIs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotErrorIsf asserts that at none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorIsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + +// NotNil asserts that the specified object is not nil. +// +// assert.NotNil(t, err) +func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotNil(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotNilf asserts that the specified object is not nil. +// +// assert.NotNilf(t, err, "error message %s", "formatted") +func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotNilf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanics(t, func(){ RemainCalm() }) +func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotPanics(t, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") +func NotPanicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotPanicsf(t, f, msg, args...) { + return + } + t.FailNow() +} + +// NotRegexp asserts that a specified regexp does not match a string. +// +// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") +// assert.NotRegexp(t, "^start", "it's not starting") +func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotRegexp(t, rx, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") +// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") +func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotRegexpf(t, rx, str, msg, args...) { + return + } + t.FailNow() +} + +// NotSame asserts that two pointers do not reference the same object. +// +// assert.NotSame(t, ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func NotSame(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSame(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotSamef asserts that two pointers do not reference the same object. +// +// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSamef(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// NotSubset asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSubset(t, list, subset, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotSubsetf asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSubsetf(t, list, subset, msg, args...) { + return + } + t.FailNow() +} + +// NotZero asserts that i is not the zero value for its type. +func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotZero(t, i, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotZerof asserts that i is not the zero value for its type. +func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotZerof(t, i, msg, args...) { + return + } + t.FailNow() +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panics(t, func(){ GoCrazy() }) +func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Panics(t, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// PanicsWithError asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithError(t TestingT, errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithError(t, errString, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func PanicsWithErrorf(t TestingT, errString string, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithErrorf(t, errString, f, msg, args...) { + return + } + t.FailNow() +} + +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithValue(t TestingT, expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithValue(t, expected, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func PanicsWithValuef(t TestingT, expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithValuef(t, expected, f, msg, args...) { + return + } + t.FailNow() +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") +func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Panicsf(t, f, msg, args...) { + return + } + t.FailNow() +} + +// Positive asserts that the specified element is positive +// +// assert.Positive(t, 1) +// assert.Positive(t, 1.23) +func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Positive(t, e, msgAndArgs...) { + return + } + t.FailNow() +} + +// Positivef asserts that the specified element is positive +// +// assert.Positivef(t, 1, "error message %s", "formatted") +// assert.Positivef(t, 1.23, "error message %s", "formatted") +func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Positivef(t, e, msg, args...) { + return + } + t.FailNow() +} + +// Regexp asserts that a specified regexp matches a string. +// +// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") +// assert.Regexp(t, "start...$", "it's not starting") +func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Regexp(t, rx, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// Regexpf asserts that a specified regexp matches a string. +// +// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") +// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") +func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Regexpf(t, rx, str, msg, args...) { + return + } + t.FailNow() +} + +// Same asserts that two pointers reference the same object. +// +// assert.Same(t, ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func Same(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Same(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// Samef asserts that two pointers reference the same object. +// +// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Samef(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Subset asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Subset(t, list, subset, msgAndArgs...) { + return + } + t.FailNow() +} + +// Subsetf asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Subsetf(t, list, subset, msg, args...) { + return + } + t.FailNow() +} + +// True asserts that the specified value is true. +// +// assert.True(t, myBool) +func True(t TestingT, value bool, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.True(t, value, msgAndArgs...) { + return + } + t.FailNow() +} + +// Truef asserts that the specified value is true. +// +// assert.Truef(t, myBool, "error message %s", "formatted") +func Truef(t TestingT, value bool, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Truef(t, value, msg, args...) { + return + } + t.FailNow() +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) +func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinDuration(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinDurationf(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// WithinRange asserts that a time is within a time range (inclusive). +// +// assert.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +func WithinRange(t TestingT, actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinRange(t, actual, start, end, msgAndArgs...) { + return + } + t.FailNow() +} + +// WithinRangef asserts that a time is within a time range (inclusive). +// +// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") +func WithinRangef(t TestingT, actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinRangef(t, actual, start, end, msg, args...) { + return + } + t.FailNow() +} + +// YAMLEq asserts that two YAML strings are equivalent. +func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.YAMLEq(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// YAMLEqf asserts that two YAML strings are equivalent. +func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.YAMLEqf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Zero asserts that i is the zero value for its type. +func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Zero(t, i, msgAndArgs...) { + return + } + t.FailNow() +} + +// Zerof asserts that i is the zero value for its type. +func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Zerof(t, i, msg, args...) { + return + } + t.FailNow() +} diff --git a/vendor/github.com/stretchr/testify/require/require.go.tmpl b/vendor/github.com/stretchr/testify/require/require.go.tmpl new file mode 100644 index 0000000000..55e42ddebd --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require.go.tmpl @@ -0,0 +1,6 @@ +{{.Comment}} +func {{.DocInfo.Name}}(t TestingT, {{.Params}}) { + if h, ok := t.(tHelper); ok { h.Helper() } + if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return } + t.FailNow() +} diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go b/vendor/github.com/stretchr/testify/require/require_forward.go new file mode 100644 index 0000000000..960bf6f2ca --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require_forward.go @@ -0,0 +1,1515 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND + */ + +package require + +import ( + assert "github.com/stretchr/testify/assert" + http "net/http" + url "net/url" + time "time" +) + +// Condition uses a Comparison to assert a complex condition. +func (a *Assertions) Condition(comp assert.Comparison, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Condition(a.t, comp, msgAndArgs...) +} + +// Conditionf uses a Comparison to assert a complex condition. +func (a *Assertions) Conditionf(comp assert.Comparison, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Conditionf(a.t, comp, msg, args...) +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Contains("Hello World", "World") +// a.Contains(["Hello", "World"], "World") +// a.Contains({"Hello": "World"}, "Hello") +func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Contains(a.t, s, contains, msgAndArgs...) +} + +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Containsf("Hello World", "World", "error message %s", "formatted") +// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") +// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") +func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Containsf(a.t, s, contains, msg, args...) +} + +// DirExists checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + DirExists(a.t, path, msgAndArgs...) +} + +// DirExistsf checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + DirExistsf(a.t, path, msg, args...) +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2]) +func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ElementsMatchf(a.t, listA, listB, msg, args...) +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Empty(obj) +func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Empty(a.t, object, msgAndArgs...) +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Emptyf(obj, "error message %s", "formatted") +func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Emptyf(a.t, object, msg, args...) +} + +// Equal asserts that two objects are equal. +// +// a.Equal(123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Equal(a.t, expected, actual, msgAndArgs...) +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualError(err, expectedErrorString) +func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualError(a.t, theError, errString, msgAndArgs...) +} + +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") +func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualErrorf(a.t, theError, errString, msg, args...) +} + +// EqualValues asserts that two objects are equal or convertable to the same types +// and equal. +// +// a.EqualValues(uint32(123), int32(123)) +func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualValues(a.t, expected, actual, msgAndArgs...) +} + +// EqualValuesf asserts that two objects are equal or convertable to the same types +// and equal. +// +// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") +func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualValuesf(a.t, expected, actual, msg, args...) +} + +// Equalf asserts that two objects are equal. +// +// a.Equalf(123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Equalf(a.t, expected, actual, msg, args...) +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Error(err) { +// assert.Equal(t, expectedError, err) +// } +func (a *Assertions) Error(err error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Error(a.t, err, msgAndArgs...) +} + +// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func (a *Assertions) ErrorAs(err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorAs(a.t, err, target, msgAndArgs...) +} + +// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func (a *Assertions) ErrorAsf(err error, target interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorAsf(a.t, err, target, msg, args...) +} + +// ErrorContains asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// a.ErrorContains(err, expectedErrorSubString) +func (a *Assertions) ErrorContains(theError error, contains string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorContains(a.t, theError, contains, msgAndArgs...) +} + +// ErrorContainsf asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// a.ErrorContainsf(err, expectedErrorSubString, "error message %s", "formatted") +func (a *Assertions) ErrorContainsf(theError error, contains string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorContainsf(a.t, theError, contains, msg, args...) +} + +// ErrorIs asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) ErrorIs(err error, target error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorIs(a.t, err, target, msgAndArgs...) +} + +// ErrorIsf asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorIsf(a.t, err, target, msg, args...) +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Errorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func (a *Assertions) Errorf(err error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Errorf(a.t, err, msg, args...) +} + +// Eventually asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// a.Eventually(func() bool { return true; }, time.Second, 10*time.Millisecond) +func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Eventually(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// Eventuallyf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// a.Eventuallyf(func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func (a *Assertions) Eventuallyf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Eventuallyf(a.t, condition, waitFor, tick, msg, args...) +} + +// Exactly asserts that two objects are equal in value and type. +// +// a.Exactly(int32(123), int64(123)) +func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Exactly(a.t, expected, actual, msgAndArgs...) +} + +// Exactlyf asserts that two objects are equal in value and type. +// +// a.Exactlyf(int32(123), int64(123), "error message %s", "formatted") +func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Exactlyf(a.t, expected, actual, msg, args...) +} + +// Fail reports a failure through +func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Fail(a.t, failureMessage, msgAndArgs...) +} + +// FailNow fails test +func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FailNow(a.t, failureMessage, msgAndArgs...) +} + +// FailNowf fails test +func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FailNowf(a.t, failureMessage, msg, args...) +} + +// Failf reports a failure through +func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Failf(a.t, failureMessage, msg, args...) +} + +// False asserts that the specified value is false. +// +// a.False(myBool) +func (a *Assertions) False(value bool, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + False(a.t, value, msgAndArgs...) +} + +// Falsef asserts that the specified value is false. +// +// a.Falsef(myBool, "error message %s", "formatted") +func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Falsef(a.t, value, msg, args...) +} + +// FileExists checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FileExists(a.t, path, msgAndArgs...) +} + +// FileExistsf checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FileExistsf(a.t, path, msg, args...) +} + +// Greater asserts that the first element is greater than the second +// +// a.Greater(2, 1) +// a.Greater(float64(2), float64(1)) +// a.Greater("b", "a") +func (a *Assertions) Greater(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Greater(a.t, e1, e2, msgAndArgs...) +} + +// GreaterOrEqual asserts that the first element is greater than or equal to the second +// +// a.GreaterOrEqual(2, 1) +// a.GreaterOrEqual(2, 2) +// a.GreaterOrEqual("b", "a") +// a.GreaterOrEqual("b", "b") +func (a *Assertions) GreaterOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + GreaterOrEqual(a.t, e1, e2, msgAndArgs...) +} + +// GreaterOrEqualf asserts that the first element is greater than or equal to the second +// +// a.GreaterOrEqualf(2, 1, "error message %s", "formatted") +// a.GreaterOrEqualf(2, 2, "error message %s", "formatted") +// a.GreaterOrEqualf("b", "a", "error message %s", "formatted") +// a.GreaterOrEqualf("b", "b", "error message %s", "formatted") +func (a *Assertions) GreaterOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + GreaterOrEqualf(a.t, e1, e2, msg, args...) +} + +// Greaterf asserts that the first element is greater than the second +// +// a.Greaterf(2, 1, "error message %s", "formatted") +// a.Greaterf(float64(2), float64(1), "error message %s", "formatted") +// a.Greaterf("b", "a", "error message %s", "formatted") +func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Greaterf(a.t, e1, e2, msg, args...) +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...) +} + +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...) +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...) +} + +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...) +} + +// HTTPError asserts that a specified handler returns an error status code. +// +// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPError(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPErrorf(a.t, handler, method, url, values, msg, args...) +} + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPRedirectf(a.t, handler, method, url, values, msg, args...) +} + +// HTTPStatusCode asserts that a specified handler returns a specified status code. +// +// a.HTTPStatusCode(myHandler, "GET", "/notImplemented", nil, 501) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPStatusCode(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPStatusCode(a.t, handler, method, url, values, statuscode, msgAndArgs...) +} + +// HTTPStatusCodef asserts that a specified handler returns a specified status code. +// +// a.HTTPStatusCodef(myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPStatusCodef(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPStatusCodef(a.t, handler, method, url, values, statuscode, msg, args...) +} + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPSuccessf(a.t, handler, method, url, values, msg, args...) +} + +// Implements asserts that an object is implemented by the specified interface. +// +// a.Implements((*MyInterface)(nil), new(MyObject)) +func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Implements(a.t, interfaceObject, object, msgAndArgs...) +} + +// Implementsf asserts that an object is implemented by the specified interface. +// +// a.Implementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Implementsf(a.t, interfaceObject, object, msg, args...) +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// a.InDelta(math.Pi, 22/7.0, 0.01) +func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDelta(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaSlicef(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// a.InDeltaf(math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaf(a.t, expected, actual, delta, msg, args...) +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...) +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonf(a.t, expected, actual, epsilon, msg, args...) +} + +// IsDecreasing asserts that the collection is decreasing +// +// a.IsDecreasing([]int{2, 1, 0}) +// a.IsDecreasing([]float{2, 1}) +// a.IsDecreasing([]string{"b", "a"}) +func (a *Assertions) IsDecreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsDecreasing(a.t, object, msgAndArgs...) +} + +// IsDecreasingf asserts that the collection is decreasing +// +// a.IsDecreasingf([]int{2, 1, 0}, "error message %s", "formatted") +// a.IsDecreasingf([]float{2, 1}, "error message %s", "formatted") +// a.IsDecreasingf([]string{"b", "a"}, "error message %s", "formatted") +func (a *Assertions) IsDecreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsDecreasingf(a.t, object, msg, args...) +} + +// IsIncreasing asserts that the collection is increasing +// +// a.IsIncreasing([]int{1, 2, 3}) +// a.IsIncreasing([]float{1, 2}) +// a.IsIncreasing([]string{"a", "b"}) +func (a *Assertions) IsIncreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsIncreasing(a.t, object, msgAndArgs...) +} + +// IsIncreasingf asserts that the collection is increasing +// +// a.IsIncreasingf([]int{1, 2, 3}, "error message %s", "formatted") +// a.IsIncreasingf([]float{1, 2}, "error message %s", "formatted") +// a.IsIncreasingf([]string{"a", "b"}, "error message %s", "formatted") +func (a *Assertions) IsIncreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsIncreasingf(a.t, object, msg, args...) +} + +// IsNonDecreasing asserts that the collection is not decreasing +// +// a.IsNonDecreasing([]int{1, 1, 2}) +// a.IsNonDecreasing([]float{1, 2}) +// a.IsNonDecreasing([]string{"a", "b"}) +func (a *Assertions) IsNonDecreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonDecreasing(a.t, object, msgAndArgs...) +} + +// IsNonDecreasingf asserts that the collection is not decreasing +// +// a.IsNonDecreasingf([]int{1, 1, 2}, "error message %s", "formatted") +// a.IsNonDecreasingf([]float{1, 2}, "error message %s", "formatted") +// a.IsNonDecreasingf([]string{"a", "b"}, "error message %s", "formatted") +func (a *Assertions) IsNonDecreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonDecreasingf(a.t, object, msg, args...) +} + +// IsNonIncreasing asserts that the collection is not increasing +// +// a.IsNonIncreasing([]int{2, 1, 1}) +// a.IsNonIncreasing([]float{2, 1}) +// a.IsNonIncreasing([]string{"b", "a"}) +func (a *Assertions) IsNonIncreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonIncreasing(a.t, object, msgAndArgs...) +} + +// IsNonIncreasingf asserts that the collection is not increasing +// +// a.IsNonIncreasingf([]int{2, 1, 1}, "error message %s", "formatted") +// a.IsNonIncreasingf([]float{2, 1}, "error message %s", "formatted") +// a.IsNonIncreasingf([]string{"b", "a"}, "error message %s", "formatted") +func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonIncreasingf(a.t, object, msg, args...) +} + +// IsType asserts that the specified objects are of the same type. +func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsType(a.t, expectedType, object, msgAndArgs...) +} + +// IsTypef asserts that the specified objects are of the same type. +func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsTypef(a.t, expectedType, object, msg, args...) +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + JSONEq(a.t, expected, actual, msgAndArgs...) +} + +// JSONEqf asserts that two JSON strings are equivalent. +// +// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + JSONEqf(a.t, expected, actual, msg, args...) +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// a.Len(mySlice, 3) +func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Len(a.t, object, length, msgAndArgs...) +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// a.Lenf(mySlice, 3, "error message %s", "formatted") +func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Lenf(a.t, object, length, msg, args...) +} + +// Less asserts that the first element is less than the second +// +// a.Less(1, 2) +// a.Less(float64(1), float64(2)) +// a.Less("a", "b") +func (a *Assertions) Less(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Less(a.t, e1, e2, msgAndArgs...) +} + +// LessOrEqual asserts that the first element is less than or equal to the second +// +// a.LessOrEqual(1, 2) +// a.LessOrEqual(2, 2) +// a.LessOrEqual("a", "b") +// a.LessOrEqual("b", "b") +func (a *Assertions) LessOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + LessOrEqual(a.t, e1, e2, msgAndArgs...) +} + +// LessOrEqualf asserts that the first element is less than or equal to the second +// +// a.LessOrEqualf(1, 2, "error message %s", "formatted") +// a.LessOrEqualf(2, 2, "error message %s", "formatted") +// a.LessOrEqualf("a", "b", "error message %s", "formatted") +// a.LessOrEqualf("b", "b", "error message %s", "formatted") +func (a *Assertions) LessOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + LessOrEqualf(a.t, e1, e2, msg, args...) +} + +// Lessf asserts that the first element is less than the second +// +// a.Lessf(1, 2, "error message %s", "formatted") +// a.Lessf(float64(1), float64(2), "error message %s", "formatted") +// a.Lessf("a", "b", "error message %s", "formatted") +func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Lessf(a.t, e1, e2, msg, args...) +} + +// Negative asserts that the specified element is negative +// +// a.Negative(-1) +// a.Negative(-1.23) +func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Negative(a.t, e, msgAndArgs...) +} + +// Negativef asserts that the specified element is negative +// +// a.Negativef(-1, "error message %s", "formatted") +// a.Negativef(-1.23, "error message %s", "formatted") +func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Negativef(a.t, e, msg, args...) +} + +// Never asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// a.Never(func() bool { return false; }, time.Second, 10*time.Millisecond) +func (a *Assertions) Never(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Never(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// Neverf asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// a.Neverf(func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func (a *Assertions) Neverf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Neverf(a.t, condition, waitFor, tick, msg, args...) +} + +// Nil asserts that the specified object is nil. +// +// a.Nil(err) +func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Nil(a.t, object, msgAndArgs...) +} + +// Nilf asserts that the specified object is nil. +// +// a.Nilf(err, "error message %s", "formatted") +func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Nilf(a.t, object, msg, args...) +} + +// NoDirExists checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func (a *Assertions) NoDirExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoDirExists(a.t, path, msgAndArgs...) +} + +// NoDirExistsf checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func (a *Assertions) NoDirExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoDirExistsf(a.t, path, msg, args...) +} + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoError(err) { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoError(a.t, err, msgAndArgs...) +} + +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoErrorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoErrorf(a.t, err, msg, args...) +} + +// NoFileExists checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func (a *Assertions) NoFileExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoFileExists(a.t, path, msgAndArgs...) +} + +// NoFileExistsf checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func (a *Assertions) NoFileExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoFileExistsf(a.t, path, msg, args...) +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContains("Hello World", "Earth") +// a.NotContains(["Hello", "World"], "Earth") +// a.NotContains({"Hello": "World"}, "Earth") +func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotContains(a.t, s, contains, msgAndArgs...) +} + +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") +// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") +// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") +func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotContainsf(a.t, s, contains, msg, args...) +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmpty(obj) { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEmpty(a.t, object, msgAndArgs...) +} + +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmptyf(obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEmptyf(a.t, object, msg, args...) +} + +// NotEqual asserts that the specified values are NOT equal. +// +// a.NotEqual(obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqual(a.t, expected, actual, msgAndArgs...) +} + +// NotEqualValues asserts that two objects are not equal even when converted to the same type +// +// a.NotEqualValues(obj1, obj2) +func (a *Assertions) NotEqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualValues(a.t, expected, actual, msgAndArgs...) +} + +// NotEqualValuesf asserts that two objects are not equal even when converted to the same type +// +// a.NotEqualValuesf(obj1, obj2, "error message %s", "formatted") +func (a *Assertions) NotEqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualValuesf(a.t, expected, actual, msg, args...) +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// a.NotEqualf(obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualf(a.t, expected, actual, msg, args...) +} + +// NotErrorIs asserts that at none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorIs(a.t, err, target, msgAndArgs...) +} + +// NotErrorIsf asserts that at none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorIsf(a.t, err, target, msg, args...) +} + +// NotNil asserts that the specified object is not nil. +// +// a.NotNil(err) +func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotNil(a.t, object, msgAndArgs...) +} + +// NotNilf asserts that the specified object is not nil. +// +// a.NotNilf(err, "error message %s", "formatted") +func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotNilf(a.t, object, msg, args...) +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanics(func(){ RemainCalm() }) +func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotPanics(a.t, f, msgAndArgs...) +} + +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") +func (a *Assertions) NotPanicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotPanicsf(a.t, f, msg, args...) +} + +// NotRegexp asserts that a specified regexp does not match a string. +// +// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") +// a.NotRegexp("^start", "it's not starting") +func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotRegexp(a.t, rx, str, msgAndArgs...) +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// a.NotRegexpf(regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") +// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") +func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotRegexpf(a.t, rx, str, msg, args...) +} + +// NotSame asserts that two pointers do not reference the same object. +// +// a.NotSame(ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) NotSame(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSame(a.t, expected, actual, msgAndArgs...) +} + +// NotSamef asserts that two pointers do not reference the same object. +// +// a.NotSamef(ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSamef(a.t, expected, actual, msg, args...) +} + +// NotSubset asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSubset(a.t, list, subset, msgAndArgs...) +} + +// NotSubsetf asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSubsetf(a.t, list, subset, msg, args...) +} + +// NotZero asserts that i is not the zero value for its type. +func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotZero(a.t, i, msgAndArgs...) +} + +// NotZerof asserts that i is not the zero value for its type. +func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotZerof(a.t, i, msg, args...) +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panics(func(){ GoCrazy() }) +func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Panics(a.t, f, msgAndArgs...) +} + +// PanicsWithError asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// a.PanicsWithError("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithError(errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithError(a.t, errString, f, msgAndArgs...) +} + +// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// a.PanicsWithErrorf("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithErrorf(errString string, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithErrorf(a.t, errString, f, msg, args...) +} + +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValue("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithValue(expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithValue(a.t, expected, f, msgAndArgs...) +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithValuef(expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithValuef(a.t, expected, f, msg, args...) +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) Panicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Panicsf(a.t, f, msg, args...) +} + +// Positive asserts that the specified element is positive +// +// a.Positive(1) +// a.Positive(1.23) +func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Positive(a.t, e, msgAndArgs...) +} + +// Positivef asserts that the specified element is positive +// +// a.Positivef(1, "error message %s", "formatted") +// a.Positivef(1.23, "error message %s", "formatted") +func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Positivef(a.t, e, msg, args...) +} + +// Regexp asserts that a specified regexp matches a string. +// +// a.Regexp(regexp.MustCompile("start"), "it's starting") +// a.Regexp("start...$", "it's not starting") +func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Regexp(a.t, rx, str, msgAndArgs...) +} + +// Regexpf asserts that a specified regexp matches a string. +// +// a.Regexpf(regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") +// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") +func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Regexpf(a.t, rx, str, msg, args...) +} + +// Same asserts that two pointers reference the same object. +// +// a.Same(ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) Same(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Same(a.t, expected, actual, msgAndArgs...) +} + +// Samef asserts that two pointers reference the same object. +// +// a.Samef(ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Samef(a.t, expected, actual, msg, args...) +} + +// Subset asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Subset(a.t, list, subset, msgAndArgs...) +} + +// Subsetf asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Subsetf(a.t, list, subset, msg, args...) +} + +// True asserts that the specified value is true. +// +// a.True(myBool) +func (a *Assertions) True(value bool, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + True(a.t, value, msgAndArgs...) +} + +// Truef asserts that the specified value is true. +// +// a.Truef(myBool, "error message %s", "formatted") +func (a *Assertions) Truef(value bool, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Truef(a.t, value, msg, args...) +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second) +func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinDuration(a.t, expected, actual, delta, msgAndArgs...) +} + +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinDurationf(a.t, expected, actual, delta, msg, args...) +} + +// WithinRange asserts that a time is within a time range (inclusive). +// +// a.WithinRange(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +func (a *Assertions) WithinRange(actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinRange(a.t, actual, start, end, msgAndArgs...) +} + +// WithinRangef asserts that a time is within a time range (inclusive). +// +// a.WithinRangef(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") +func (a *Assertions) WithinRangef(actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinRangef(a.t, actual, start, end, msg, args...) +} + +// YAMLEq asserts that two YAML strings are equivalent. +func (a *Assertions) YAMLEq(expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + YAMLEq(a.t, expected, actual, msgAndArgs...) +} + +// YAMLEqf asserts that two YAML strings are equivalent. +func (a *Assertions) YAMLEqf(expected string, actual string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + YAMLEqf(a.t, expected, actual, msg, args...) +} + +// Zero asserts that i is the zero value for its type. +func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Zero(a.t, i, msgAndArgs...) +} + +// Zerof asserts that i is the zero value for its type. +func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Zerof(a.t, i, msg, args...) +} diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl new file mode 100644 index 0000000000..54124df1d3 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl @@ -0,0 +1,5 @@ +{{.CommentWithoutT "a"}} +func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) { + if h, ok := a.t.(tHelper); ok { h.Helper() } + {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) +} diff --git a/vendor/github.com/stretchr/testify/require/requirements.go b/vendor/github.com/stretchr/testify/require/requirements.go new file mode 100644 index 0000000000..91772dfeb9 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/requirements.go @@ -0,0 +1,29 @@ +package require + +// TestingT is an interface wrapper around *testing.T +type TestingT interface { + Errorf(format string, args ...interface{}) + FailNow() +} + +type tHelper interface { + Helper() +} + +// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful +// for table driven tests. +type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{}) + +// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful +// for table driven tests. +type ValueAssertionFunc func(TestingT, interface{}, ...interface{}) + +// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful +// for table driven tests. +type BoolAssertionFunc func(TestingT, bool, ...interface{}) + +// ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful +// for table driven tests. +type ErrorAssertionFunc func(TestingT, error, ...interface{}) + +//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require.go.tmpl -include-format-funcs" diff --git a/vendor/modules.txt b/vendor/modules.txt index 57dde3cbdb..0d1c8a87e9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -228,6 +228,7 @@ github.com/rubenv/sql-migrate/sqlparse # github.com/stretchr/testify v1.8.0 ## explicit; go 1.13 github.com/stretchr/testify/assert +github.com/stretchr/testify/require # github.com/twmb/murmur3 v1.1.5 ## explicit; go 1.11 github.com/twmb/murmur3