// 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) }