Skip to content

Commit 453c6b4

Browse files
committed
Fix data races in unit tests
1 parent 32f3acc commit 453c6b4

File tree

3 files changed

+56
-23
lines changed

3 files changed

+56
-23
lines changed

internal/pkg/agent/application/upgrade/watcher_notwindows.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,12 @@ func TakedownWatcher(ctx context.Context, log *logger.Logger, pidFetchFunc watch
6565
}
6666
return accumulatedSignalingErrors
6767
}
68+
69+
func isProcessLive(process *os.Process) (bool, error) {
70+
signalErr := process.Signal(syscall.Signal(0))
71+
if signalErr != nil {
72+
return false, nil //nolint:nilerr // if we receive an error it means that the process is not running, so the check completed without errors
73+
} else {
74+
return true, nil
75+
}
76+
}

internal/pkg/agent/application/upgrade/watcher_test.go

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import (
1212
"os/exec"
1313
"path/filepath"
1414
"runtime"
15+
"strings"
1516
"sync"
16-
"syscall"
1717
"testing"
1818
"time"
1919

@@ -1043,7 +1043,7 @@ func Test_takedownWatcher(t *testing.T) {
10431043
}{
10441044
{
10451045
name: "no contention for watcher applocker",
1046-
setup: func(t *testing.T, log *logger.Logger, workdir string) (watcherPIDsFetcher, []testProcess) {
1046+
setup: func(_ *testing.T, _ *logger.Logger, _ string) (watcherPIDsFetcher, []testProcess) {
10471047
// nothing to do here, always return and empty list of pids
10481048
return func() ([]int, error) {
10491049
return nil, nil
@@ -1065,8 +1065,7 @@ func Test_takedownWatcher(t *testing.T) {
10651065
{
10661066
name: "contention with test binary listening to signals: test binary is terminated gracefully",
10671067
setup: func(t *testing.T, log *logger.Logger, workdir string) (watcherPIDsFetcher, []testProcess) {
1068-
1069-
cmd, testChan := createTestlockerCommand(t, log, applockerFileName, testExecutableAbsolutePath, workdir, false)
1068+
cmd, testChan := createTestlockerCommand(t, log.Named("testlocker"), applockerFileName, testExecutableAbsolutePath, workdir, false)
10701069
require.NoError(t, err, "error starting testlocker binary")
10711070

10721071
// wait for test binary to acquire lock
@@ -1087,7 +1086,7 @@ func Test_takedownWatcher(t *testing.T) {
10871086
require.NotNil(t, testlockerProcess, "test locker process info should have a not nil cmd")
10881087

10891088
require.Eventually(t, func() bool {
1090-
running, checkErr := isProcessRunning(testlockerProcess.cmd)
1089+
running, checkErr := isProcessRunning(t, testlockerProcess.cmd)
10911090
if checkErr != nil {
10921091
t.Logf("error checking for testlocker process running: %s", checkErr.Error())
10931092
return false
@@ -1112,7 +1111,7 @@ func Test_takedownWatcher(t *testing.T) {
11121111
{
11131112
name: "contention with test binary not listening to signals: test binary is not terminated",
11141113
setup: func(t *testing.T, log *logger.Logger, workdir string) (watcherPIDsFetcher, []testProcess) {
1115-
cmd, waitChan := createTestlockerCommand(t, log, applockerFileName, testExecutableAbsolutePath, workdir, true)
1114+
cmd, waitChan := createTestlockerCommand(t, log.Named("testlocker"), applockerFileName, testExecutableAbsolutePath, workdir, true)
11161115
require.NoError(t, err, "error starting testlocker binary")
11171116

11181117
// wait for test binary to acquire lock
@@ -1134,7 +1133,7 @@ func Test_takedownWatcher(t *testing.T) {
11341133

11351134
// check that the process is still running for a time
11361135
assert.Never(t, func() bool {
1137-
running, checkErr := isProcessRunning(testlockerProcess.cmd)
1136+
running, checkErr := isProcessRunning(t, testlockerProcess.cmd)
11381137
if checkErr != nil {
11391138
t.Logf("error checking for testlocker process running: %s", checkErr.Error())
11401139
return false
@@ -1159,9 +1158,12 @@ func Test_takedownWatcher(t *testing.T) {
11591158
t.Run(tc.name, func(t *testing.T) {
11601159
workDir := t.TempDir()
11611160
log, obsLogs := loggertest.New(t.Name())
1161+
t.Cleanup(func() {
1162+
// however it ends, try to print out the logs of TakedownWatcher
1163+
loggertest.PrintObservedLogs(obsLogs.All(), t.Log)
1164+
})
11621165
pidFetcher, processInfos := tc.setup(t, log, workDir)
1163-
tc.wantErr(t, TakedownWatcher(t.Context(), log, pidFetcher))
1164-
t.Logf("test logs: %v", obsLogs)
1166+
tc.wantErr(t, TakedownWatcher(t.Context(), log.Named("TakedownWatcher"), pidFetcher))
11651167
if tc.assertPostTakedown != nil {
11661168
tc.assertPostTakedown(t, workDir, processInfos)
11671169
}
@@ -1195,30 +1197,30 @@ func createTestlockerCommand(t *testing.T, log *logger.Logger, applockerFileName
11951197
return watcherCmd, watchTerminated
11961198
}
11971199

1198-
func isProcessRunning(cmd *exec.Cmd) (bool, error) {
1200+
func isProcessRunning(t *testing.T, cmd *exec.Cmd) (bool, error) {
11991201
if cmd.Process == nil {
12001202
return false, nil
12011203
}
1202-
1204+
t.Logf("checking if pid %d is still running", cmd.Process.Pid)
12031205
// search for the pid on the running processes
12041206
process, err := os.FindProcess(cmd.Process.Pid)
12051207
if err != nil {
1208+
t.Logf("error string: %q", err.Error())
1209+
if runtime.GOOS == "windows" && strings.Contains(err.Error(), "The parameter is incorrect") {
1210+
// in windows, noone can hear you scream
1211+
// invalid parameter means that the process object cannot be found
1212+
t.Logf("pid %d is not running because on windows we got an incorrect parameter error", cmd.Process.Pid)
1213+
return false, nil
1214+
}
1215+
1216+
t.Logf("error finding process: %T %v", err, err)
12061217
return false, err
12071218
}
12081219

12091220
if process == nil {
1221+
t.Logf("pid %d is not running because os.GetProcess returned a nil process", cmd.Process.Pid)
12101222
return false, nil
12111223
}
1212-
// if process is not nil we need to split between unix and non-unix OSes
1213-
if runtime.GOOS == "windows" {
1214-
return true, nil
1215-
} else {
1216-
// on unix system we always get a process back, we need to do some further checks
1217-
signalErr := cmd.Process.Signal(syscall.Signal(0))
1218-
if signalErr != nil {
1219-
return false, nil //nolint:nilerr // if we receive an error it means that the process is not running, so the check completed without errors
1220-
} else {
1221-
return true, nil
1222-
}
1223-
}
1224+
1225+
return isProcessLive(cmd.Process)
12241226
}

internal/pkg/agent/application/upgrade/watcher_windows.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,25 @@ func signalPID(log *logger.Logger, pid int) error {
130130

131131
return nil
132132
}
133+
134+
func isProcessLive(process *os.Process) (bool, error) {
135+
//exitCodeStillActive according to https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess
136+
const exitCodeStillActive = 259
137+
// Open the process with PROCESS_QUERY_LIMITED_INFORMATION access
138+
handle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, uint32(process.Pid))
139+
if err != nil {
140+
return false, fmt.Errorf("OpenProcess failed: %w", err)
141+
}
142+
143+
defer func(handle windows.Handle) {
144+
_ = windows.CloseHandle(handle)
145+
}(handle)
146+
147+
var exitCode uint32
148+
err = windows.GetExitCodeProcess(handle, &exitCode)
149+
if err != nil {
150+
return false, fmt.Errorf("getting process exit code: %w", err)
151+
}
152+
153+
return exitCode == exitCodeStillActive, nil
154+
}

0 commit comments

Comments
 (0)