blob: 1db3154a735b4ab675784438450c4e038767708d [file] [log] [blame] [edit]
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime_test
import (
"bufio"
"bytes"
"errors"
"internal/testenv"
"io"
"os"
"os/exec"
"strings"
"syscall"
"testing"
)
// TestSignalPid1 verifies that a Go program running as PID 1 with no
// SIGTERM handler provides a sane exit code upon receiving SIGTERM.
//
// The test is Linux-specific because it uses CLONE_NEWPID to run as PID 1.
func TestSignalPid1(t *testing.T) {
t.Parallel()
exe, err := buildTestProg(t, "testprog")
if err != nil {
t.Fatal(err)
}
cmd := testenv.Command(t, exe, "SignalPid1")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWPID | syscall.CLONE_NEWUSER,
UidMappings: []syscall.SysProcIDMap{
{ContainerID: 0, HostID: os.Getuid(), Size: 1},
},
GidMappings: []syscall.SysProcIDMap{
{ContainerID: 0, HostID: os.Getgid(), Size: 1},
},
}
stdout, err := cmd.StdoutPipe()
if err != nil {
t.Fatal(err)
}
var stderr bytes.Buffer
cmd.Stderr = &stderr
if err := cmd.Start(); err != nil {
t.Skipf("cannot create PID namespace (may require unprivileged user namespaces): %v", err)
}
waited := false
defer func() {
if !waited {
cmd.Process.Kill()
cmd.Wait()
}
}()
// Wait for child to signal readiness.
r := bufio.NewReader(stdout)
line, err := r.ReadString('\n')
if err != nil {
t.Fatalf("reading from child: %v", err)
}
if strings.TrimRight(line, "\n") != "ready" {
t.Fatalf("unexpected output from child: %q", line)
}
go io.Copy(io.Discard, r) // Drain any further output.
const (
signal = syscall.SIGTERM
expExitCode = int(128 + signal)
)
// Send signal from outside the child PID namespace.
if err := cmd.Process.Signal(signal); err != nil {
t.Fatalf("sending signal %d (%q): %v", signal, signal, err)
}
err = cmd.Wait()
waited = true
t.Logf("child: %v", err)
if s := stderr.String(); s != "" {
t.Fatalf("child stderr: %s", s)
}
if exitErr, ok := errors.AsType[*exec.ExitError](err); ok {
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
if ec := status.ExitStatus(); ec == expExitCode {
return // PASS.
}
}
}
t.Errorf("Want child exited with %d, got: %+v", expExitCode, err)
}