gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 0245c606d77c8b99833ccc2c0923a298fb482236
parent bee8458a2d12bdd42079fcb2c4ca88ebeafe305b
Author: tobi <31960611+tsmethurst@users.noreply.github.com>
Date:   Wed, 31 Aug 2022 17:31:21 +0200

[chore] Test fixes (#788)

* use 'test' value for testrig storage backend

* update test dependency

* add WaitFor func in testrig

* use WaitFor function instead of time.Sleep

* tidy up tests

* make SentMessages a sync.map

* go fmt
Diffstat:
Mgo.mod | 4++--
Mgo.sum | 7+++++--
Minternal/api/client/admin/mediacleanup_test.go | 38+++++++++++++++++++-------------------
Minternal/api/s2s/user/inboxpost_test.go | 17+++++++++--------
Minternal/api/s2s/user/outboxget_test.go | 11+----------
Minternal/api/s2s/user/repliesget_test.go | 11+----------
Minternal/api/s2s/user/statusget_test.go | 24++----------------------
Minternal/api/s2s/user/userget_test.go | 38++++++++++++++------------------------
Minternal/federation/dereferencing/media_test.go | 12+++++-------
Minternal/federation/federatingactor_test.go | 20++++++++++++++++----
Minternal/media/manager_test.go | 10+++++-----
Minternal/processing/account_test.go | 43+++++++++++++++++++++++++------------------
Minternal/processing/followrequest_test.go | 39+++++++++++++++++++++++++++++++--------
Minternal/processing/fromfederator_test.go | 51++++++++++++++++++++++++++++++++-------------------
Minternal/processing/media/getfile_test.go | 23+++++++++++++++--------
Minternal/trans/trans_test.go | 4++--
Mtestrig/config.go | 15+++++++--------
Mtestrig/transportcontroller.go | 15+++++++++++----
Mtestrig/util.go | 25+++++++++++++++++++++++++
Mvendor/github.com/stretchr/testify/assert/assertion_compare.go | 24+++++++++++++++++++++++-
Mvendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go | 2+-
Mvendor/github.com/stretchr/testify/assert/assertion_format.go | 10++++++++++
Mvendor/github.com/stretchr/testify/assert/assertion_forward.go | 20++++++++++++++++++++
Mvendor/github.com/stretchr/testify/assert/assertions.go | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mvendor/github.com/stretchr/testify/require/require.go | 26++++++++++++++++++++++++++
Mvendor/github.com/stretchr/testify/require/require_forward.go | 20++++++++++++++++++++
Mvendor/github.com/stretchr/testify/suite/suite.go | 25+++++++++++++++++++++----
Mvendor/gopkg.in/yaml.v3/decode.go | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mvendor/gopkg.in/yaml.v3/parserc.go | 11++++++++++-
Mvendor/modules.txt | 4++--
30 files changed, 492 insertions(+), 213 deletions(-)

diff --git a/go.mod b/go.mod @@ -37,7 +37,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 github.com/spf13/cobra v1.4.0 github.com/spf13/viper v1.11.0 - github.com/stretchr/testify v1.7.1 + github.com/stretchr/testify v1.8.0 github.com/superseriousbusiness/activity v1.1.0-gts github.com/superseriousbusiness/exif-terminator v0.4.0 github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB @@ -138,7 +138,7 @@ require ( gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/uint128 v1.2.0 // indirect modernc.org/cc/v3 v3.36.0 // indirect modernc.org/ccgo/v3 v3.16.8 // indirect diff --git a/go.sum b/go.sum @@ -499,14 +499,16 @@ github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUs github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/superseriousbusiness/activity v1.1.0-gts h1:BSnMzs/84s0Zme7BngE9iJAHV7g1Bv1nhLCP0aJtU3I= @@ -987,8 +989,9 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/api/client/admin/mediacleanup_test.go b/internal/api/client/admin/mediacleanup_test.go @@ -27,6 +27,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/api/client/admin" + "github.com/superseriousbusiness/gotosocial/testrig" ) type MediaCleanupTestSuite struct { @@ -47,15 +48,15 @@ func (suite *MediaCleanupTestSuite) TestMediaCleanup() { // we should have OK because our request was valid suite.Equal(http.StatusOK, recorder.Code) - // Wait for async task to finish - time.Sleep(1 * time.Second) - - // Get media we prunes - prunedAttachment, err := suite.db.GetAttachmentByID(context.Background(), testAttachment.ID) - suite.NoError(err) - - // the media should no longer be cached - suite.False(*prunedAttachment.Cached) + // the attachment should be updated in the database + if !testrig.WaitFor(func() bool { + if prunedAttachment, _ := suite.db.GetAttachmentByID(context.Background(), testAttachment.ID); prunedAttachment != nil { + return !*prunedAttachment.Cached + } + return false + }) { + suite.FailNow("timed out waiting for attachment to be pruned") + } } func (suite *MediaCleanupTestSuite) TestMediaCleanupNoArg() { @@ -73,15 +74,14 @@ func (suite *MediaCleanupTestSuite) TestMediaCleanupNoArg() { // we should have OK because our request was valid suite.Equal(http.StatusOK, recorder.Code) - // Wait for async task to finish - time.Sleep(1 * time.Second) - - // Get media we prunes - prunedAttachment, err := suite.db.GetAttachmentByID(context.Background(), testAttachment.ID) - suite.NoError(err) - - // the media should no longer be cached - suite.False(*prunedAttachment.Cached) + if !testrig.WaitFor(func() bool { + if prunedAttachment, _ := suite.db.GetAttachmentByID(context.Background(), testAttachment.ID); prunedAttachment != nil { + return !*prunedAttachment.Cached + } + return false + }) { + suite.FailNow("timed out waiting for attachment to be pruned") + } } func (suite *MediaCleanupTestSuite) TestMediaCleanupNotOldEnough() { @@ -101,7 +101,7 @@ func (suite *MediaCleanupTestSuite) TestMediaCleanupNotOldEnough() { // Wait for async task to finish time.Sleep(1 * time.Second) - // Get media we prunes + // Get media we pruned prunedAttachment, err := suite.db.GetAttachmentByID(context.Background(), testAttachment.ID) suite.NoError(err) diff --git a/internal/api/s2s/user/inboxpost_test.go b/internal/api/s2s/user/inboxpost_test.go @@ -444,14 +444,15 @@ func (suite *InboxPostTestSuite) TestPostDelete() { suite.Empty(b) suite.Equal(http.StatusOK, result.StatusCode) - // sleep for a sec so side effects can process in the background - time.Sleep(2 * time.Second) - - // local account 2 blocked foss_satan, that block should be gone now - testBlock := suite.testBlocks["local_account_2_block_remote_account_1"] - dbBlock := &gtsmodel.Block{} - err = suite.db.GetByID(ctx, testBlock.ID, dbBlock) - suite.ErrorIs(err, db.ErrNoEntries) + if !testrig.WaitFor(func() bool { + // local account 2 blocked foss_satan, that block should be gone now + testBlock := suite.testBlocks["local_account_2_block_remote_account_1"] + dbBlock := &gtsmodel.Block{} + err = suite.db.GetByID(ctx, testBlock.ID, dbBlock) + return suite.ErrorIs(err, db.ErrNoEntries) + }) { + suite.FailNow("timed out waiting for block to be removed") + } // no statuses from foss satan should be left in the database dbStatuses, err := suite.db.GetAccountStatuses(ctx, deletedAccount.ID, 0, false, false, "", "", false, false, false) diff --git a/internal/api/s2s/user/outboxget_test.go b/internal/api/s2s/user/outboxget_test.go @@ -46,15 +46,6 @@ func (suite *OutboxGetTestSuite) TestGetOutbox() { signedRequest := derefRequests["foss_satan_dereference_zork_outbox"] targetAccount := suite.testAccounts["local_account_1"] - clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1) - fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1) - - tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker) - federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker) - emailSender := testrig.NewEmailSender("../../../../web/template/", nil) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker) - userModule := user.New(processor).(*user.Module) - // setup request recorder := httptest.NewRecorder() ctx, _ := testrig.CreateGinTestContext(recorder, nil) @@ -76,7 +67,7 @@ func (suite *OutboxGetTestSuite) TestGetOutbox() { } // trigger the function being tested - userModule.OutboxGETHandler(ctx) + suite.userModule.OutboxGETHandler(ctx) // check response suite.EqualValues(http.StatusOK, recorder.Code) diff --git a/internal/api/s2s/user/repliesget_test.go b/internal/api/s2s/user/repliesget_test.go @@ -49,15 +49,6 @@ func (suite *RepliesGetTestSuite) TestGetReplies() { targetAccount := suite.testAccounts["local_account_1"] targetStatus := suite.testStatuses["local_account_1_status_1"] - clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1) - fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1) - - tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker) - federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker) - emailSender := testrig.NewEmailSender("../../../../web/template/", nil) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker) - userModule := user.New(processor).(*user.Module) - // setup request recorder := httptest.NewRecorder() ctx, _ := testrig.CreateGinTestContext(recorder, nil) @@ -83,7 +74,7 @@ func (suite *RepliesGetTestSuite) TestGetReplies() { } // trigger the function being tested - userModule.StatusRepliesGETHandler(ctx) + suite.userModule.StatusRepliesGETHandler(ctx) // check response suite.EqualValues(http.StatusOK, recorder.Code) diff --git a/internal/api/s2s/user/statusget_test.go b/internal/api/s2s/user/statusget_test.go @@ -32,8 +32,6 @@ import ( "github.com/superseriousbusiness/activity/streams" "github.com/superseriousbusiness/activity/streams/vocab" "github.com/superseriousbusiness/gotosocial/internal/api/s2s/user" - "github.com/superseriousbusiness/gotosocial/internal/concurrency" - "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/testrig" ) @@ -48,15 +46,6 @@ func (suite *StatusGetTestSuite) TestGetStatus() { targetAccount := suite.testAccounts["local_account_1"] targetStatus := suite.testStatuses["local_account_1_status_1"] - clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1) - fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1) - - tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker) - federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker) - emailSender := testrig.NewEmailSender("../../../../web/template/", nil) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker) - userModule := user.New(processor).(*user.Module) - // setup request recorder := httptest.NewRecorder() ctx, _ := testrig.CreateGinTestContext(recorder, nil) @@ -82,7 +71,7 @@ func (suite *StatusGetTestSuite) TestGetStatus() { } // trigger the function being tested - userModule.StatusGETHandler(ctx) + suite.userModule.StatusGETHandler(ctx) // check response suite.EqualValues(http.StatusOK, recorder.Code) @@ -116,15 +105,6 @@ func (suite *StatusGetTestSuite) TestGetStatusLowercase() { targetAccount := suite.testAccounts["local_account_1"] targetStatus := suite.testStatuses["local_account_1_status_1"] - clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1) - fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1) - - tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker) - federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker) - emailSender := testrig.NewEmailSender("../../../../web/template/", nil) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker) - userModule := user.New(processor).(*user.Module) - // setup request recorder := httptest.NewRecorder() ctx, _ := testrig.CreateGinTestContext(recorder, nil) @@ -150,7 +130,7 @@ func (suite *StatusGetTestSuite) TestGetStatusLowercase() { } // trigger the function being tested - userModule.StatusGETHandler(ctx) + suite.userModule.StatusGETHandler(ctx) // check response suite.EqualValues(http.StatusOK, recorder.Code) diff --git a/internal/api/s2s/user/userget_test.go b/internal/api/s2s/user/userget_test.go @@ -25,7 +25,6 @@ import ( "net/http" "net/http/httptest" "testing" - "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/suite" @@ -33,8 +32,6 @@ import ( "github.com/superseriousbusiness/activity/streams/vocab" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/api/s2s/user" - "github.com/superseriousbusiness/gotosocial/internal/concurrency" - "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/testrig" ) @@ -49,15 +46,6 @@ func (suite *UserGetTestSuite) TestGetUser() { signedRequest := derefRequests["foss_satan_dereference_zork"] targetAccount := suite.testAccounts["local_account_1"] - clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1) - fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1) - - tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker) - federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker) - emailSender := testrig.NewEmailSender("../../../../web/template/", nil) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker) - userModule := user.New(processor).(*user.Module) - // setup request recorder := httptest.NewRecorder() ctx, _ := testrig.CreateGinTestContext(recorder, nil) @@ -79,7 +67,7 @@ func (suite *UserGetTestSuite) TestGetUser() { } // trigger the function being tested - userModule.UsersGETHandler(ctx) + suite.userModule.UsersGETHandler(ctx) // check response suite.EqualValues(http.StatusOK, recorder.Code) @@ -110,6 +98,12 @@ func (suite *UserGetTestSuite) TestGetUser() { // TestGetUserPublicKeyDeleted checks whether the public key of a deleted account can still be dereferenced. // This is needed by remote instances for authenticating delete requests and stuff like that. func (suite *UserGetTestSuite) TestGetUserPublicKeyDeleted() { + if err := suite.processor.Start(); err != nil { + suite.FailNow(err.Error()) + } + defer suite.processor.Stop() + + userModule := user.New(suite.processor).(*user.Module) targetAccount := suite.testAccounts["local_account_1"] // first delete the account, as though zork had deleted himself @@ -123,22 +117,18 @@ func (suite *UserGetTestSuite) TestGetUserPublicKeyDeleted() { DeleteOriginID: targetAccount.ID, }) - // now wait just a sec for it to go through.... - time.Sleep(1 * time.Second) + // wait for the account delete to be processed + if !testrig.WaitFor(func() bool { + a, _ := suite.db.GetAccountByID(context.Background(), targetAccount.ID) + return !a.SuspendedAt.IsZero() + }) { + suite.FailNow("delete of account timed out") + } // the dereference we're gonna use derefRequests := testrig.NewTestDereferenceRequests(suite.testAccounts) signedRequest := derefRequests["foss_satan_dereference_zork_public_key"] - clientWorker := concurrency.NewWorkerPool[messages.FromClientAPI](-1, -1) - fedWorker := concurrency.NewWorkerPool[messages.FromFederator](-1, -1) - - tc := testrig.NewTestTransportController(testrig.NewMockHTTPClient(nil, "../../../../testrig/media"), suite.db, fedWorker) - federator := testrig.NewTestFederator(suite.db, tc, suite.storage, suite.mediaManager, fedWorker) - emailSender := testrig.NewEmailSender("../../../../web/template/", nil) - processor := testrig.NewTestProcessor(suite.db, suite.storage, federator, emailSender, suite.mediaManager, clientWorker, fedWorker) - userModule := user.New(processor).(*user.Module) - // setup request recorder := httptest.NewRecorder() ctx, _ := testrig.CreateGinTestContext(recorder, nil) diff --git a/internal/federation/dereferencing/media_test.go b/internal/federation/dereferencing/media_test.go @@ -20,13 +20,12 @@ package dereferencing_test import ( "context" - "fmt" "testing" - "time" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/media" + "github.com/superseriousbusiness/gotosocial/testrig" ) type AttachmentTestSuite struct { @@ -128,12 +127,11 @@ func (suite *AttachmentTestSuite) TestDereferenceAttachmentAsync() { suite.NoError(err) attachmentID := processingMedia.AttachmentID() - // wait for the media to finish processing - for finished := processingMedia.Finished(); !finished; finished = processingMedia.Finished() { - time.Sleep(10 * time.Millisecond) - fmt.Printf("\n\nnot finished yet...\n\n") + if !testrig.WaitFor(func() bool { + return processingMedia.Finished() + }) { + suite.FailNow("timed out waiting for media to be processed") } - fmt.Printf("\n\nfinished!\n\n") // now get the attachment from the database attachment, err := suite.db.GetAttachmentByID(ctx, attachmentID) diff --git a/internal/federation/federatingactor_test.go b/internal/federation/federatingactor_test.go @@ -115,10 +115,22 @@ func (suite *FederatingActorTestSuite) TestSendRemoteFollower() { suite.NotNil(activity) // because we added 1 remote follower for zork, there should be a url in sentMessage - suite.Len(httpClient.SentMessages, 1) - msg, ok := httpClient.SentMessages[testRemoteAccount.InboxURI] - suite.True(ok) - suite.Equal(`{"@context":"https://www.w3.org/ns/activitystreams","actor":"http://localhost:8080/users/the_mighty_zork","id":"http://localhost:8080/whatever_some_create","object":{"attributedTo":"http://localhost:8080/users/the_mighty_zork","content":"boobies","id":"http://localhost:8080/users/the_mighty_zork/statuses/01G1TR6BADACCZWQMNF9X21TV5","published":"2022-06-02T12:22:21+02:00","tag":[],"to":"http://localhost:8080/users/the_mighty_zork/followers","type":"Note","url":"http://localhost:8080/@the_mighty_zork/statuses/01G1TR6BADACCZWQMNF9X21TV5"},"published":"2022-06-02T12:22:21+02:00","to":"http://localhost:8080/users/the_mighty_zork/followers","type":"Create"}`, string(msg)) + var sent [][]byte + if !testrig.WaitFor(func() bool { + sentI, ok := httpClient.SentMessages.Load(testRemoteAccount.InboxURI) + if ok { + sent, ok = sentI.([][]byte) + if !ok { + panic("SentMessages entry was not []byte") + } + return true + } + return false + }) { + suite.FailNow("timed out waiting for message") + } + + suite.Equal(`{"@context":"https://www.w3.org/ns/activitystreams","actor":"http://localhost:8080/users/the_mighty_zork","id":"http://localhost:8080/whatever_some_create","object":{"attributedTo":"http://localhost:8080/users/the_mighty_zork","content":"boobies","id":"http://localhost:8080/users/the_mighty_zork/statuses/01G1TR6BADACCZWQMNF9X21TV5","published":"2022-06-02T12:22:21+02:00","tag":[],"to":"http://localhost:8080/users/the_mighty_zork/followers","type":"Note","url":"http://localhost:8080/@the_mighty_zork/statuses/01G1TR6BADACCZWQMNF9X21TV5"},"published":"2022-06-02T12:22:21+02:00","to":"http://localhost:8080/users/the_mighty_zork/followers","type":"Create"}`, string(sent[0])) } func TestFederatingActorTestSuite(t *testing.T) { diff --git a/internal/media/manager_test.go b/internal/media/manager_test.go @@ -26,7 +26,6 @@ import ( "os" "path" "testing" - "time" "codeberg.org/gruf/go-store/kv" "codeberg.org/gruf/go-store/storage" @@ -34,6 +33,7 @@ import ( gtsmodel "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/media" gtsstorage "github.com/superseriousbusiness/gotosocial/internal/storage" + "github.com/superseriousbusiness/gotosocial/testrig" ) type ManagerTestSuite struct { @@ -360,11 +360,11 @@ func (suite *ManagerTestSuite) TestSimpleJpegProcessAsync() { attachmentID := processingMedia.AttachmentID() // wait for the media to finish processing - for finished := processingMedia.Finished(); !finished; finished = processingMedia.Finished() { - time.Sleep(10 * time.Millisecond) - fmt.Printf("\n\nnot finished yet...\n\n") + if !testrig.WaitFor(func() bool { + return processingMedia.Finished() + }) { + suite.FailNow("timed out waiting for media to be processed") } - fmt.Printf("\n\nfinished!\n\n") // fetch the attachment from the database attachment, err := suite.db.GetAttachmentByID(ctx, attachmentID) diff --git a/internal/processing/account_test.go b/internal/processing/account_test.go @@ -29,6 +29,7 @@ import ( "github.com/superseriousbusiness/activity/pub" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/testrig" ) type AccountTestSuite struct { @@ -59,23 +60,30 @@ func (suite *AccountTestSuite) TestAccountDeleteLocal() { suite.NoError(errWithCode) // the delete should be federated outwards to the following account's inbox - var sent []byte - var ok bool - for !ok { - sent, ok = suite.httpClient.SentMessages[followingAccount.InboxURI] - } - - suite.True(ok) - delete := &struct { + var sent [][]byte + delete := new(struct { Actor string `json:"actor"` ID string `json:"id"` Object string `json:"object"` To string `json:"to"` CC string `json:"cc"` Type string `json:"type"` - }{} - err = json.Unmarshal(sent, delete) - suite.NoError(err) + }) + + if !testrig.WaitFor(func() bool { + sentI, ok := suite.httpClient.SentMessages.Load(followingAccount.InboxURI) + if ok { + sent, ok = sentI.([][]byte) + if !ok { + panic("SentMessages entry was not [][]byte") + } + err = json.Unmarshal(sent[0], delete) + return err == nil + } + return false + }) { + suite.FailNow("timed out waiting for message") + } suite.Equal(deletingAccount.URI, delete.Actor) suite.Equal(deletingAccount.URI, delete.Object) @@ -83,13 +91,12 @@ func (suite *AccountTestSuite) TestAccountDeleteLocal() { suite.Equal(pub.PublicActivityPubIRI, delete.CC) suite.Equal("Delete", delete.Type) - // wait for the delete to go through - time.Sleep(1 * time.Second) - - // the deleted account should be deleted - dbAccount, err := suite.db.GetAccountByID(ctx, deletingAccount.ID) - suite.NoError(err) - suite.WithinDuration(dbAccount.SuspendedAt, time.Now(), 30*time.Second) + if !testrig.WaitFor(func() bool { + dbAccount, _ := suite.db.GetAccountByID(ctx, deletingAccount.ID) + return suite.WithinDuration(dbAccount.SuspendedAt, time.Now(), 30*time.Second) + }) { + suite.FailNow("timed out waiting for account to be deleted") + } } func TestAccountTestSuite(t *testing.T) { diff --git a/internal/processing/followrequest_test.go b/internal/processing/followrequest_test.go @@ -28,6 +28,7 @@ import ( "github.com/stretchr/testify/suite" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/testrig" ) type FollowRequestTestSuite struct { @@ -54,11 +55,22 @@ func (suite *FollowRequestTestSuite) TestFollowRequestAccept() { relationship, errWithCode := suite.processor.FollowRequestAccept(context.Background(), suite.testAutheds["local_account_1"], requestingAccount.ID) suite.NoError(errWithCode) suite.EqualValues(&apimodel.Relationship{ID: "01FHMQX3GAABWSM0S2VZEC2SWC", Following: false, ShowingReblogs: false, Notifying: false, FollowedBy: true, Blocking: false, BlockedBy: false, Muting: false, MutingNotifications: false, Requested: false, DomainBlocking: false, Endorsed: false, Note: ""}, relationship) - time.Sleep(1 * time.Second) // accept should be sent to some_user - sent, ok := suite.httpClient.SentMessages[requestingAccount.InboxURI] - suite.True(ok) + var sent [][]byte + if !testrig.WaitFor(func() bool { + sentI, ok := suite.httpClient.SentMessages.Load(requestingAccount.InboxURI) + if ok { + sent, ok = sentI.([][]byte) + if !ok { + panic("SentMessages entry was not []byte") + } + return true + } + return false + }) { + suite.FailNow("timed out waiting for message") + } accept := &struct { Actor string `json:"actor"` @@ -73,7 +85,7 @@ func (suite *FollowRequestTestSuite) TestFollowRequestAccept() { To string `json:"to"` Type string `json:"type"` }{} - err = json.Unmarshal(sent, accept) + err = json.Unmarshal(sent[0], accept) suite.NoError(err) suite.Equal(targetAccount.URI, accept.Actor) @@ -106,11 +118,22 @@ func (suite *FollowRequestTestSuite) TestFollowRequestReject() { relationship, errWithCode := suite.processor.FollowRequestReject(context.Background(), suite.testAutheds["local_account_1"], requestingAccount.ID) suite.NoError(errWithCode) suite.EqualValues(&apimodel.Relationship{ID: "01FHMQX3GAABWSM0S2VZEC2SWC", Following: false, ShowingReblogs: false, Notifying: false, FollowedBy: false, Blocking: false, BlockedBy: false, Muting: false, MutingNotifications: false, Requested: false, DomainBlocking: false, Endorsed: false, Note: ""}, relationship) - time.Sleep(1 * time.Second) // reject should be sent to some_user - sent, ok := suite.httpClient.SentMessages[requestingAccount.InboxURI] - suite.True(ok) + var sent [][]byte + if !testrig.WaitFor(func() bool { + sentI, ok := suite.httpClient.SentMessages.Load(requestingAccount.InboxURI) + if ok { + sent, ok = sentI.([][]byte) + if !ok { + panic("SentMessages entry was not []byte") + } + return true + } + return false + }) { + suite.FailNow("timed out waiting for message") + } reject := &struct { Actor string `json:"actor"` @@ -125,7 +148,7 @@ func (suite *FollowRequestTestSuite) TestFollowRequestReject() { To string `json:"to"` Type string `json:"type"` }{} - err = json.Unmarshal(sent, reject) + err = json.Unmarshal(sent[0], reject) suite.NoError(err) suite.Equal(targetAccount.URI, reject.Actor) diff --git a/internal/processing/fromfederator_test.go b/internal/processing/fromfederator_test.go @@ -482,26 +482,22 @@ func (suite *FromFederatorTestSuite) TestProcessFollowRequestUnlocked() { }) suite.NoError(err) - // a notification should be streamed - var msg *stream.Message - select { - case msg = <-wssStream.Messages: - // fine - case <-time.After(5 * time.Second): - suite.FailNow("no message from wssStream") + // an accept message should be sent to satan's inbox + var sent [][]byte + if !testrig.WaitFor(func() bool { + sentI, ok := suite.httpClient.SentMessages.Load(originAccount.InboxURI) + if ok { + sent, ok = sentI.([][]byte) + if !ok { + panic("SentMessages entry was not []byte") + } + return true + } + return false + }) { + suite.FailNow("timed out waiting for message") } - suite.Equal(stream.EventTypeNotification, msg.Event) - suite.NotEmpty(msg.Payload) - suite.EqualValues([]string{stream.TimelineHome}, msg.Stream) - notif := &model.Notification{} - err = json.Unmarshal([]byte(msg.Payload), notif) - suite.NoError(err) - suite.Equal("follow", notif.Type) - suite.Equal(originAccount.ID, notif.Account.ID) - // an accept message should be sent to satan's inbox - suite.Len(suite.httpClient.SentMessages, 1) - acceptBytes := suite.httpClient.SentMessages[originAccount.InboxURI] accept := &struct { Actor string `json:"actor"` ID string `json:"id"` @@ -515,7 +511,7 @@ func (suite *FromFederatorTestSuite) TestProcessFollowRequestUnlocked() { To string `json:"to"` Type string `json:"type"` }{} - err = json.Unmarshal(acceptBytes, accept) + err = json.Unmarshal(sent[0], accept) suite.NoError(err) suite.Equal(targetAccount.URI, accept.Actor) @@ -526,6 +522,23 @@ func (suite *FromFederatorTestSuite) TestProcessFollowRequestUnlocked() { suite.Equal("Follow", accept.Object.Type) suite.Equal(originAccount.URI, accept.To) suite.Equal("Accept", accept.Type) + + // a notification should be streamed + var msg *stream.Message + select { + case msg = <-wssStream.Messages: + // fine + case <-time.After(5 * time.Second): + suite.FailNow("no message from wssStream") + } + suite.Equal(stream.EventTypeNotification, msg.Event) + suite.NotEmpty(msg.Payload) + suite.EqualValues([]string{stream.TimelineHome}, msg.Stream) + notif := &model.Notification{} + err = json.Unmarshal([]byte(msg.Payload), notif) + suite.NoError(err) + suite.Equal("follow", notif.Type) + suite.Equal(originAccount.ID, notif.Account.ID) } // TestCreateStatusFromIRI checks if a forwarded status can be dereferenced by the processor. diff --git a/internal/processing/media/getfile_test.go b/internal/processing/media/getfile_test.go @@ -23,10 +23,10 @@ import ( "io" "path" "testing" - "time" "github.com/stretchr/testify/suite" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/media" "github.com/superseriousbusiness/gotosocial/testrig" ) @@ -99,10 +99,16 @@ func (suite *GetFileTestSuite) TestGetRemoteFileUncached() { suite.Equal(suite.testRemoteAttachments[testAttachment.RemoteURL].Data, b) suite.Equal(suite.testRemoteAttachments[testAttachment.RemoteURL].ContentType, content.ContentType) suite.EqualValues(len(suite.testRemoteAttachments[testAttachment.RemoteURL].Data), content.ContentLength) - time.Sleep(2 * time.Second) // wait a few seconds for the media manager to finish doing stuff // the attachment should be updated in the database - dbAttachment, err := suite.db.GetAttachmentByID(ctx, testAttachment.ID) + var dbAttachment *gtsmodel.MediaAttachment + if !testrig.WaitFor(func() bool { + dbAttachment, err = suite.db.GetAttachmentByID(ctx, testAttachment.ID) + return dbAttachment != nil + }) { + suite.FailNow("timed out waiting for updated attachment") + } + suite.NoError(err) suite.True(*dbAttachment.Cached) @@ -149,12 +155,13 @@ func (suite *GetFileTestSuite) TestGetRemoteFileUncachedInterrupted() { suite.NoError(closer.Close()) } - time.Sleep(2 * time.Second) // wait a few seconds for the media manager to finish doing stuff - // the attachment should still be updated in the database even though the caller hung up - dbAttachment, err := suite.db.GetAttachmentByID(ctx, testAttachment.ID) - suite.NoError(err) - suite.True(*dbAttachment.Cached) + if !testrig.WaitFor(func() bool { + dbAttachment, _ := suite.db.GetAttachmentByID(ctx, testAttachment.ID) + return *dbAttachment.Cached + }) { + suite.FailNow("timed out waiting for attachment to be updated") + } // the file should be back in storage at the same path as before refreshedBytes, err := suite.storage.Get(ctx, testAttachment.File.Path) diff --git a/internal/trans/trans_test.go b/internal/trans/trans_test.go @@ -33,9 +33,9 @@ type TransTestSuite struct { func (suite *TransTestSuite) SetupTest() { testrig.InitTestConfig() - testrig.InitTestLog() + testrig.InitTestLog() - suite.testAccounts = testrig.NewTestAccounts() + suite.testAccounts = testrig.NewTestAccounts() suite.db = testrig.NewTestDB() testrig.StandardDBSetup(suite.db, nil) diff --git a/testrig/config.go b/testrig/config.go @@ -19,9 +19,6 @@ package testrig import ( - "os" - "path" - "github.com/coreos/go-oidc/v3/oidc" "github.com/superseriousbusiness/gotosocial/internal/config" ) @@ -29,12 +26,11 @@ import ( // InitTestConfig initializes viper configuration with test defaults. func InitTestConfig() { config.Config(func(cfg *config.Configuration) { - *cfg = TestDefaults + *cfg = testDefaults }) } -// TestDefaults returns a Values struct with values set that are suitable for local testing. -var TestDefaults = config.Configuration{ +var testDefaults = config.Configuration{ LogLevel: "trace", LogDbQueries: true, ApplicationName: "gotosocial", @@ -69,8 +65,11 @@ var TestDefaults = config.Configuration{ MediaDescriptionMaxChars: 500, MediaRemoteCacheDays: 30, - StorageBackend: "local", - StorageLocalBasePath: path.Join(os.TempDir(), "gotosocial"), + // the testrig only uses in-memory storage, so we can + // safely set this value to 'test' to avoid running storage + // migrations, and other silly things like that + StorageBackend: "test", + StorageLocalBasePath: "", StatusesMaxChars: 5000, StatusesCWMaxChars: 100, diff --git a/testrig/transportcontroller.go b/testrig/transportcontroller.go @@ -24,6 +24,7 @@ import ( "io" "net/http" "strings" + "sync" "github.com/superseriousbusiness/activity/pub" "github.com/superseriousbusiness/activity/streams" @@ -64,7 +65,7 @@ type MockHTTPClient struct { testRemoteServices map[string]vocab.ActivityStreamsService testRemoteAttachments map[string]RemoteAttachmentFile - SentMessages map[string][]byte + SentMessages sync.Map } // NewMockHTTPClient returns a client that conforms to the pub.HttpClient interface. @@ -90,8 +91,6 @@ func NewMockHTTPClient(do func(req *http.Request) (*http.Response, error), relat mockHTTPClient.testRemoteServices = NewTestFediServices() mockHTTPClient.testRemoteAttachments = NewTestFediAttachments(relativeMediaPath) - mockHTTPClient.SentMessages = make(map[string][]byte) - mockHTTPClient.do = func(req *http.Request) (*http.Response, error) { responseCode := http.StatusNotFound responseBytes := []byte(`{"error":"404 not found"}`) @@ -103,7 +102,15 @@ func NewMockHTTPClient(do func(req *http.Request) (*http.Response, error), relat if err != nil { panic(err) } - mockHTTPClient.SentMessages[req.URL.String()] = b + + if sI, loaded := mockHTTPClient.SentMessages.LoadOrStore(req.URL.String(), [][]byte{b}); loaded { + s, ok := sI.([][]byte) + if !ok { + panic("SentMessages entry wasn't [][]byte") + } + s = append(s, b) + mockHTTPClient.SentMessages.Store(req.URL.String(), s) + } responseCode = http.StatusOK responseBytes = []byte(`{"ok":"accepted"}`) diff --git a/testrig/util.go b/testrig/util.go @@ -87,3 +87,28 @@ func TimeMustParse(timeString string) time.Time { } return t } + +// WaitFor calls condition every 200ms, returning true +// when condition() returns true, or false after 5s. +// +// It's useful for when you're waiting for something to +// happen, but you don't know exactly how long it will take, +// and you want to fail if the thing doesn't happen within 5s. +func WaitFor(condition func() bool) bool { + tick := time.NewTicker(200 * time.Millisecond) + defer tick.Stop() + + timeout := time.NewTimer(5 * time.Second) + defer timeout.Stop() + + for { + select { + case <-tick.C: + if condition() { + return true + } + case <-timeout.C: + return false + } + } +} diff --git a/vendor/github.com/stretchr/testify/assert/assertion_compare.go b/vendor/github.com/stretchr/testify/assert/assertion_compare.go @@ -1,6 +1,7 @@ package assert import ( + "bytes" "fmt" "reflect" "time" @@ -32,7 +33,8 @@ var ( stringType = reflect.TypeOf("") - timeType = reflect.TypeOf(time.Time{}) + timeType = reflect.TypeOf(time.Time{}) + bytesType = reflect.TypeOf([]byte{}) ) func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { @@ -323,6 +325,26 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) { return compare(timeObj1.UnixNano(), timeObj2.UnixNano(), reflect.Int64) } + case reflect.Slice: + { + // We only care about the []byte type. + if !canConvert(obj1Value, bytesType) { + break + } + + // []byte can be compared! + bytesObj1, ok := obj1.([]byte) + if !ok { + bytesObj1 = obj1Value.Convert(bytesType).Interface().([]byte) + + } + bytesObj2, ok := obj2.([]byte) + if !ok { + bytesObj2 = obj2Value.Convert(bytesType).Interface().([]byte) + } + + return CompareType(bytes.Compare(bytesObj1, bytesObj2)), true + } } return compareEqual, false diff --git a/vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go b/vendor/github.com/stretchr/testify/assert/assertion_compare_can_convert.go @@ -9,7 +9,7 @@ package assert import "reflect" -// Wrapper around reflect.Value.CanConvert, for compatability +// Wrapper around reflect.Value.CanConvert, for compatibility // reasons. func canConvert(value reflect.Value, to reflect.Type) bool { return value.CanConvert(to) diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go @@ -736,6 +736,16 @@ func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta tim return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...) } +// 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{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return WithinRange(t, actual, start, end, append([]interface{}{msg}, args...)...) +} + // YAMLEqf asserts that two YAML strings are equivalent. func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go @@ -1461,6 +1461,26 @@ func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta return 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{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return 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{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return 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{}) bool { if h, ok := a.t.(tHelper); ok { diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go @@ -8,6 +8,7 @@ import ( "fmt" "math" "os" + "path/filepath" "reflect" "regexp" "runtime" @@ -144,7 +145,8 @@ func CallerInfo() []string { if len(parts) > 1 { dir := parts[len(parts)-2] if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" { - callers = append(callers, fmt.Sprintf("%s:%d", file, line)) + path, _ := filepath.Abs(file) + callers = append(callers, fmt.Sprintf("%s:%d", path, line)) } } @@ -563,16 +565,17 @@ func isEmpty(object interface{}) bool { switch objValue.Kind() { // collection types are empty when they have no element - case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: + case reflect.Chan, reflect.Map, reflect.Slice: return objValue.Len() == 0 - // pointers are empty if nil or if the value they point to is empty + // pointers are empty if nil or if the value they point to is empty case reflect.Ptr: if objValue.IsNil() { return true } deref := objValue.Elem().Interface() return isEmpty(deref) - // for all other types, compare against the zero value + // for all other types, compare against the zero value + // array types are empty when they match their zero-initialized state default: zero := reflect.Zero(objValue.Type()) return reflect.DeepEqual(object, zero.Interface()) @@ -815,7 +818,6 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok return true // we consider nil to be equal to the nil set } - subsetValue := reflect.ValueOf(subset) defer func() { if e := recover(); e != nil { ok = false @@ -825,14 +827,32 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok listKind := reflect.TypeOf(list).Kind() subsetKind := reflect.TypeOf(subset).Kind() - if listKind != reflect.Array && listKind != reflect.Slice { + if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map { return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...) } - if subsetKind != reflect.Array && subsetKind != reflect.Slice { + if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map { return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...) } + subsetValue := reflect.ValueOf(subset) + if subsetKind == reflect.Map && listKind == reflect.Map { + listValue := reflect.ValueOf(list) + subsetKeys := subsetValue.MapKeys() + + for i := 0; i < len(subsetKeys); i++ { + subsetKey := subsetKeys[i] + subsetElement := subsetValue.MapIndex(subsetKey).Interface() + listElement := listValue.MapIndex(subsetKey).Interface() + + if !ObjectsAreEqual(subsetElement, listElement) { + return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", list, subsetElement), msgAndArgs...) + } + } + + return true + } + for i := 0; i < subsetValue.Len(); i++ { element := subsetValue.Index(i).Interface() ok, found := containsElement(list, element) @@ -859,7 +879,6 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) return Fail(t, "nil is the empty set which is a subset of every set", msgAndArgs...) } - subsetValue := reflect.ValueOf(subset) defer func() { if e := recover(); e != nil { ok = false @@ -869,14 +888,32 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) listKind := reflect.TypeOf(list).Kind() subsetKind := reflect.TypeOf(subset).Kind() - if listKind != reflect.Array && listKind != reflect.Slice { + if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map { return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...) } - if subsetKind != reflect.Array && subsetKind != reflect.Slice { + if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map { return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...) } + subsetValue := reflect.ValueOf(subset) + if subsetKind == reflect.Map && listKind == reflect.Map { + listValue := reflect.ValueOf(list) + subsetKeys := subsetValue.MapKeys() + + for i := 0; i < len(subsetKeys); i++ { + subsetKey := subsetKeys[i] + subsetElement := subsetValue.MapIndex(subsetKey).Interface() + listElement := listValue.MapIndex(subsetKey).Interface() + + if !ObjectsAreEqual(subsetElement, listElement) { + return true + } + } + + return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...) + } + for i := 0; i < subsetValue.Len(); i++ { element := subsetValue.Index(i).Interface() ok, found := containsElement(list, element) @@ -1109,6 +1146,27 @@ func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, return true } +// 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, start, end time.Time, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + if end.Before(start) { + return Fail(t, "Start should be before end", msgAndArgs...) + } + + if actual.Before(start) { + return Fail(t, fmt.Sprintf("Time %v expected to be in time range %v to %v, but is before the range", actual, start, end), msgAndArgs...) + } else if actual.After(end) { + return Fail(t, fmt.Sprintf("Time %v expected to be in time range %v to %v, but is after the range", actual, start, end), msgAndArgs...) + } + + return true +} + func toFloat(x interface{}) (float64, bool) { var xf float64 xok := true diff --git a/vendor/github.com/stretchr/testify/require/require.go b/vendor/github.com/stretchr/testify/require/require.go @@ -1864,6 +1864,32 @@ func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta tim 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 { diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go b/vendor/github.com/stretchr/testify/require/require_forward.go @@ -1462,6 +1462,26 @@ func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta 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 { diff --git a/vendor/github.com/stretchr/testify/suite/suite.go b/vendor/github.com/stretchr/testify/suite/suite.go @@ -7,6 +7,7 @@ import ( "reflect" "regexp" "runtime/debug" + "sync" "testing" "time" @@ -21,17 +22,22 @@ var matchMethod = flag.String("testify.m", "", "regular expression to select tes // retrieving the current *testing.T context. type Suite struct { *assert.Assertions + mu sync.RWMutex require *require.Assertions t *testing.T } // T retrieves the current *testing.T context. func (suite *Suite) T() *testing.T { + suite.mu.RLock() + defer suite.mu.RUnlock() return suite.t } // SetT sets the current *testing.T context. func (suite *Suite) SetT(t *testing.T) { + suite.mu.Lock() + defer suite.mu.Unlock() suite.t = t suite.Assertions = assert.New(t) suite.require = require.New(t) @@ -39,6 +45,8 @@ func (suite *Suite) SetT(t *testing.T) { // Require returns a require context for suite. func (suite *Suite) Require() *require.Assertions { + suite.mu.Lock() + defer suite.mu.Unlock() if suite.require == nil { suite.require = require.New(suite.T()) } @@ -51,14 +59,20 @@ func (suite *Suite) Require() *require.Assertions { // assert.Assertions with require.Assertions), this method is provided so you // can call `suite.Assert().NoError()`. func (suite *Suite) Assert() *assert.Assertions { + suite.mu.Lock() + defer suite.mu.Unlock() if suite.Assertions == nil { suite.Assertions = assert.New(suite.T()) } return suite.Assertions } -func failOnPanic(t *testing.T) { +func recoverAndFailOnPanic(t *testing.T) { r := recover() + failOnPanic(t, r) +} + +func failOnPanic(t *testing.T, r interface{}) { if r != nil { t.Errorf("test panicked: %v\n%s", r, debug.Stack()) t.FailNow() @@ -81,7 +95,7 @@ func (suite *Suite) Run(name string, subtest func()) bool { // Run takes a testing suite and runs all of the tests attached // to it. func Run(t *testing.T, suite TestingSuite) { - defer failOnPanic(t) + defer recoverAndFailOnPanic(t) suite.SetT(t) @@ -126,10 +140,12 @@ func Run(t *testing.T, suite TestingSuite) { F: func(t *testing.T) { parentT := suite.T() suite.SetT(t) - defer failOnPanic(t) + defer recoverAndFailOnPanic(t) defer func() { + r := recover() + if stats != nil { - passed := !t.Failed() + passed := !t.Failed() && r == nil stats.end(method.Name, passed) } @@ -142,6 +158,7 @@ func Run(t *testing.T, suite TestingSuite) { } suite.SetT(parentT) + failOnPanic(t, r) }() if setupTestSuite, ok := suite.(SetupTestSuite); ok { diff --git a/vendor/gopkg.in/yaml.v3/decode.go b/vendor/gopkg.in/yaml.v3/decode.go @@ -100,7 +100,10 @@ func (p *parser) peek() yaml_event_type_t { if p.event.typ != yaml_NO_EVENT { return p.event.typ } - if !yaml_parser_parse(&p.parser, &p.event) { + // It's curious choice from the underlying API to generally return a + // positive result on success, but on this case return true in an error + // scenario. This was the source of bugs in the past (issue #666). + if !yaml_parser_parse(&p.parser, &p.event) || p.parser.error != yaml_NO_ERROR { p.fail() } return p.event.typ @@ -320,6 +323,8 @@ type decoder struct { decodeCount int aliasCount int aliasDepth int + + mergedFields map[interface{}]bool } var ( @@ -808,6 +813,11 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) { } } + mergedFields := d.mergedFields + d.mergedFields = nil + + var mergeNode *Node + mapIsNew := false if out.IsNil() { out.Set(reflect.MakeMap(outt)) @@ -815,11 +825,18 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) { } for i := 0; i < l; i += 2 { if isMerge(n.Content[i]) { - d.merge(n.Content[i+1], out) + mergeNode = n.Content[i+1] continue } k := reflect.New(kt).Elem() if d.unmarshal(n.Content[i], k) { + if mergedFields != nil { + ki := k.Interface() + if mergedFields[ki] { + continue + } + mergedFields[ki] = true + } kkind := k.Kind() if kkind == reflect.Interface { kkind = k.Elem().Kind() @@ -833,6 +850,12 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) { } } } + + d.mergedFields = mergedFields + if mergeNode != nil { + d.merge(n, mergeNode, out) + } + d.stringMapType = stringMapType d.generalMapType = generalMapType return true @@ -844,7 +867,8 @@ func isStringMap(n *Node) bool { } l := len(n.Content) for i := 0; i < l; i += 2 { - if n.Content[i].ShortTag() != strTag { + shortTag := n.Content[i].ShortTag() + if shortTag != strTag && shortTag != mergeTag { return false } } @@ -861,7 +885,6 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) { var elemType reflect.Type if sinfo.InlineMap != -1 { inlineMap = out.Field(sinfo.InlineMap) - inlineMap.Set(reflect.New(inlineMap.Type()).Elem()) elemType = inlineMap.Type().Elem() } @@ -870,6 +893,9 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) { d.prepare(n, field) } + mergedFields := d.mergedFields + d.mergedFields = nil + var mergeNode *Node var doneFields []bool if d.uniqueKeys { doneFields = make([]bool, len(sinfo.FieldsList)) @@ -879,13 +905,20 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) { for i := 0; i < l; i += 2 { ni := n.Content[i] if isMerge(ni) { - d.merge(n.Content[i+1], out) + mergeNode = n.Content[i+1] continue } if !d.unmarshal(ni, name) { continue } - if info, ok := sinfo.FieldsMap[name.String()]; ok { + sname := name.String() + if mergedFields != nil { + if mergedFields[sname] { + continue + } + mergedFields[sname] = true + } + if info, ok := sinfo.FieldsMap[sname]; ok { if d.uniqueKeys { if doneFields[info.Id] { d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.Line, name.String(), out.Type())) @@ -911,6 +944,11 @@ func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) { d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.Line, name.String(), out.Type())) } } + + d.mergedFields = mergedFields + if mergeNode != nil { + d.merge(n, mergeNode, out) + } return true } @@ -918,19 +956,29 @@ func failWantMap() { failf("map merge requires map or sequence of maps as the value") } -func (d *decoder) merge(n *Node, out reflect.Value) { - switch n.Kind { +func (d *decoder) merge(parent *Node, merge *Node, out reflect.Value) { + mergedFields := d.mergedFields + if mergedFields == nil { + d.mergedFields = make(map[interface{}]bool) + for i := 0; i < len(parent.Content); i += 2 { + k := reflect.New(ifaceType).Elem() + if d.unmarshal(parent.Content[i], k) { + d.mergedFields[k.Interface()] = true + } + } + } + + switch merge.Kind { case MappingNode: - d.unmarshal(n, out) + d.unmarshal(merge, out) case AliasNode: - if n.Alias != nil && n.Alias.Kind != MappingNode { + if merge.Alias != nil && merge.Alias.Kind != MappingNode { failWantMap() } - d.unmarshal(n, out) + d.unmarshal(merge, out) case SequenceNode: - // Step backwards as earlier nodes take precedence. - for i := len(n.Content) - 1; i >= 0; i-- { - ni := n.Content[i] + for i := 0; i < len(merge.Content); i++ { + ni := merge.Content[i] if ni.Kind == AliasNode { if ni.Alias != nil && ni.Alias.Kind != MappingNode { failWantMap() @@ -943,6 +991,8 @@ func (d *decoder) merge(n *Node, out reflect.Value) { default: failWantMap() } + + d.mergedFields = mergedFields } func isMerge(n *Node) bool { diff --git a/vendor/gopkg.in/yaml.v3/parserc.go b/vendor/gopkg.in/yaml.v3/parserc.go @@ -687,6 +687,9 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) + if token == nil { + return false + } parser.marks = append(parser.marks, token.start_mark) skip_token(parser) } @@ -786,7 +789,7 @@ func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) { } token := peek_token(parser) - if token.typ != yaml_BLOCK_SEQUENCE_START_TOKEN && token.typ != yaml_BLOCK_MAPPING_START_TOKEN { + if token == nil || token.typ != yaml_BLOCK_SEQUENCE_START_TOKEN && token.typ != yaml_BLOCK_MAPPING_START_TOKEN { return } @@ -813,6 +816,9 @@ func yaml_parser_split_stem_comment(parser *yaml_parser_t, stem_len int) { func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) + if token == nil { + return false + } parser.marks = append(parser.marks, token.start_mark) skip_token(parser) } @@ -922,6 +928,9 @@ func yaml_parser_parse_block_mapping_value(parser *yaml_parser_t, event *yaml_ev func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_event_t, first bool) bool { if first { token := peek_token(parser) + if token == nil { + return false + } parser.marks = append(parser.marks, token.start_mark) skip_token(parser) } diff --git a/vendor/modules.txt b/vendor/modules.txt @@ -346,7 +346,7 @@ github.com/spf13/viper/internal/encoding/javaproperties github.com/spf13/viper/internal/encoding/json github.com/spf13/viper/internal/encoding/toml github.com/spf13/viper/internal/encoding/yaml -# github.com/stretchr/testify v1.7.1 +# github.com/stretchr/testify v1.8.0 ## explicit; go 1.13 github.com/stretchr/testify/assert github.com/stretchr/testify/require @@ -750,7 +750,7 @@ gopkg.in/square/go-jose.v2/json # gopkg.in/yaml.v2 v2.4.0 ## explicit; go 1.15 gopkg.in/yaml.v2 -# gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b +# gopkg.in/yaml.v3 v3.0.1 ## explicit gopkg.in/yaml.v3 # lukechampine.com/uint128 v1.2.0