From 1cf5620739588459b27f1d9a76e154bcce3f0013 Mon Sep 17 00:00:00 2001 From: Viktor Tsapovskiy Date: Fri, 10 Apr 2026 09:32:39 +0300 Subject: [PATCH 1/7] helpers: switch instead chained if/else Using switch over chained if/else for grouped validations. Using errors.New and fmt.Errorf with %w for proper error wrapping Part of #485 --- test_helpers/pool_helper.go | 44 +++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/test_helpers/pool_helper.go b/test_helpers/pool_helper.go index 8e134e8b4..0213142a6 100644 --- a/test_helpers/pool_helper.go +++ b/test_helpers/pool_helper.go @@ -133,33 +133,37 @@ func Retry(f func(interface{}) error, args interface{}, count int, timeout time. func InsertOnInstance(ctx context.Context, dialer tarantool.Dialer, connOpts tarantool.Opts, space interface{}, tuple interface{}) error { conn, err := tarantool.Connect(ctx, dialer, connOpts) - if err != nil { - return fmt.Errorf("fail to connect: %s", err.Error()) - } - if conn == nil { + + switch { + case err != nil: + return fmt.Errorf("fail to connect: %w", err) + case conn == nil: return fmt.Errorf("conn is nil after Connect") } + defer func() { _ = conn.Close() }() data, err := conn.Do(tarantool.NewInsertRequest(space).Tuple(tuple)).Get() - if err != nil { - return fmt.Errorf("failed to Insert: %s", err.Error()) - } - if len(data) != 1 { + + switch { + case err != nil: + return fmt.Errorf("failed to Insert: %w", err) + case len(data) != 1: return fmt.Errorf("response Body len != 1") } - if tpl, ok := data[0].([]interface{}); !ok { + + tpl, ok := data[0].([]interface{}) + if !ok { return fmt.Errorf("unexpected body of Insert") - } else { - expectedTpl, ok := tuple.([]interface{}) - if !ok { - return fmt.Errorf("failed to cast") - } + } - err = compareTuples(expectedTpl, tpl) - if err != nil { - return err - } + expectedTpl, ok := tuple.([]interface{}) + if !ok { + return fmt.Errorf("failed to cast") + } + + if err := compareTuples(expectedTpl, tpl); err != nil { + return err } return nil @@ -179,7 +183,7 @@ func InsertOnInstances( err := SetClusterRO(ctx, dialers, connOpts, roles) if err != nil { - return fmt.Errorf("fail to set roles for cluster: %s", err.Error()) + return fmt.Errorf("fail to set roles for cluster: %w", err) } errs := make([]error, len(dialers)) @@ -214,6 +218,7 @@ func SetInstanceRO(ctx context.Context, dialer tarantool.Dialer, connOpts tarant checkRole := func(conn *tarantool.Connection, isReplica bool) string { data, err := conn.Do(tarantool.NewCallRequest("box.info")).Get() + switch { case err != nil: return fmt.Sprintf("failed to get box.info: %s", err) @@ -228,6 +233,7 @@ func SetInstanceRO(ctx context.Context, dialer tarantool.Dialer, connOpts tarant status, statusFound := boxInfo["status"] readonly, readonlyFound := boxInfo["ro"] + switch { case !statusFound: return "box.info.status is missing" From 36b3b72cb0c389acb90f564e3f915969dfde6b10 Mon Sep 17 00:00:00 2001 From: Viktor Tsapovskiy Date: Thu, 16 Apr 2026 00:25:05 +0300 Subject: [PATCH 2/7] helpers: use typed decoding (GetTyped) Used GetTyped(&data) instead Get() for expected type. Part of #485 --- test_helpers/pool_helper.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/test_helpers/pool_helper.go b/test_helpers/pool_helper.go index 0213142a6..61c6ea0ac 100644 --- a/test_helpers/pool_helper.go +++ b/test_helpers/pool_helper.go @@ -86,20 +86,16 @@ func ProcessListenOnInstance(args interface{}) error { for i := 0; i < listenArgs.ServersNumber; i++ { req := tarantool.NewEvalRequest("return box.cfg.listen") - data, err := listenArgs.ConnPool.Do(req, listenArgs.Mode).Get() + var data []string + err := listenArgs.ConnPool.Do(req, listenArgs.Mode).GetTyped(&data) if err != nil { - return fmt.Errorf("fail to Eval: %s", err.Error()) + return fmt.Errorf("failed to get response: %w", err) } - if len(data) < 1 { - return fmt.Errorf("response.Data is empty after Eval") + if len(data) == 0 { + return errors.New("empty response from Eval") } - port, ok := data[0].(string) - if !ok { - return fmt.Errorf("response.Data is incorrect after Eval") - } - - actualPorts[port] = true + actualPorts[data[0]] = true } equal := reflect.DeepEqual(actualPorts, listenArgs.ExpectedPorts) From 68d3492e2a575295640b40f4ad34b07519d05e48 Mon Sep 17 00:00:00 2001 From: Viktor Tsapovskiy Date: Thu, 16 Apr 2026 01:46:39 +0300 Subject: [PATCH 3/7] helpers: custom comparing func instead DeepEqual Added comparePortMaps for deep comparing map[string]bool. Used in ProcessListenOnInstance. Part of #485 --- test_helpers/pool_helper.go | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/test_helpers/pool_helper.go b/test_helpers/pool_helper.go index 61c6ea0ac..e537cc13e 100644 --- a/test_helpers/pool_helper.go +++ b/test_helpers/pool_helper.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "reflect" "sync" "time" @@ -41,6 +40,26 @@ func compareTuples(expectedTpl []interface{}, actualTpl []interface{}) error { return nil } +func comparePortMaps(expected map[string]bool, actual map[string]bool) error { + if len(actual) != len(expected) { + return fmt.Errorf("unexpected number of ports: expected %d, actual %d", + len(expected), len(actual)) + } + + for port, expectedValue := range expected { + actualValue, ok := actual[port] + if !ok { + return fmt.Errorf("missing expected port: %s", port) + } + if actualValue != expectedValue { + return fmt.Errorf("unexpected value for port %s: expected %t, actual %t", + port, expectedValue, actualValue) + } + } + + return nil +} + func CheckPoolStatuses(args interface{}) error { checkArgs, ok := args.(CheckStatusesArgs) if !ok { @@ -98,10 +117,8 @@ func ProcessListenOnInstance(args interface{}) error { actualPorts[data[0]] = true } - equal := reflect.DeepEqual(actualPorts, listenArgs.ExpectedPorts) - if !equal { - return fmt.Errorf("expected ports: %v, actual ports: %v", - listenArgs.ExpectedPorts, actualPorts) + if err := comparePortMaps(listenArgs.ExpectedPorts, actualPorts); err != nil { + return err } return nil From adafb8dd7da3296b30f829e87f164e913f46625b Mon Sep 17 00:00:00 2001 From: Viktor Tsapovskiy Date: Thu, 16 Apr 2026 14:05:05 +0300 Subject: [PATCH 4/7] helpers: replace custom Retry with assert.Eventually New pattern look like this: ``` assert.Eventually(t, func() bool { err = f(args) return err == nil }, defaultTimeoutRetry, defaultTickRetry) ``` Part of #485 --- pool/connection_pool_test.go | 137 ++++++++++++++++++++++------------- shutdown_test.go | 27 +++---- test_helpers/pool_helper.go | 19 ----- test_helpers/utils.go | 17 ++--- 4 files changed, 105 insertions(+), 95 deletions(-) diff --git a/pool/connection_pool_test.go b/pool/connection_pool_test.go index 1598938a3..129f73f7e 100644 --- a/pool/connection_pool_test.go +++ b/pool/connection_pool_test.go @@ -95,8 +95,10 @@ var connOpts = tarantool.Opts{ Timeout: 5 * time.Second, } -var defaultCountRetry = 5 -var defaultTimeoutRetry = 500 * time.Millisecond +const defaultCountRetry = 5 + +const tick = 500 * time.Millisecond +const timeout = defaultCountRetry * tick var helpInstances []*test_helpers.TarantoolInstance @@ -437,9 +439,10 @@ func TestReconnect(t *testing.T) { server: false, }, } - - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) err = test_helpers.RestartTarantool(helpInstances[0]) @@ -454,9 +457,10 @@ func TestReconnect(t *testing.T) { server: true, }, } - - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) } @@ -486,8 +490,10 @@ func TestDisconnect_withReconnect(t *testing.T) { servers[serverId]: false, }, } - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, - args, defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) // Restart the server after success. @@ -503,9 +509,10 @@ func TestDisconnect_withReconnect(t *testing.T) { servers[serverId]: true, }, } - - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, - args, defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) } @@ -534,9 +541,10 @@ func TestDisconnectAll(t *testing.T) { server2: false, }, } - - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) err = test_helpers.RestartTarantool(helpInstances[0]) @@ -555,9 +563,10 @@ func TestDisconnectAll(t *testing.T) { server2: true, }, } - - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) } @@ -592,8 +601,10 @@ func TestAdd(t *testing.T) { }, } - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) } @@ -639,8 +650,10 @@ func TestAdd_exist(t *testing.T) { }, } - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) } @@ -676,8 +689,10 @@ func TestAdd_unreachable(t *testing.T) { }, } - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) } @@ -781,8 +796,10 @@ func TestRemove(t *testing.T) { }, } - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) } @@ -810,8 +827,10 @@ func TestRemove_double(t *testing.T) { }, } - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) } @@ -838,8 +857,10 @@ func TestRemove_unknown(t *testing.T) { }, } - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) } @@ -887,8 +908,10 @@ func TestRemove_concurrent(t *testing.T) { }, } - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) } @@ -972,8 +995,10 @@ func TestClose(t *testing.T) { }, } - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) } @@ -1035,8 +1060,10 @@ func TestCloseGraceful(t *testing.T) { }, } - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) } @@ -1397,8 +1424,10 @@ func TestRequestOnClosed(t *testing.T) { server2: false, }, } - err = test_helpers.Retry(test_helpers.CheckPoolStatuses, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.CheckPoolStatuses(args) + return err == nil + }, timeout, tick) require.NoError(t, err) _, err = connPool.Do(tarantool.NewPingRequest(), pool.ANY).Get() @@ -1968,8 +1997,10 @@ func TestUpdateInstancesRoles(t *testing.T) { Mode: pool.ANY, } - err = test_helpers.Retry(test_helpers.ProcessListenOnInstance, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.ProcessListenOnInstance(args) + return err == nil + }, timeout, tick) require.NoError(t, err) // RW @@ -1980,8 +2011,10 @@ func TestUpdateInstancesRoles(t *testing.T) { Mode: pool.RW, } - err = test_helpers.Retry(test_helpers.ProcessListenOnInstance, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.ProcessListenOnInstance(args) + return err == nil + }, timeout, tick) require.NoError(t, err) // RO @@ -1992,8 +2025,10 @@ func TestUpdateInstancesRoles(t *testing.T) { Mode: pool.RO, } - err = test_helpers.Retry(test_helpers.ProcessListenOnInstance, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.ProcessListenOnInstance(args) + return err == nil + }, timeout, tick) require.NoError(t, err) // PreferRW @@ -2004,8 +2039,10 @@ func TestUpdateInstancesRoles(t *testing.T) { Mode: pool.PreferRW, } - err = test_helpers.Retry(test_helpers.ProcessListenOnInstance, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.ProcessListenOnInstance(args) + return err == nil + }, timeout, tick) require.NoError(t, err) // PreferRO @@ -2016,8 +2053,10 @@ func TestUpdateInstancesRoles(t *testing.T) { Mode: pool.PreferRO, } - err = test_helpers.Retry(test_helpers.ProcessListenOnInstance, args, - defaultCountRetry, defaultTimeoutRetry) + assert.Eventually(t, func() bool { + err = test_helpers.ProcessListenOnInstance(args) + return err == nil + }, timeout, tick) require.NoError(t, err) } diff --git a/shutdown_test.go b/shutdown_test.go index 0d4ffeb5f..45d449481 100644 --- a/shutdown_test.go +++ b/shutdown_test.go @@ -5,7 +5,6 @@ package tarantool_test import ( - "fmt" "sync" "syscall" "testing" @@ -84,24 +83,16 @@ func testGracefulShutdown(t *testing.T, conn *Connection, inst *test_helpers.Tar require.NoError(t, inst.Signal(syscall.SIGTERM)) // Check that we can't send new requests after shutdown starts. - // Retry helps to wait a bit until server starts to shutdown + // assert.Eventually helps to wait a bit until server starts to shutdown // and send us the shutdown event. - shutdownWaitRetries := 5 - shutdownWaitTimeout := 100 * time.Millisecond - - err = test_helpers.Retry(func(interface{}) error { - _, err = conn.Do(NewPingRequest()).Get() - if err == nil { - return fmt.Errorf("expected error for requests sent on shutdown") - } - - if err.Error() != "server shutdown in progress (0x4005)" { - return err - } - - return nil - }, nil, shutdownWaitRetries, shutdownWaitTimeout) - require.NoError(t, err) + const shutdownWaitRetries = 5 + tick := 100 * time.Millisecond + timeout := time.Duration(shutdownWaitRetries) * tick + + require.Eventually(t, func() bool { + _, err := conn.Do(NewPingRequest()).Get() + return err != nil && err.Error() == "server shutdown in progress (0x4005)" + }, timeout, tick, "expected shutdown error 'server shutdown in progress (0x4005)'") // Check that requests started before the shutdown finish successfully. data, err := fut.Get() diff --git a/test_helpers/pool_helper.go b/test_helpers/pool_helper.go index e537cc13e..0a6741ee9 100644 --- a/test_helpers/pool_helper.go +++ b/test_helpers/pool_helper.go @@ -124,25 +124,6 @@ func ProcessListenOnInstance(args interface{}) error { return nil } -func Retry(f func(interface{}) error, args interface{}, count int, timeout time.Duration) error { - var err error - - for i := 0; ; i++ { - err = f(args) - if err == nil { - return err - } - - if i >= (count - 1) { - break - } - - time.Sleep(timeout) - } - - return err -} - func InsertOnInstance(ctx context.Context, dialer tarantool.Dialer, connOpts tarantool.Opts, space interface{}, tuple interface{}) error { conn, err := tarantool.Connect(ctx, dialer, connOpts) diff --git a/test_helpers/utils.go b/test_helpers/utils.go index f3d6dd78d..8faaacf6e 100644 --- a/test_helpers/utils.go +++ b/test_helpers/utils.go @@ -49,16 +49,15 @@ func DeleteRecordByKey(t T, conn tarantool.Connector, // Returns false in case of connection is not in the connected state // after specified retries count, true otherwise. func WaitUntilReconnected(conn *tarantool.Connection, retries uint, timeout time.Duration) bool { - err := Retry(func(arg interface{}) error { - conn := arg.(*tarantool.Connection) - connected := conn.ConnectedNow() - if !connected { - return fmt.Errorf("not connected") + for i := 0; i < int(retries); i++ { + if conn.ConnectedNow() { + return true } - return nil - }, conn, int(retries), timeout) - - return err == nil + if i < int(retries)-1 { + time.Sleep(timeout) + } + } + return conn.ConnectedNow() } func SkipIfSQLUnsupported(t T) { From a5e864a3100a5ecce0074227951047dfa547f496 Mon Sep 17 00:00:00 2001 From: Viktor Tsapovskiy Date: Thu, 16 Apr 2026 14:49:47 +0300 Subject: [PATCH 5/7] helpers: add reusable helper test_helpers.ExecuteOnAll InsertOnInstances and SetClusterRO use ExecuteOnAll to execute on all instances in parallel. Minor changes: * checkRole in SetInstanceRO returns error instead string. * structured box API instead of raw CallRequest used in checkRole. Part of #485 --- test_helpers/pool_helper.go | 105 +++++++++++++++++------------------- 1 file changed, 50 insertions(+), 55 deletions(-) diff --git a/test_helpers/pool_helper.go b/test_helpers/pool_helper.go index 0a6741ee9..6c858d82f 100644 --- a/test_helpers/pool_helper.go +++ b/test_helpers/pool_helper.go @@ -8,6 +8,7 @@ import ( "time" "github.com/tarantool/go-tarantool/v3" + "github.com/tarantool/go-tarantool/v3/box" "github.com/tarantool/go-tarantool/v3/pool" ) @@ -180,19 +181,9 @@ func InsertOnInstances( return fmt.Errorf("fail to set roles for cluster: %w", err) } - errs := make([]error, len(dialers)) - var wg sync.WaitGroup - wg.Add(len(dialers)) - for i, dialer := range dialers { - // Pass loop variable(s) to avoid its capturing by reference (not needed since Go 1.22). - go func(i int, dialer tarantool.Dialer) { - defer wg.Done() - errs[i] = InsertOnInstance(ctx, dialer, connOpts, space, tuple) - }(i, dialer) - } - wg.Wait() - - return errors.Join(errs...) + return ExecuteOnAll(ctx, dialers, func(_ context.Context, d tarantool.Dialer, _ int) error { + return InsertOnInstance(ctx, d, connOpts, space, tuple) + }) } func SetInstanceRO(ctx context.Context, dialer tarantool.Dialer, connOpts tarantool.Opts, @@ -210,75 +201,79 @@ func SetInstanceRO(ctx context.Context, dialer tarantool.Dialer, connOpts tarant return err } - checkRole := func(conn *tarantool.Connection, isReplica bool) string { - data, err := conn.Do(tarantool.NewCallRequest("box.info")).Get() - - switch { - case err != nil: - return fmt.Sprintf("failed to get box.info: %s", err) - case len(data) < 1: - return "box.info is empty" - } - - boxInfo, ok := data[0].(map[interface{}]interface{}) - if !ok { - return "unexpected type in box.info response" + checkRole := func(conn *tarantool.Connection, isReplica bool) error { + boxInfo, err := box.MustNew(conn).Info() + if err != nil { + return fmt.Errorf("failed to get box.info: %s", err) } - status, statusFound := boxInfo["status"] - readonly, readonlyFound := boxInfo["ro"] - switch { - case !statusFound: - return "box.info.status is missing" - case status != "running": - return fmt.Sprintf("box.info.status='%s' (waiting for 'running')", status) - case !readonlyFound: - return "box.info.ro is missing" - case readonly != isReplica: - return fmt.Sprintf("box.info.ro='%v' (waiting for '%v')", readonly, isReplica) + case boxInfo.Status != "running": + return fmt.Errorf("box.info.status='%s' (waiting for 'running')", boxInfo.Status) + case boxInfo.RO != isReplica: + return fmt.Errorf("box.info.ro='%v' (waiting for '%v')", boxInfo.RO, isReplica) default: - return "" + return nil } } - problem := "not checked yet" + err = errors.New("not checked yet") // Wait for the role to be applied. - for len(problem) != 0 { + for err != nil { select { case <-time.After(10 * time.Millisecond): case <-ctx.Done(): - return fmt.Errorf("%w: failed to apply role, the last problem: %s", - ctx.Err(), problem) + return fmt.Errorf("%w: failed to apply role, the last error: %s", + ctx.Err(), err) } - problem = checkRole(conn, isReplica) + err = checkRole(conn, isReplica) } return nil } -func SetClusterRO(ctx context.Context, dialers []tarantool.Dialer, connOpts tarantool.Opts, - roles []bool) error { - if len(dialers) != len(roles) { - return fmt.Errorf("number of servers should be equal to number of roles") - } - - // Apply roles in parallel. - errs := make([]error, len(dialers)) +func ExecuteOnAll(ctx context.Context, dialers []tarantool.Dialer, fn func(context.Context, tarantool.Dialer, int) error) error { var wg sync.WaitGroup + var errs []error + var mu sync.Mutex + wg.Add(len(dialers)) for i, dialer := range dialers { - // Pass loop variable(s) to avoid its capturing by reference (not needed since Go 1.22). - go func(i int, dialer tarantool.Dialer) { + go func(idx int, d tarantool.Dialer) { defer wg.Done() - errs[i] = SetInstanceRO(ctx, dialer, connOpts, roles[i]) + select { + case <-ctx.Done(): + mu.Lock() + errs = append(errs, fmt.Errorf("instance %d: %w", idx, ctx.Err())) + mu.Unlock() + default: + if err := fn(ctx, d, idx); err != nil { + mu.Lock() + errs = append(errs, fmt.Errorf("instance %d: %w", idx, err)) + mu.Unlock() + } + } }(i, dialer) } + wg.Wait() + if len(errs) > 0 { + return errors.Join(errs...) + } + return nil +} + +func SetClusterRO(ctx context.Context, dialers []tarantool.Dialer, connOpts tarantool.Opts, + roles []bool) error { + if len(dialers) != len(roles) { + return fmt.Errorf("number of servers should be equal to number of roles") + } - return errors.Join(errs...) + return ExecuteOnAll(ctx, dialers, func(_ context.Context, d tarantool.Dialer, idx int) error { + return SetInstanceRO(ctx, d, connOpts, roles[idx]) + }) } func StartTarantoolInstances(instsOpts []StartOpts) ([]*TarantoolInstance, error) { From 2f40eaa610a90a17f7e2c6b72a2892f419ffc50f Mon Sep 17 00:00:00 2001 From: Viktor Tsapovskiy Date: Thu, 16 Apr 2026 16:13:06 +0300 Subject: [PATCH 6/7] helpers: typed args instead of any in pool_helper Changed signature of ProcessListenOnInstance and ListenOnInstanceArgs using ListenOnInstanceArgs and CheckStatusesArgs instead of interface{}. Closes #485 --- test_helpers/pool_helper.go | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/test_helpers/pool_helper.go b/test_helpers/pool_helper.go index 6c858d82f..6b5949da9 100644 --- a/test_helpers/pool_helper.go +++ b/test_helpers/pool_helper.go @@ -61,26 +61,21 @@ func comparePortMaps(expected map[string]bool, actual map[string]bool) error { return nil } -func CheckPoolStatuses(args interface{}) error { - checkArgs, ok := args.(CheckStatusesArgs) - if !ok { - return fmt.Errorf("incorrect args") - } - - connected, _ := checkArgs.ConnPool.ConnectedNow(checkArgs.Mode) - if connected != checkArgs.ExpectedPoolStatus { +func CheckPoolStatuses(args CheckStatusesArgs) error { + connected, _ := args.ConnPool.ConnectedNow(args.Mode) + if connected != args.ExpectedPoolStatus { return fmt.Errorf( "incorrect connection pool status: expected status %t actual status %t", - checkArgs.ExpectedPoolStatus, connected) + args.ExpectedPoolStatus, connected) } - poolInfo := checkArgs.ConnPool.GetInfo() - for _, server := range checkArgs.Servers { + poolInfo := args.ConnPool.GetInfo() + for _, server := range args.Servers { status := poolInfo[server].ConnectedNow - if checkArgs.ExpectedStatuses[server] != status { + if args.ExpectedStatuses[server] != status { return fmt.Errorf( "incorrect conn status: addr %s expected status %t actual status %t", - server, checkArgs.ExpectedStatuses[server], status) + server, args.ExpectedStatuses[server], status) } } @@ -96,18 +91,13 @@ func CheckPoolStatuses(args interface{}) error { // ports or to all ports. // For PreferRW mode expected received ports equals to master ports // or to all ports. -func ProcessListenOnInstance(args interface{}) error { +func ProcessListenOnInstance(args ListenOnInstanceArgs) error { actualPorts := map[string]bool{} - listenArgs, ok := args.(ListenOnInstanceArgs) - if !ok { - return fmt.Errorf("incorrect args") - } - - for i := 0; i < listenArgs.ServersNumber; i++ { + for i := 0; i < args.ServersNumber; i++ { req := tarantool.NewEvalRequest("return box.cfg.listen") var data []string - err := listenArgs.ConnPool.Do(req, listenArgs.Mode).GetTyped(&data) + err := args.ConnPool.Do(req, args.Mode).GetTyped(&data) if err != nil { return fmt.Errorf("failed to get response: %w", err) } @@ -118,7 +108,7 @@ func ProcessListenOnInstance(args interface{}) error { actualPorts[data[0]] = true } - if err := comparePortMaps(listenArgs.ExpectedPorts, actualPorts); err != nil { + if err := comparePortMaps(args.ExpectedPorts, actualPorts); err != nil { return err } From 19c1e8e2ff8ffa15ef6b8feec2f0da1d8f79ac2a Mon Sep 17 00:00:00 2001 From: Oleg Jukovec Date: Sat, 25 Apr 2026 00:05:49 +0300 Subject: [PATCH 7/7] changelog: document test_helpers API changes --- CHANGELOG.md | 7 +++++++ test_helpers/pool_helper.go | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ac046a15..425e50852 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. * New `MockDoer` interface for custom `Doer` testing with builder pattern methods: `AddResponse`, `AddResponseRaw`, `AddResponseError`, `Requests`. * New `MockRequestNamed` type for verifying specific requests in tests. +* New `test_helpers.ExecuteOnAll` function to execute operations on all + instances in parallel with context support. ### Changed @@ -62,11 +64,16 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. become private, `SetError` and `SetResponse` become private (#470). * `ConnectionPool.Close()` returns a single error value, combining multiple errors using errors.Join() (#540). +* `test_helpers.CheckPoolStatuses` and `test_helpers.ProcessListenOnInstance` + now accept typed arguments (`CheckStatusesArgs` and `ListenOnInstanceArgs` + respectively) instead of `interface{}`. ### Removed * Deprecated `NewCall16Request` and `NewCall17Request` constructors. Use `NewCallRequest` instead. +* `test_helpers.Retry` function. Use `assert.Eventually` from testify instead. + `test_helpers.WaitUntilReconnected` reimplemented without `Retry`. ### Fixed diff --git a/test_helpers/pool_helper.go b/test_helpers/pool_helper.go index 6b5949da9..b55944a2a 100644 --- a/test_helpers/pool_helper.go +++ b/test_helpers/pool_helper.go @@ -224,7 +224,8 @@ func SetInstanceRO(ctx context.Context, dialer tarantool.Dialer, connOpts tarant return nil } -func ExecuteOnAll(ctx context.Context, dialers []tarantool.Dialer, fn func(context.Context, tarantool.Dialer, int) error) error { +func ExecuteOnAll(ctx context.Context, dialers []tarantool.Dialer, + fn func(context.Context, tarantool.Dialer, int) error) error { var wg sync.WaitGroup var errs []error var mu sync.Mutex