3x-ui/xray/process_test.go

162 lines
3.5 KiB
Go

//go:build !windows
package xray
import (
"os"
"os/exec"
"os/signal"
"path/filepath"
"syscall"
"testing"
"time"
xuilogger "github.com/mhsanaei/3x-ui/v3/logger"
"github.com/op/go-logging"
)
func TestStopWaitsForGracefulExit(t *testing.T) {
initProcessTestLogger(t)
p := startProcessHelper(t, "delayed-term")
start := time.Now()
if err := p.Stop(); err != nil {
t.Fatalf("Stop: %v", err)
}
if elapsed := time.Since(start); elapsed < 150*time.Millisecond {
t.Fatalf("Stop returned before child exited; elapsed=%s", elapsed)
}
if p.IsRunning() {
t.Fatal("process still reports running after Stop")
}
}
func TestIntentionalStopDoesNotRecordExitError(t *testing.T) {
initProcessTestLogger(t)
p := startProcessHelper(t, "default-term")
if err := p.Stop(); err != nil {
t.Fatalf("Stop: %v", err)
}
if err := p.GetErr(); err != nil {
t.Fatalf("GetErr after intentional stop = %v, want nil", err)
}
if result := p.GetResult(); result != "" {
t.Fatalf("GetResult after intentional stop = %q, want empty", result)
}
}
func TestStopKillsProcessThatIgnoresSIGTERM(t *testing.T) {
initProcessTestLogger(t)
oldGraceful := xrayGracefulStopTimeout
oldForce := xrayForceStopTimeout
xrayGracefulStopTimeout = 100 * time.Millisecond
xrayForceStopTimeout = 2 * time.Second
t.Cleanup(func() {
xrayGracefulStopTimeout = oldGraceful
xrayForceStopTimeout = oldForce
})
p := startProcessHelper(t, "ignore-term")
if err := p.Stop(); err != nil {
t.Fatalf("Stop: %v", err)
}
if p.IsRunning() {
t.Fatal("process still reports running after forced stop")
}
}
func initProcessTestLogger(t *testing.T) {
t.Helper()
t.Setenv("XUI_LOG_FOLDER", t.TempDir())
xuilogger.InitLogger(logging.ERROR)
}
func startProcessHelper(t *testing.T, mode string) *process {
t.Helper()
readyPath := filepath.Join(t.TempDir(), "ready")
cmd := exec.Command(os.Args[0], "-test.run=TestXrayProcessHelper", "--", mode)
cmd.Env = append(os.Environ(),
"XRAY_PROCESS_HELPER=1",
"XRAY_PROCESS_READY="+readyPath,
)
p := newProcess(nil)
if err := p.startCommand(cmd); err != nil {
t.Fatalf("start helper process: %v", err)
}
waitForProcessHelperReady(t, readyPath)
t.Cleanup(func() {
if p.IsRunning() {
p.intentionalStop.Store(true)
_ = p.cmd.Process.Kill()
_ = p.waitForExit(2 * time.Second)
}
})
return p
}
func waitForProcessHelperReady(t *testing.T, readyPath string) {
t.Helper()
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
if _, err := os.Stat(readyPath); err == nil {
return
}
time.Sleep(10 * time.Millisecond)
}
t.Fatalf("helper process did not become ready")
}
func TestXrayProcessHelper(t *testing.T) {
if os.Getenv("XRAY_PROCESS_HELPER") != "1" {
return
}
mode := ""
for i, arg := range os.Args {
if arg == "--" && i+1 < len(os.Args) {
mode = os.Args[i+1]
break
}
}
switch mode {
case "delayed-term":
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGTERM)
markProcessHelperReady(t)
<-sigCh
time.Sleep(200 * time.Millisecond)
os.Exit(0)
case "default-term":
markProcessHelperReady(t)
select {}
case "ignore-term":
signal.Ignore(syscall.SIGTERM)
markProcessHelperReady(t)
select {}
default:
t.Fatalf("unknown helper mode %q", mode)
}
}
func markProcessHelperReady(t *testing.T) {
t.Helper()
readyPath := os.Getenv("XRAY_PROCESS_READY")
if readyPath == "" {
t.Fatal("XRAY_PROCESS_READY is not set")
}
if err := os.WriteFile(readyPath, []byte("ready"), 0644); err != nil {
t.Fatalf("write helper ready file: %v", err)
}
}