mirror of https://github.com/coder/coder.git
103 lines
2.6 KiB
Go
103 lines
2.6 KiB
Go
//go:build linux
|
|
|
|
package reaper_test
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-reap"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/v2/agent/reaper"
|
|
"github.com/coder/coder/v2/testutil"
|
|
)
|
|
|
|
// TestReap checks that's the reaper is successfully reaping
|
|
// exited processes and passing the PIDs through the shared
|
|
// channel.
|
|
//
|
|
//nolint:paralleltest
|
|
func TestReap(t *testing.T) {
|
|
// Don't run the reaper test in CI. It does weird
|
|
// things like forkexecing which may have unintended
|
|
// consequences in CI.
|
|
if testutil.InCI() {
|
|
t.Skip("Detected CI, skipping reaper tests")
|
|
}
|
|
|
|
pids := make(reap.PidCh, 1)
|
|
err := reaper.ForkReap(
|
|
reaper.WithPIDCallback(pids),
|
|
// Provide some argument that immediately exits.
|
|
reaper.WithExecArgs("/bin/sh", "-c", "exit 0"),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
cmd := exec.Command("tail", "-f", "/dev/null")
|
|
err = cmd.Start()
|
|
require.NoError(t, err)
|
|
|
|
cmd2 := exec.Command("tail", "-f", "/dev/null")
|
|
err = cmd2.Start()
|
|
require.NoError(t, err)
|
|
|
|
err = cmd.Process.Kill()
|
|
require.NoError(t, err)
|
|
|
|
err = cmd2.Process.Kill()
|
|
require.NoError(t, err)
|
|
|
|
expectedPIDs := []int{cmd.Process.Pid, cmd2.Process.Pid}
|
|
|
|
for i := 0; i < len(expectedPIDs); i++ {
|
|
select {
|
|
case <-time.After(testutil.WaitShort):
|
|
t.Fatalf("Timed out waiting for process")
|
|
case pid := <-pids:
|
|
require.Contains(t, expectedPIDs, pid)
|
|
}
|
|
}
|
|
}
|
|
|
|
//nolint:paralleltest // Signal handling.
|
|
func TestReapInterrupt(t *testing.T) {
|
|
// Don't run the reaper test in CI. It does weird
|
|
// things like forkexecing which may have unintended
|
|
// consequences in CI.
|
|
if testutil.InCI() {
|
|
t.Skip("Detected CI, skipping reaper tests")
|
|
}
|
|
|
|
errC := make(chan error, 1)
|
|
pids := make(reap.PidCh, 1)
|
|
|
|
// Use signals to notify when the child process is ready for the
|
|
// next step of our test.
|
|
usrSig := make(chan os.Signal, 1)
|
|
signal.Notify(usrSig, syscall.SIGUSR1, syscall.SIGUSR2)
|
|
defer signal.Stop(usrSig)
|
|
|
|
go func() {
|
|
errC <- reaper.ForkReap(
|
|
reaper.WithPIDCallback(pids),
|
|
reaper.WithCatchSignals(os.Interrupt),
|
|
// Signal propagation does not extend to children of children, so
|
|
// we create a little bash script to ensure sleep is interrupted.
|
|
reaper.WithExecArgs("/bin/sh", "-c", fmt.Sprintf("pid=0; trap 'kill -USR2 %d; kill -TERM $pid' INT; sleep 10 &\npid=$!; kill -USR1 %d; wait", os.Getpid(), os.Getpid())),
|
|
)
|
|
}()
|
|
|
|
require.Equal(t, <-usrSig, syscall.SIGUSR1)
|
|
err := syscall.Kill(os.Getpid(), syscall.SIGINT)
|
|
require.NoError(t, err)
|
|
require.Equal(t, <-usrSig, syscall.SIGUSR2)
|
|
|
|
require.NoError(t, <-errC)
|
|
}
|