main_sigterm_test.go
Rama master · réplica de lectura del repositorio original.
//go:build !windows
package main
import (
"bytes"
"os"
"os/exec"
"strings"
"sync"
"syscall"
"testing"
"time"
)
type lockedBuffer struct {
mu sync.Mutex
buf bytes.Buffer
}
func (b *lockedBuffer) Write(p []byte) (int, error) {
b.mu.Lock()
defer b.mu.Unlock()
return b.buf.Write(p)
}
func (b *lockedBuffer) String() string {
b.mu.Lock()
defer b.mu.Unlock()
return b.buf.String()
}
// Q16: `docker stop` sends SIGTERM. The process must shut down through the
// same path as Ctrl-C (listeners closed, UPnP released, access log flushed)
// and exit cleanly, not be killed by the default signal handling.
func TestSIGTERMShutsDownGracefully(t *testing.T) {
if os.Getenv("QG_HELPER_MAIN") == "1" {
main()
return
}
var out lockedBuffer
cmd := exec.Command(os.Args[0], "-test.run=^TestSIGTERMShutsDownGracefully$")
cmd.Env = append(os.Environ(), "QG_HELPER_MAIN=1", "QG_DATA="+t.TempDir(),
"QG_TLS=off", "QG_HTTP=127.0.0.1:0", "QG_ADMIN=127.0.0.1:0")
cmd.Stdout, cmd.Stderr = &out, &out
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
deadline := time.Now().Add(20 * time.Second)
for !strings.Contains(out.String(), "engine: http listening") {
if time.Now().After(deadline) {
_ = cmd.Process.Kill()
t.Fatalf("quicgate did not start:\n%s", out.String())
}
time.Sleep(50 * time.Millisecond)
}
if err := cmd.Process.Signal(syscall.SIGTERM); err != nil {
t.Fatal(err)
}
done := make(chan error, 1)
go func() { done <- cmd.Wait() }()
select {
case err := <-done:
if err != nil {
t.Fatalf("process did not exit cleanly on SIGTERM: %v\n%s", err, out.String())
}
case <-time.After(20 * time.Second):
_ = cmd.Process.Kill()
t.Fatalf("process still running 20s after SIGTERM:\n%s", out.String())
}
if !strings.Contains(out.String(), "engine: shutdown complete") {
t.Fatalf("SIGTERM did not go through the graceful shutdown path:\n%s", out.String())
}
}