Skip to content

Commit 7a64653

Browse files
fix(tests): improve signal handling and error reporting in lock tests (#622)
* fix(tests): improve signal handling and error reporting in lock tests * fix(tests): prevent parallel execution in interrupt signal tests * fix(tests): enhance signal handling by adding support for SIGTERM
1 parent 6b42080 commit 7a64653

3 files changed

Lines changed: 62 additions & 46 deletions

File tree

lock/lock_test.go

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package lock
33
import (
44
"bytes"
55
"context"
6+
"errors"
67
"fmt"
78
"os"
89
"os/exec"
@@ -269,7 +270,7 @@ func TestLockIsRemovedAfterInterruptSignal(t *testing.T) {
269270
if platform.IsWindows() {
270271
t.Skip("cannot send a signal to a child process in Windows")
271272
}
272-
t.Parallel()
273+
// don't run in parallel
273274
lockfile := getTempfile(t)
274275

275276
var err error
@@ -285,24 +286,23 @@ func TestLockIsRemovedAfterInterruptSignal(t *testing.T) {
285286
require.NoError(t, err, "starting child process")
286287

287288
time.Sleep(time.Second)
288-
err = cmd.Process.Signal(syscall.SIGINT)
289+
err = cmd.Process.Signal(os.Interrupt)
289290
require.NoError(t, err, "sending interrupt signal to child process")
290291

291292
err = cmd.Wait()
292-
if err != nil {
293-
assert.NoError(t, err, "waiting for child process to finish")
294-
}
295-
if buffer.Len() == 0 {
296-
t.Skip(`test inconclusive: command sometimes not starting on macOS ¯\_(ツ)_/¯`)
293+
if isSignalError(err) {
294+
t.Skip("inconclusive test: command failed to run properly - flaky test on macOS only")
297295
}
296+
assert.NoError(t, err, "waiting for child process to finish")
297+
298298
assert.Equal(t, "started\nlock acquired\ntask interrupted\nlock released\n", buffer.String())
299299
}
300300

301301
func TestLockIsRemovedAfterInterruptSignalInsideShell(t *testing.T) {
302302
if platform.IsWindows() {
303303
t.Skip("cannot send a signal to a child process in Windows")
304304
}
305-
t.Parallel()
305+
// don't run in parallel
306306
lockfile := getTempfile(t)
307307

308308
var err error
@@ -318,15 +318,28 @@ func TestLockIsRemovedAfterInterruptSignalInsideShell(t *testing.T) {
318318
require.NoError(t, err, "starting child process inside a shell")
319319

320320
time.Sleep(time.Second)
321-
err = cmd.Process.Signal(syscall.SIGINT)
321+
err = cmd.Process.Signal(os.Interrupt)
322322
require.NoError(t, err, "sending interrupt signal to child process")
323323

324324
err = cmd.Wait()
325-
if err != nil {
326-
assert.NoError(t, err, "waiting for child process to finish")
327-
}
328-
if buffer.Len() == 0 {
329-
t.Skip(`test inconclusive: command sometimes not starting on macOS ¯\_(ツ)_/¯`)
325+
if isSignalError(err) {
326+
t.Skip("inconclusive test: command failed to run properly - flaky test on macOS only")
330327
}
328+
assert.NoError(t, err, "waiting for child process to finish")
329+
331330
assert.Equal(t, "started\nlock acquired\ntask interrupted\nlock released\n", buffer.String())
332331
}
332+
333+
func isSignalError(err error) bool {
334+
if err == nil {
335+
return false
336+
}
337+
exitErr, ok := errors.AsType[*exec.ExitError](err)
338+
if !ok {
339+
return false
340+
}
341+
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
342+
return status.Signaled()
343+
}
344+
return false
345+
}

lock/test/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ func main() {
2727
}
2828

2929
func run(wait int, lockfile string) int {
30-
// Catch CTR-C key
3130
sigChan := make(chan os.Signal, 2)
32-
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGABRT)
31+
signal.Ignore()
32+
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
3333
// remove signal catch before leaving
3434
defer signal.Stop(sigChan)
3535

wrapper_test.go

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"slices"
1616
"strings"
1717
"testing"
18+
"testing/synctest"
1819
"time"
1920

2021
"github.com/creativeprojects/clog"
@@ -914,38 +915,40 @@ func TestBackupWithError(t *testing.T) {
914915
func TestBackupWithResticLockFailureRetried(t *testing.T) {
915916
t.Parallel()
916917

917-
lockWait := constants.MinResticLockRetryDelay + time.Second
918-
lockMessage := "unable to create lock in backend: repository is already locked exclusively by PID 60485 on VM by user (UID 503, GID 23)" + platform.LineSeparator +
919-
"lock was created at 2023-09-24 15:29:57 (69.406ms ago)" + platform.LineSeparator +
920-
"storage ID c8a44e77" + platform.LineSeparator +
921-
"the `unlock` command can be used to remove stale locks" + platform.LineSeparator
922-
tempfile := filepath.Join(t.TempDir(), "TestBackupWithResticLockFailureRetried.txt")
923-
err := os.WriteFile(tempfile, []byte(lockMessage), 0o600)
924-
require.NoError(t, err)
925-
defer os.Remove(tempfile)
918+
synctest.Test(t, func(t *testing.T) {
919+
lockWait := constants.MinResticLockRetryDelay + time.Second
920+
lockMessage := "unable to create lock in backend: repository is already locked exclusively by PID 60485 on VM by user (UID 503, GID 23)" + platform.LineSeparator +
921+
"lock was created at 2023-09-24 15:29:57 (69.406ms ago)" + platform.LineSeparator +
922+
"storage ID c8a44e77" + platform.LineSeparator +
923+
"the `unlock` command can be used to remove stale locks" + platform.LineSeparator
924+
tempfile := filepath.Join(t.TempDir(), "TestBackupWithResticLockFailureRetried.txt")
925+
err := os.WriteFile(tempfile, []byte(lockMessage), 0o600)
926+
require.NoError(t, err)
927+
defer os.Remove(tempfile)
926928

927-
sigChan := make(chan os.Signal, 1)
928-
global := &config.Global{
929-
ResticLockRetryAfter: lockWait,
930-
}
931-
profile := config.NewProfile(nil, "name")
932-
profile.Backup = &config.BackupSection{}
933-
ctx := &Context{
934-
global: global,
935-
binary: mockBinary,
936-
profile: profile,
937-
command: "",
938-
request: Request{arguments: []string{"--stderr", "@" + tempfile, "--exit", "1"}},
939-
sigChan: sigChan,
940-
terminal: term.NewTerminal(),
941-
}
942-
wrapper := newResticWrapper(ctx)
943-
wrapper.lockWait = &lockWait
944-
wrapper.startTime = time.Now()
929+
sigChan := make(chan os.Signal, 1)
930+
global := &config.Global{
931+
ResticLockRetryAfter: lockWait,
932+
}
933+
profile := config.NewProfile(nil, "name")
934+
profile.Backup = &config.BackupSection{}
935+
ctx := &Context{
936+
global: global,
937+
binary: mockBinary,
938+
profile: profile,
939+
command: "",
940+
request: Request{arguments: []string{"--stderr", "@" + tempfile, "--exit", "1"}},
941+
sigChan: sigChan,
942+
terminal: term.NewTerminal(),
943+
}
944+
wrapper := newResticWrapper(ctx)
945+
wrapper.lockWait = &lockWait
946+
wrapper.startTime = time.Now()
945947

946-
err = wrapper.runCommand("backup")
947-
assert.Error(t, err)
948-
assert.NotErrorIs(t, err, errInterrupt)
948+
err = wrapper.runCommand("backup")
949+
assert.Error(t, err)
950+
assert.NotErrorIs(t, err, errInterrupt)
951+
})
949952
}
950953

951954
func TestBackupWithResticLockFailureCancelled(t *testing.T) {

0 commit comments

Comments
 (0)