diff --git a/tests/robustness/multiclient_test/README.md b/tests/robustness/multiclient_test/README.md index 62d6a6201..0a052a6d4 100644 --- a/tests/robustness/multiclient_test/README.md +++ b/tests/robustness/multiclient_test/README.md @@ -34,7 +34,9 @@ func TestExample(t *testing.T) { numClients := 4 // Define per-client test actions - f := func(ctx context.Context) { + f := func(ctx context.Context, t *testing.T) { + t.Helper() + _, err := eng.ExecAction(ctx, /* engine-action-key */, /* opts */) // ... other actions ... } @@ -43,6 +45,6 @@ func TestExample(t *testing.T) { ctx := testlogging.Context(t) // Run test actions for each client concurrently - th.RunN(ctx, numClients, f) + th.RunN(ctx, t, numClients, f) } ``` diff --git a/tests/robustness/multiclient_test/framework/harness.go b/tests/robustness/multiclient_test/framework/harness.go index 64172a0f3..f62cd3a91 100644 --- a/tests/robustness/multiclient_test/framework/harness.go +++ b/tests/robustness/multiclient_test/framework/harness.go @@ -6,12 +6,13 @@ import ( "context" "errors" "flag" + "fmt" "io/ioutil" "log" "os" "path" - "sync" "syscall" + "testing" "github.com/kopia/kopia/tests/robustness/engine" "github.com/kopia/kopia/tests/robustness/fiofilewriter" @@ -207,31 +208,44 @@ func (th *TestHarness) Engine() *engine.Engine { // Run runs the provided function asynchronously for each of the given client // contexts, waits for all of them to finish, and optionally cleans up clients. -func (th *TestHarness) Run(ctxs []context.Context, cleanup bool, f func(context.Context)) { - var wg sync.WaitGroup +func (th *TestHarness) Run( //nolint:thelper + ctxs []context.Context, + t *testing.T, cleanup bool, + f func(context.Context, *testing.T), +) { + t.Run("group", func(t *testing.T) { + testNum := 0 - for _, ctx := range ctxs { - wg.Add(1) + for _, ctx := range ctxs { + ctx := ctx + testNum++ - go func(ctx context.Context) { - f(ctx) + t.Run(fmt.Sprint(testNum), func(t *testing.T) { + t.Parallel() + f(ctx, t) + }) + } + }) - if cleanup { - th.snapshotter.CleanupClient(ctx) - } - - wg.Done() - }(ctx) + if !cleanup { + return } - wg.Wait() + for _, ctx := range ctxs { + th.snapshotter.CleanupClient(ctx) + } } // RunN creates client contexts, runs the provided function asynchronously for // each client, waits for all of them to finish, and cleans up clients. -func (th *TestHarness) RunN(ctx context.Context, numClients int, f func(context.Context)) { +func (th *TestHarness) RunN( //nolint:thelper + ctx context.Context, + t *testing.T, + numClients int, + f func(context.Context, *testing.T), +) { ctxs := NewClientContexts(ctx, numClients) - th.Run(ctxs, true, f) + th.Run(ctxs, t, true, f) } // Cleanup shuts down the engine and stops the test app. It requires a context diff --git a/tests/robustness/multiclient_test/framework/snapshotter.go b/tests/robustness/multiclient_test/framework/snapshotter.go index fea1277b2..cabb5ef53 100644 --- a/tests/robustness/multiclient_test/framework/snapshotter.go +++ b/tests/robustness/multiclient_test/framework/snapshotter.go @@ -159,6 +159,10 @@ func (mcs *MultiClientSnapshotter) CleanupClient(ctx context.Context) { delete(mcs.clients, c.ID) mcs.mu.Unlock() + if s == nil { + return + } + s.DisconnectClient(c.ID) s.Cleanup() mcs.server.RemoveClient(c.ID) diff --git a/tests/robustness/multiclient_test/multiclient_test.go b/tests/robustness/multiclient_test/multiclient_test.go index 412f0742a..273051e83 100644 --- a/tests/robustness/multiclient_test/multiclient_test.go +++ b/tests/robustness/multiclient_test/multiclient_test.go @@ -37,7 +37,7 @@ func TestManySmallFiles(t *testing.T) { fiofilewriter.MinNumFilesPerWriteField: strconv.Itoa(numFiles), } - f := func(ctx context.Context) { + f := func(ctx context.Context, t *testing.T) { //nolint:thelper err := tryRestoreIntoDataDirectory(ctx, t) require.NoError(t, err) @@ -52,7 +52,7 @@ func TestManySmallFiles(t *testing.T) { } ctx := testlogging.Context(t) - th.RunN(ctx, numClients, f) + th.RunN(ctx, t, numClients, f) } func TestOneLargeFile(t *testing.T) { @@ -68,7 +68,7 @@ func TestOneLargeFile(t *testing.T) { fiofilewriter.MinNumFilesPerWriteField: strconv.Itoa(numFiles), } - f := func(ctx context.Context) { + f := func(ctx context.Context, t *testing.T) { //nolint:thelper err := tryRestoreIntoDataDirectory(ctx, t) require.NoError(t, err) @@ -83,7 +83,7 @@ func TestOneLargeFile(t *testing.T) { } ctx := testlogging.Context(t) - th.RunN(ctx, numClients, f) + th.RunN(ctx, t, numClients, f) } func TestManySmallFilesAcrossDirecoryTree(t *testing.T) { @@ -103,7 +103,7 @@ func TestManySmallFilesAcrossDirecoryTree(t *testing.T) { engine.ActionRepeaterField: strconv.Itoa(actionRepeats), } - f := func(ctx context.Context) { + f := func(ctx context.Context, t *testing.T) { //nolint:thelper err := tryRestoreIntoDataDirectory(ctx, t) require.NoError(t, err) @@ -118,7 +118,7 @@ func TestManySmallFilesAcrossDirecoryTree(t *testing.T) { } ctx := testlogging.Context(t) - th.RunN(ctx, numClients, f) + th.RunN(ctx, t, numClients, f) } func TestRandomizedSmall(t *testing.T) { @@ -141,7 +141,7 @@ func TestRandomizedSmall(t *testing.T) { }, } - f := func(ctx context.Context) { + f := func(ctx context.Context, t *testing.T) { //nolint:thelper err := tryRestoreIntoDataDirectory(ctx, t) require.NoError(t, err) @@ -152,13 +152,11 @@ func TestRandomizedSmall(t *testing.T) { } ctx := testlogging.Context(t) - th.RunN(ctx, numClients, f) + th.RunN(ctx, t, numClients, f) } -// tryExecAction runs eng.ExecAction on the given parameters and masks no-op errors. -func tryRestoreIntoDataDirectory(ctx context.Context, t *testing.T) error { - t.Helper() - +// tryRestoreIntoDataDirectory runs eng.ExecAction on the given parameters and masks no-op errors. +func tryRestoreIntoDataDirectory(ctx context.Context, t *testing.T) error { //nolint:thelper _, err := eng.ExecAction(ctx, engine.RestoreIntoDataDirectoryActionKey, nil) if errors.Is(err, robustness.ErrNoOp) { t.Log("Action resulted in no-op") @@ -169,9 +167,7 @@ func tryRestoreIntoDataDirectory(ctx context.Context, t *testing.T) error { } // tryRandomAction runs eng.ExecAction on the given parameters and masks no-op errors. -func tryRandomAction(ctx context.Context, t *testing.T, opts engine.ActionOpts) error { - t.Helper() - +func tryRandomAction(ctx context.Context, t *testing.T, opts engine.ActionOpts) error { //nolint:thelper err := eng.RandomAction(ctx, opts) if errors.Is(err, robustness.ErrNoOp) { t.Log("Random action resulted in no-op")