mirror of
https://github.com/gin-gonic/gin.git
synced 2026-06-06 20:18:19 +08:00
112 lines
3.1 KiB
Go
112 lines
3.1 KiB
Go
//go:build !windows
|
|
|
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
|
// Use of this source code is governed by a MIT style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package gin
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"syscall"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestRunWithHotReload_InvalidFD(t *testing.T) {
|
|
t.Setenv(hotReloadListenerEnv, "not-a-number")
|
|
err := New().RunWithHotReload()
|
|
assert.ErrorContains(t, err, "invalid")
|
|
}
|
|
|
|
// TestRunWithHotReload_GracefulShutdown starts the engine via RunWithHotReload
|
|
// and verifies it shuts down cleanly on SIGTERM. signal.Notify inside
|
|
// serveWithSignals captures SIGTERM before the default handler fires, so the
|
|
// test process is not terminated.
|
|
func TestRunWithHotReload_GracefulShutdown(t *testing.T) {
|
|
// Reserve a free port then release it; there is a small TOCTOU window.
|
|
ln0, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
addr := ln0.Addr().String()
|
|
ln0.Close()
|
|
|
|
engine := New()
|
|
engine.GET("/ping", func(c *Context) { c.String(200, "pong") })
|
|
|
|
errCh := make(chan error, 1)
|
|
go func() { errCh <- engine.RunWithHotReload(addr) }()
|
|
|
|
require.Eventually(t, func() bool {
|
|
conn, err := net.DialTimeout("tcp", addr, time.Second)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
conn.Close()
|
|
return true
|
|
}, 5*time.Second, 10*time.Millisecond, "server never became reachable")
|
|
|
|
proc, err := os.FindProcess(os.Getpid())
|
|
require.NoError(t, err)
|
|
require.NoError(t, proc.Signal(syscall.SIGTERM))
|
|
|
|
select {
|
|
case err := <-errCh:
|
|
assert.NoError(t, err)
|
|
case <-time.After(10 * time.Second):
|
|
t.Fatal("server did not shut down within 10s")
|
|
}
|
|
}
|
|
|
|
// TestRunWithHotReload_InheritedListener exercises the child-process path by
|
|
// pre-opening a TCP socket, duplicating its fd, and advertising it via the
|
|
// environment variable that runInherited reads.
|
|
func TestRunWithHotReload_InheritedListener(t *testing.T) {
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
defer ln.Close()
|
|
addr := ln.Addr().String()
|
|
|
|
// tcpLn.File() dups the underlying fd; we then dup again so that
|
|
// runInherited's f.Close() doesn't affect our reference.
|
|
tcpLn := ln.(*net.TCPListener)
|
|
f, err := tcpLn.File()
|
|
require.NoError(t, err)
|
|
defer f.Close()
|
|
|
|
dupFD, err := syscall.Dup(int(f.Fd()))
|
|
require.NoError(t, err)
|
|
// dupFD is now owned by RunWithHotReload; do not close it here.
|
|
|
|
t.Setenv(hotReloadListenerEnv, fmt.Sprintf("%d", dupFD))
|
|
|
|
engine := New()
|
|
engine.GET("/ping", func(c *Context) { c.String(200, "pong") })
|
|
|
|
errCh := make(chan error, 1)
|
|
go func() { errCh <- engine.RunWithHotReload() }()
|
|
|
|
require.Eventually(t, func() bool {
|
|
conn, err := net.DialTimeout("tcp", addr, time.Second)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
conn.Close()
|
|
return true
|
|
}, 5*time.Second, 10*time.Millisecond, "inherited server never became reachable")
|
|
|
|
proc, _ := os.FindProcess(os.Getpid())
|
|
proc.Signal(syscall.SIGTERM)
|
|
|
|
select {
|
|
case err := <-errCh:
|
|
assert.NoError(t, err)
|
|
case <-time.After(10 * time.Second):
|
|
t.Fatal("inherited server did not shut down within 10s")
|
|
}
|
|
}
|