diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d1dceaca..69f745266 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,13 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. * Fixed the fluctuating behavior of the TestConnectionHandlerOpenUpdateClose test by increasing the waiting time (#502). +* On Linux, tarantool processes started by `test_helpers.StartTarantool` + are now terminated when the parent test process dies, preventing leaked + instances after a panic (#147). +* Reordered tests to defer `test_helpers.StopTarantoolWithCleanup` only + after asserting `StartTarantool` did not return an error, so a failed + start no longer panics with a nil-pointer dereference in the deferred + cleanup (#147). ## [v2.4.1] - 2025-10-16 diff --git a/arrow/tarantool_test.go b/arrow/tarantool_test.go index f99ef73dc..79265d52b 100644 --- a/arrow/tarantool_test.go +++ b/arrow/tarantool_test.go @@ -108,12 +108,11 @@ func runTestMain(m *testing.M) int { ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, }) - defer test_helpers.StopTarantoolWithCleanup(instance) - if err != nil { log.Printf("Failed to prepare test Tarantool: %s", err) return 1 } + defer test_helpers.StopTarantoolWithCleanup(instance) return m.Run() } diff --git a/crud/tarantool_test.go b/crud/tarantool_test.go index 08dde0587..03f177756 100644 --- a/crud/tarantool_test.go +++ b/crud/tarantool_test.go @@ -1446,12 +1446,11 @@ func TestYieldEveryOption(t *testing.T) { // https://stackoverflow.com/questions/27629380/how-to-exit-a-go-program-honoring-deferred-calls func runTestMain(m *testing.M) int { inst, err := test_helpers.StartTarantool(startOpts) - defer test_helpers.StopTarantoolWithCleanup(inst) - if err != nil { log.Printf("Failed to prepare test tarantool: %s", err) return 1 } + defer test_helpers.StopTarantoolWithCleanup(inst) return m.Run() } diff --git a/datetime/datetime_test.go b/datetime/datetime_test.go index 465d386c8..7d4aa78ed 100644 --- a/datetime/datetime_test.go +++ b/datetime/datetime_test.go @@ -981,12 +981,11 @@ func runTestMain(m *testing.M) int { ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, }) - defer test_helpers.StopTarantoolWithCleanup(instance) - if err != nil { log.Printf("Failed to prepare test Tarantool: %s", err) return 1 } + defer test_helpers.StopTarantoolWithCleanup(instance) return m.Run() } diff --git a/decimal/decimal_test.go b/decimal/decimal_test.go index a7f4cdc1c..50ba8c31e 100644 --- a/decimal/decimal_test.go +++ b/decimal/decimal_test.go @@ -599,12 +599,11 @@ func runTestMain(m *testing.M) int { ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, }) - defer test_helpers.StopTarantoolWithCleanup(instance) - if err != nil { log.Printf("Failed to prepare test Tarantool: %s", err) return 1 } + defer test_helpers.StopTarantoolWithCleanup(instance) return m.Run() } diff --git a/tarantool_test.go b/tarantool_test.go index 56dabf6ea..b654acfe1 100644 --- a/tarantool_test.go +++ b/tarantool_test.go @@ -2762,8 +2762,8 @@ func newWatcherReconnectionPrepareTestConnection(t *testing.T) (*Connection, con ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, }) - t.Cleanup(func() { test_helpers.StopTarantoolWithCleanup(inst) }) require.NoErrorf(t, err, "Unable to start Tarantool") + t.Cleanup(func() { test_helpers.StopTarantoolWithCleanup(inst) }) ctx, cancel := test_helpers.GetConnectContext() @@ -2843,8 +2843,8 @@ func TestConnection_NewWatcher_reconnect(t *testing.T) { ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, }) - defer test_helpers.StopTarantoolWithCleanup(inst) require.NoErrorf(t, err, "Unable to start Tarantool") + defer test_helpers.StopTarantoolWithCleanup(inst) reconnectOpts := opts reconnectOpts.Reconnect = 100 * time.Millisecond @@ -3097,8 +3097,8 @@ func TestConnection_named_index_after_reconnect(t *testing.T) { ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, }) - defer test_helpers.StopTarantoolWithCleanup(inst) require.NoErrorf(t, err, "Unable to start Tarantool") + defer test_helpers.StopTarantoolWithCleanup(inst) reconnectOpts := opts reconnectOpts.Reconnect = 100 * time.Millisecond @@ -3205,8 +3205,8 @@ func TestConnectIsBlocked(t *testing.T) { ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, }) - defer test_helpers.StopTarantoolWithCleanup(inst) require.NoErrorf(t, err, "Unable to start Tarantool") + defer test_helpers.StopTarantoolWithCleanup(inst) var counter int mockDialer := mockSlowDialer{original: testDialer, counter: &counter} diff --git a/test_helpers/cmd_linux.go b/test_helpers/cmd_linux.go new file mode 100644 index 000000000..9f46a7ce4 --- /dev/null +++ b/test_helpers/cmd_linux.go @@ -0,0 +1,21 @@ +//go:build linux + +package test_helpers + +import ( + "os/exec" + "syscall" +) + +// commandKillOnExit returns an *exec.Cmd that will be terminated by the +// kernel when the parent process dies, see +// https://github.com/golang/go/issues/37206. Without this, a panic in a +// test leaves the spawned tarantool process running and blocking the TCP +// port for subsequent runs (issue #147). +func commandKillOnExit(name string, arg ...string) *exec.Cmd { + cmd := exec.Command(name, arg...) + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGTERM, + } + return cmd +} diff --git a/test_helpers/cmd_other.go b/test_helpers/cmd_other.go new file mode 100644 index 000000000..933642fa6 --- /dev/null +++ b/test_helpers/cmd_other.go @@ -0,0 +1,14 @@ +//go:build !linux + +package test_helpers + +import ( + "os/exec" +) + +// commandKillOnExit is a fallback for platforms without a Pdeathsig +// equivalent. It just delegates to exec.Command, so a parent panic may +// leave the tarantool child alive (issue #147). +func commandKillOnExit(name string, arg ...string) *exec.Cmd { + return exec.Command(name, arg...) +} diff --git a/test_helpers/main.go b/test_helpers/main.go index 77f88fb3a..e00f39e75 100644 --- a/test_helpers/main.go +++ b/test_helpers/main.go @@ -430,7 +430,7 @@ func StartTarantool(startOpts StartOpts) (*TarantoolInstance, error) { args = append(args, "--config", startOpts.ConfigFile) args = append(args, "--name", startOpts.InstanceName) } - inst.Cmd = exec.Command(getTarantoolExec(), args...) + inst.Cmd = commandKillOnExit(getTarantoolExec(), args...) inst.Cmd.Dir = startOpts.WorkDir inst.Cmd.Env = append( diff --git a/uuid/uuid_test.go b/uuid/uuid_test.go index b5ad93b0e..2c47120e7 100644 --- a/uuid/uuid_test.go +++ b/uuid/uuid_test.go @@ -149,12 +149,11 @@ func runTestMain(m *testing.M) int { ConnectRetry: 10, RetryTimeout: 500 * time.Millisecond, }) - defer test_helpers.StopTarantoolWithCleanup(inst) - if err != nil { log.Printf("Failed to prepare test tarantool: %s", err) return 1 } + defer test_helpers.StopTarantoolWithCleanup(inst) return m.Run() }