From 2c1a90c9c61b6641f10c3be85e509517230cd5fb Mon Sep 17 00:00:00 2001 From: ionutboangiu Date: Thu, 11 Apr 2024 16:47:16 +0300 Subject: [PATCH] Optimize and fix unstable tests Revise backup loop tests to not rely on time.Sleep anymore and remove unused fields from them. Removed 3ns TTL from tpreader test dataDB configuration that caused in- consistent results. Ensure connManager cache is reloaded in filter tests. Before they could cause deadlocks. Remove redundant inits, global vars and setup tests for debit and accounts tests. Optimize some analyzers tests. --- analyzers/analyzers_it_test.go | 9 ++++- analyzers/codec_test.go | 6 ++-- analyzers/connector_test.go | 2 +- apier/v1/accounts_test.go | 43 ++++++---------------- apier/v1/debit_test.go | 65 ++++++++++++---------------------- apier/v1/filters_test.go | 2 ++ engine/stats_test.go | 24 +++++-------- engine/thresholds_test.go | 56 ++++++++++++----------------- engine/tpreader_test.go | 1 - engine/z_resources_test.go | 63 +++++++++++++------------------- 10 files changed, 102 insertions(+), 169 deletions(-) diff --git a/analyzers/analyzers_it_test.go b/analyzers/analyzers_it_test.go index f50e4b9a5..bf1f25f83 100644 --- a/analyzers/analyzers_it_test.go +++ b/analyzers/analyzers_it_test.go @@ -74,6 +74,14 @@ func newRPCClient(cfg *config.ListenCfg) (c *birpc.Client, err error) { // Test start here func TestAnalyzerSIT(t *testing.T) { + switch *utils.DBType { + case utils.MetaMongo: + case utils.MetaInternal, utils.MetaMySQL, utils.MetaPostgres: + t.SkipNow() + default: + t.Fatal("unsupported dbtype value") + } + for _, stest := range sTestsAlsPrf { t.Run("TestAnalyzerSIT", stest) } @@ -138,7 +146,6 @@ func testAnalyzerSLoadTarrifPlans(t *testing.T) { } else if reply != utils.OK { t.Error("Unexpected reply returned", reply) } - time.Sleep(100 * time.Millisecond) } func testAnalyzerSChargerSv1ProcessEvent(t *testing.T) { diff --git a/analyzers/codec_test.go b/analyzers/codec_test.go index fef50a59b..bd6033ce3 100644 --- a/analyzers/codec_test.go +++ b/analyzers/codec_test.go @@ -86,7 +86,7 @@ func TestNewServerCodec(t *testing.T) { if err = codec.Close(); err != nil { t.Fatal(err) } - time.Sleep(200 * time.Millisecond) + time.Sleep(10 * time.Millisecond) runtime.Gosched() if cnt, err := anz.db.DocCount(); err != nil { t.Fatal(err) @@ -150,7 +150,7 @@ func TestNewBiRPCCodec(t *testing.T) { if err = codec.Close(); err != nil { t.Fatal(err) } - time.Sleep(200 * time.Millisecond) + time.Sleep(15 * time.Millisecond) runtime.Gosched() if cnt, err := anz.db.DocCount(); err != nil { t.Fatal(err) @@ -212,7 +212,7 @@ func TestNewBiRPCCodec2(t *testing.T) { if err = codec.Close(); err != nil { t.Fatal(err) } - time.Sleep(200 * time.Millisecond) + time.Sleep(15 * time.Millisecond) runtime.Gosched() if cnt, err := anz.db.DocCount(); err != nil { t.Fatal(err) diff --git a/analyzers/connector_test.go b/analyzers/connector_test.go index 03b0e1bab..83034b8d8 100644 --- a/analyzers/connector_test.go +++ b/analyzers/connector_test.go @@ -53,7 +53,7 @@ func TestNewAnalyzeConnector(t *testing.T) { if err = rpc.Call(context.Background(), utils.CoreSv1Ping, "args", "reply"); err == nil || err.Error() != "error" { t.Errorf("Expected 'error' received %v", err) } - time.Sleep(100 * time.Millisecond) + time.Sleep(15 * time.Millisecond) runtime.Gosched() if cnt, err := anz.db.DocCount(); err != nil { t.Fatal(err) diff --git a/apier/v1/accounts_test.go b/apier/v1/accounts_test.go index 3b874b6a4..351e8fdf4 100644 --- a/apier/v1/accounts_test.go +++ b/apier/v1/accounts_test.go @@ -27,21 +27,13 @@ import ( "github.com/cgrates/cgrates/utils" ) -var ( - apierAcnts *APIerSv1 - apierAcntsAcntStorage *engine.InternalDB -) - -func init() { +func TestAccountsSetGet(t *testing.T) { cfg := config.NewDefaultCGRConfig() - apierAcntsAcntStorage = engine.NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) - apierAcnts = &APIerSv1{ - DataManager: engine.NewDataManager(apierAcntsAcntStorage, config.CgrConfig().CacheCfg(), nil), + db := engine.NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) + apierSv1 := &APIerSv1{ + DataManager: engine.NewDataManager(db, config.CgrConfig().CacheCfg(), nil), Config: cfg, } -} - -func TestSetAccounts(t *testing.T) { cgrTenant := "cgrates.org" iscTenant := "itsyscom.com" b10 := &engine.Balance{Value: 10, Weight: 10} @@ -56,53 +48,38 @@ func TestSetAccounts(t *testing.T) { iscAcnt2 := &engine.Account{ID: utils.ConcatenatedKey(iscTenant, "account2"), BalanceMap: map[string]engine.Balances{utils.MetaMonetary + utils.MetaOut: {b10}}} for _, account := range []*engine.Account{cgrAcnt1, cgrAcnt2, cgrAcnt3, iscAcnt1, iscAcnt2} { - if err := apierAcntsAcntStorage.SetAccountDrv(account); err != nil { + if err := db.SetAccountDrv(account); err != nil { t.Error(err) } } - //apierAcntsAcntStorage.CacheRatingPrefixes(utils.ActionPrefix) -} -/* This was a comment -func TestGetAccountIds(t *testing.T) { - var accountIds []string - var attrs AttrGetAccountIds - if err := apierAcnts.GetAccountIds(attrs, &accountIds); err != nil { - t.Error("Unexpected error", err.Error()) - } else if len(accountIds) != 5 { - t.Errorf("Accounts returned: %+v", accountIds) - } -} -*/ - -func TestGetAccounts(t *testing.T) { var accounts []any var attrs utils.AttrGetAccounts - if err := apierAcnts.GetAccounts(context.Background(), &utils.AttrGetAccounts{Tenant: "cgrates.org"}, &accounts); err != nil { + if err := apierSv1.GetAccounts(context.Background(), &utils.AttrGetAccounts{Tenant: "cgrates.org"}, &accounts); err != nil { t.Error("Unexpected error", err.Error()) } else if len(accounts) != 3 { t.Errorf("Accounts returned: %+v", accounts) } attrs = utils.AttrGetAccounts{Tenant: "itsyscom.com"} - if err := apierAcnts.GetAccounts(context.Background(), &attrs, &accounts); err != nil { + if err := apierSv1.GetAccounts(context.Background(), &attrs, &accounts); err != nil { t.Error("Unexpected error", err.Error()) } else if len(accounts) != 2 { t.Errorf("Accounts returned: %+v", accounts) } attrs = utils.AttrGetAccounts{Tenant: "cgrates.org", AccountIDs: []string{"account1"}} - if err := apierAcnts.GetAccounts(context.Background(), &attrs, &accounts); err != nil { + if err := apierSv1.GetAccounts(context.Background(), &attrs, &accounts); err != nil { t.Error("Unexpected error", err.Error()) } else if len(accounts) != 1 { t.Errorf("Accounts returned: %+v", accounts) } attrs = utils.AttrGetAccounts{Tenant: "itsyscom.com", AccountIDs: []string{"INVALID"}} - if err := apierAcnts.GetAccounts(context.Background(), &attrs, &accounts); err != nil { + if err := apierSv1.GetAccounts(context.Background(), &attrs, &accounts); err != nil { t.Error("Unexpected error", err.Error()) } else if len(accounts) != 0 { t.Errorf("Accounts returned: %+v", accounts) } attrs = utils.AttrGetAccounts{Tenant: "INVALID"} - if err := apierAcnts.GetAccounts(context.Background(), &attrs, &accounts); err != nil { + if err := apierSv1.GetAccounts(context.Background(), &attrs, &accounts); err != nil { t.Error("Unexpected error", err.Error()) } else if len(accounts) != 0 { t.Errorf("Accounts returned: %+v", accounts) diff --git a/apier/v1/debit_test.go b/apier/v1/debit_test.go index d8e7870aa..07960f685 100644 --- a/apier/v1/debit_test.go +++ b/apier/v1/debit_test.go @@ -28,60 +28,39 @@ import ( "github.com/cgrates/cgrates/utils" ) -var ( - apierDebit *APIerSv1 - apierDebitStorage *engine.InternalDB - responder *engine.Responder - dm *engine.DataManager -) - -func init() { - cfg := config.NewDefaultCGRConfig() - config.SetCgrConfig(cfg) - apierDebitStorage = engine.NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) - - responder := &engine.Responder{MaxComputedUsage: cfg.RalsCfg().MaxComputedUsage} - dm = engine.NewDataManager(apierDebitStorage, config.CgrConfig().CacheCfg(), nil) - engine.SetDataStorage(dm) - apierDebit = &APIerSv1{ - DataManager: dm, - Config: cfg, - Responder: responder, - } -} - -func TestDebitUsageWithOptionsSetConfig(t *testing.T) { - cfg := config.NewDefaultCGRConfig() - config.SetCgrConfig(cfg) - apierDebitStorage = engine.NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) - responder := &engine.Responder{MaxComputedUsage: cfg.RalsCfg().MaxComputedUsage} - dm = engine.NewDataManager(apierDebitStorage, cfg.CacheCfg(), nil) - engine.SetDataStorage(dm) - apierDebit = &APIerSv1{ - DataManager: dm, - Config: cfg, - Responder: responder, - } -} - func TestDebitUsageWithOptions(t *testing.T) { + cfg := config.NewDefaultCGRConfig() + dataDB := engine.NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) + dm := engine.NewDataManager(dataDB, cfg.CacheCfg(), nil) + engine.SetDataStorage(dm) + apierDebit := &APIerSv1{ + DataManager: dm, + Config: cfg, + Responder: &engine.Responder{ + MaxComputedUsage: cfg.RalsCfg().MaxComputedUsage, + }, + } + cgrTenant := "cgrates.org" - b10 := &engine.Balance{Value: 10, Weight: 10} cgrAcnt1 := &engine.Account{ ID: utils.ConcatenatedKey(cgrTenant, "account1"), BalanceMap: map[string]engine.Balances{ - utils.MetaMonetary: {b10}, + utils.MetaMonetary: { + { + Value: 10, + Weight: 10, + }, + }, }, } - if err := apierDebitStorage.SetAccountDrv(cgrAcnt1); err != nil { + if err := dataDB.SetAccountDrv(cgrAcnt1); err != nil { t.Error(err) } - dstDe := &engine.Destination{Id: "*any", Prefixes: []string{"*any"}} - if err := apierDebitStorage.SetDestinationDrv(dstDe, utils.NonTransactional); err != nil { + if err := dataDB.SetDestinationDrv(dstDe, utils.NonTransactional); err != nil { t.Error(err) } - if err := apierDebitStorage.SetReverseDestinationDrv(dstDe.Id, dstDe.Prefixes, utils.NonTransactional); err != nil { + if err := dataDB.SetReverseDestinationDrv(dstDe.Id, dstDe.Prefixes, utils.NonTransactional); err != nil { t.Error(err) } rp1 := &engine.RatingPlan{ @@ -156,7 +135,7 @@ func TestDebitUsageWithOptions(t *testing.T) { } // Reload the account and verify that the usage of $1 was removed from the monetary balance - resolvedAccount, err := apierDebitStorage.GetAccountDrv(cgrAcnt1.ID) + resolvedAccount, err := dataDB.GetAccountDrv(cgrAcnt1.ID) if err != nil { t.Error(err) } diff --git a/apier/v1/filters_test.go b/apier/v1/filters_test.go index 35ebaf582..48b23b5e9 100644 --- a/apier/v1/filters_test.go +++ b/apier/v1/filters_test.go @@ -72,6 +72,7 @@ func TestFiltersSetFilterReloadCache(t *testing.T) { cM := engine.NewConnManager(cfg, map[string]chan birpc.ClientConnector{ utils.ConcatenatedKey(utils.MetaInternal, utils.MetaCaches): rpcInternal, }) + cM.Reload() apierSv1 := &APIerSv1{ Config: cfg, DataManager: dm, @@ -263,6 +264,7 @@ func TestFiltersSetFilterClearCache(t *testing.T) { cM := engine.NewConnManager(cfg, map[string]chan birpc.ClientConnector{ utils.ConcatenatedKey(utils.MetaInternal, utils.MetaCaches): rpcInternal, }) + cM.Reload() apierSv1 := &APIerSv1{ Config: cfg, DataManager: dm, diff --git a/engine/stats_test.go b/engine/stats_test.go index 6cbe95261..e13e3893f 100644 --- a/engine/stats_test.go +++ b/engine/stats_test.go @@ -995,12 +995,7 @@ func TestStatQueueMatchingStatQueuesForEventLocks4(t *testing.T) { func TestStatQueueReload(t *testing.T) { cfg := config.NewDefaultCGRConfig() cfg.StatSCfg().StoreInterval = 5 * time.Millisecond - data := NewInternalDB(nil, nil, true, config.CgrConfig().DataDbCfg().Items) - dm := NewDataManager(data, cfg.CacheCfg(), nil) - filterS := NewFilterS(cfg, nil, dm) sS := &StatService{ - dm: dm, - filterS: filterS, stopBackup: make(chan struct{}), loopStopped: make(chan struct{}, 1), cgrcfg: cfg, @@ -1008,27 +1003,26 @@ func TestStatQueueReload(t *testing.T) { sS.loopStopped <- struct{}{} sS.Reload() close(sS.stopBackup) + select { + case <-sS.loopStopped: + case <-time.After(time.Second): + t.Error("timed out waiting for loop to stop") + } } func TestStatQueueStartLoop(t *testing.T) { cfg := config.NewDefaultCGRConfig() cfg.StatSCfg().StoreInterval = -1 - data := NewInternalDB(nil, nil, true, config.CgrConfig().DataDbCfg().Items) - dm := NewDataManager(data, cfg.CacheCfg(), nil) - filterS := NewFilterS(cfg, nil, dm) sS := &StatService{ - dm: dm, - filterS: filterS, - stopBackup: make(chan struct{}), loopStopped: make(chan struct{}, 1), cgrcfg: cfg, } sS.StartLoop() - time.Sleep(10 * time.Millisecond) - - if len(sS.loopStopped) != 1 { - t.Errorf("expected loopStopped field to have only one element, received: <%+v>", len(sS.loopStopped)) + select { + case <-sS.loopStopped: + case <-time.After(time.Second): + t.Error("timed out waiting for loop to stop") } } diff --git a/engine/thresholds_test.go b/engine/thresholds_test.go index 00b9c4011..99f5a462a 100644 --- a/engine/thresholds_test.go +++ b/engine/thresholds_test.go @@ -1735,22 +1735,16 @@ func TestThresholdMatchingThresholdForEventLocks5(t *testing.T) { func TestThresholdsRunBackupStoreIntervalLessThanZero(t *testing.T) { cfg := config.NewDefaultCGRConfig() cfg.ThresholdSCfg().StoreInterval = -1 - data := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) - dm := NewDataManager(data, cfg.CacheCfg(), nil) - filterS := NewFilterS(cfg, nil, dm) tS := &ThresholdService{ - dm: dm, - storedTdIDs: make(utils.StringSet), cgrcfg: cfg, - filterS: filterS, loopStopped: make(chan struct{}, 1), - stopBackup: make(chan struct{}), } tS.runBackup() - - if len(tS.loopStopped) != 1 { - t.Errorf("expected loopStopped field to have only one element, received: <%+v>", len(tS.loopStopped)) + select { + case <-tS.loopStopped: + case <-time.After(time.Second): + t.Error("timed out waiting for loop to stop") } } @@ -1759,14 +1753,12 @@ func TestThresholdsRunBackupStop(t *testing.T) { cfg.ThresholdSCfg().StoreInterval = 5 * time.Millisecond data := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) dm := NewDataManager(data, cfg.CacheCfg(), nil) - filterS := NewFilterS(cfg, nil, dm) tS := &ThresholdService{ dm: dm, storedTdIDs: utils.StringSet{ "Th1": struct{}{}, }, cgrcfg: cfg, - filterS: filterS, loopStopped: make(chan struct{}, 1), stopBackup: make(chan struct{}), } @@ -1786,33 +1778,30 @@ func TestThresholdsRunBackupStop(t *testing.T) { ID: "Th1", } - go func() { - time.Sleep(9 * time.Millisecond) - close(tS.stopBackup) - }() + // Backup loop checks for the state of the stopBackup + // channel after storing the threshold. Channel can be + // safely closed beforehand. + close(tS.stopBackup) tS.runBackup() if rcv, err := tS.dm.GetThreshold("cgrates.org", "Th1", true, false, utils.NonTransactional); err != nil { t.Error(err) } else if !reflect.DeepEqual(rcv, exp) { - t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(exp), utils.ToJSON(rcv)) + t.Errorf("threshold: want %+v, got %+v", exp, rcv) } - if len(tS.loopStopped) != 1 { - t.Errorf("expected loopStopped field to have only one element, received: <%+v>", len(tS.loopStopped)) + select { + case <-tS.loopStopped: + case <-time.After(time.Second): + t.Error("timed out waiting for loop to stop") } } func TestThresholdsReload(t *testing.T) { cfg := config.NewDefaultCGRConfig() cfg.ThresholdSCfg().StoreInterval = 5 * time.Millisecond - data := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) - dm := NewDataManager(data, cfg.CacheCfg(), nil) - filterS := NewFilterS(cfg, nil, dm) tS := &ThresholdService{ - dm: dm, - filterS: filterS, stopBackup: make(chan struct{}), loopStopped: make(chan struct{}, 1), cgrcfg: cfg, @@ -1820,27 +1809,26 @@ func TestThresholdsReload(t *testing.T) { tS.loopStopped <- struct{}{} tS.Reload() close(tS.stopBackup) + select { + case <-tS.loopStopped: + case <-time.After(time.Second): + t.Error("timed out waiting for loop to stop") + } } func TestThresholdsStartLoop(t *testing.T) { cfg := config.NewDefaultCGRConfig() cfg.ThresholdSCfg().StoreInterval = -1 - data := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) - dm := NewDataManager(data, cfg.CacheCfg(), nil) - filterS := NewFilterS(cfg, nil, dm) tS := &ThresholdService{ - dm: dm, - filterS: filterS, - stopBackup: make(chan struct{}), loopStopped: make(chan struct{}, 1), cgrcfg: cfg, } tS.StartLoop() - time.Sleep(10 * time.Millisecond) - - if len(tS.loopStopped) != 1 { - t.Errorf("expected loopStopped field to have only one element, received: <%+v>", len(tS.loopStopped)) + select { + case <-tS.loopStopped: + case <-time.After(time.Second): + t.Error("timed out waiting for loop to stop") } } diff --git a/engine/tpreader_test.go b/engine/tpreader_test.go index 2589ac2a2..ce8d33cb1 100644 --- a/engine/tpreader_test.go +++ b/engine/tpreader_test.go @@ -1691,7 +1691,6 @@ func TestTPReaderLoadAccountActionsFilteredErr(t *testing.T) { cfg.DataDbCfg().Items = map[string]*config.ItemOpt{ utils.CacheTBLTPAccountActions: { Limit: 3, - TTL: 3, Remote: true, }, } diff --git a/engine/z_resources_test.go b/engine/z_resources_test.go index d632f2bed..634dc5b76 100644 --- a/engine/z_resources_test.go +++ b/engine/z_resources_test.go @@ -5917,22 +5917,16 @@ func TestResourcesLockUnlockResources(t *testing.T) { func TestResourcesRunBackupStoreIntervalLessThanZero(t *testing.T) { cfg := config.NewDefaultCGRConfig() cfg.ResourceSCfg().StoreInterval = -1 - data := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) - dm := NewDataManager(data, cfg.CacheCfg(), nil) - filterS := NewFilterS(cfg, nil, dm) rS := &ResourceService{ - dm: dm, - storedResources: make(utils.StringSet), - cgrcfg: cfg, - filterS: filterS, - loopStopped: make(chan struct{}, 1), - stopBackup: make(chan struct{}), + cgrcfg: cfg, + loopStopped: make(chan struct{}, 1), } rS.runBackup() - - if len(rS.loopStopped) != 1 { - t.Errorf("expected loopStopped field to have only one element, received: <%+v>", len(rS.loopStopped)) + select { + case <-rS.loopStopped: + case <-time.After(time.Second): + t.Error("timed out waiting for loop to stop") } } @@ -5941,14 +5935,12 @@ func TestResourcesRunBackupStop(t *testing.T) { cfg.ResourceSCfg().StoreInterval = 5 * time.Millisecond data := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) dm := NewDataManager(data, cfg.CacheCfg(), nil) - filterS := NewFilterS(cfg, nil, dm) rS := &ResourceService{ dm: dm, storedResources: utils.StringSet{ "Res1": struct{}{}, }, cgrcfg: cfg, - filterS: filterS, loopStopped: make(chan struct{}, 1), stopBackup: make(chan struct{}), } @@ -5968,33 +5960,29 @@ func TestResourcesRunBackupStop(t *testing.T) { ID: "Res1", } - go func() { - time.Sleep(9 * time.Millisecond) - close(rS.stopBackup) - // rS.stopBackup <- struct{}{} - }() + // Backup loop checks for the state of the stopBackup + // channel after storing the resource. Channel can be + // safely closed beforehand. + close(rS.stopBackup) rS.runBackup() if rcv, err := rS.dm.GetResource("cgrates.org", "Res1", true, false, utils.NonTransactional); err != nil { t.Error(err) } else if !reflect.DeepEqual(rcv, exp) { - t.Errorf("expected: <%+v>, \nreceived: <%+v>", utils.ToJSON(exp), utils.ToJSON(rcv)) + t.Errorf("resouce: want %+v, got %+v", exp, rcv) } - if len(rS.loopStopped) != 1 { - t.Errorf("expected loopStopped field to have only one element, received: <%+v>", len(rS.loopStopped)) + select { + case <-rS.loopStopped: + case <-time.After(time.Second): + t.Error("timed out waiting for loop to stop") } } func TestResourcesReload(t *testing.T) { cfg := config.NewDefaultCGRConfig() cfg.ResourceSCfg().StoreInterval = 5 * time.Millisecond - data := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) - dm := NewDataManager(data, cfg.CacheCfg(), nil) - filterS := NewFilterS(cfg, nil, dm) rS := &ResourceService{ - dm: dm, - filterS: filterS, stopBackup: make(chan struct{}), loopStopped: make(chan struct{}, 1), cgrcfg: cfg, @@ -6002,27 +5990,26 @@ func TestResourcesReload(t *testing.T) { rS.loopStopped <- struct{}{} rS.Reload() close(rS.stopBackup) + select { + case <-rS.loopStopped: + case <-time.After(time.Second): + t.Error("timed out waiting for loop to stop") + } } func TestResourcesStartLoop(t *testing.T) { cfg := config.NewDefaultCGRConfig() cfg.ResourceSCfg().StoreInterval = -1 - data := NewInternalDB(nil, nil, true, cfg.DataDbCfg().Items) - dm := NewDataManager(data, cfg.CacheCfg(), nil) - filterS := NewFilterS(cfg, nil, dm) rS := &ResourceService{ - dm: dm, - filterS: filterS, - stopBackup: make(chan struct{}), - loopStopped: make(chan struct{}, 1), + loopStopped: make(chan struct{}), cgrcfg: cfg, } rS.StartLoop() - time.Sleep(10 * time.Millisecond) - - if len(rS.loopStopped) != 1 { - t.Errorf("expected loopStopped field to have only one element, received: <%+v>", len(rS.loopStopped)) + select { + case <-rS.loopStopped: + case <-time.After(time.Second): + t.Error("timed out waiting for loop to stop") } }