1
0

process_test.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. //go:build !windows
  2. package xray
  3. import (
  4. "errors"
  5. "os"
  6. "os/exec"
  7. "os/signal"
  8. "path/filepath"
  9. "syscall"
  10. "testing"
  11. "time"
  12. xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
  13. "github.com/op/go-logging"
  14. )
  15. func TestWriteFileAtomicModeAndRenameFailure(t *testing.T) {
  16. dir := t.TempDir()
  17. path := filepath.Join(dir, "config.json")
  18. if err := os.WriteFile(path, []byte("old"), 0o644); err != nil {
  19. t.Fatalf("seed: %v", err)
  20. }
  21. if err := writeFileAtomic(path, []byte("new"), 0o600); err != nil {
  22. t.Fatalf("writeFileAtomic: %v", err)
  23. }
  24. data, err := os.ReadFile(path)
  25. if err != nil {
  26. t.Fatalf("read: %v", err)
  27. }
  28. if string(data) != "new" {
  29. t.Fatalf("content = %q, want new", data)
  30. }
  31. info, err := os.Stat(path)
  32. if err != nil {
  33. t.Fatalf("stat: %v", err)
  34. }
  35. if info.Mode().Perm() != 0o600 {
  36. t.Fatalf("mode = %o, want 600", info.Mode().Perm())
  37. }
  38. originalRename := renameFile
  39. renameFile = func(_, _ string) error { return errors.New("injected rename failure") }
  40. t.Cleanup(func() { renameFile = originalRename })
  41. if err := writeFileAtomic(path, []byte("partial"), 0o600); err == nil {
  42. t.Fatal("rename failure = nil")
  43. }
  44. data, err = os.ReadFile(path)
  45. if err != nil {
  46. t.Fatalf("read preserved file: %v", err)
  47. }
  48. if string(data) != "new" {
  49. t.Fatalf("content after failed rename = %q, want committed content", data)
  50. }
  51. matches, err := filepath.Glob(filepath.Join(dir, ".config-*.tmp"))
  52. if err != nil {
  53. t.Fatalf("glob: %v", err)
  54. }
  55. if len(matches) != 0 {
  56. t.Fatalf("temporary files leaked: %v", matches)
  57. }
  58. }
  59. func TestStopWaitsForGracefulExit(t *testing.T) {
  60. initProcessTestLogger(t)
  61. p := startProcessHelper(t, "delayed-term")
  62. start := time.Now()
  63. if err := p.Stop(); err != nil {
  64. t.Fatalf("Stop: %v", err)
  65. }
  66. if elapsed := time.Since(start); elapsed < 150*time.Millisecond {
  67. t.Fatalf("Stop returned before child exited; elapsed=%s", elapsed)
  68. }
  69. if p.IsRunning() {
  70. t.Fatal("process still reports running after Stop")
  71. }
  72. }
  73. func TestIntentionalStopDoesNotRecordExitError(t *testing.T) {
  74. initProcessTestLogger(t)
  75. p := startProcessHelper(t, "default-term")
  76. if err := p.Stop(); err != nil {
  77. t.Fatalf("Stop: %v", err)
  78. }
  79. if err := p.GetErr(); err != nil {
  80. t.Fatalf("GetErr after intentional stop = %v, want nil", err)
  81. }
  82. if result := p.GetResult(); result != "" {
  83. t.Fatalf("GetResult after intentional stop = %q, want empty", result)
  84. }
  85. }
  86. func TestStopKillsProcessThatIgnoresSIGTERM(t *testing.T) {
  87. initProcessTestLogger(t)
  88. oldGraceful := xrayGracefulStopTimeout
  89. oldForce := xrayForceStopTimeout
  90. xrayGracefulStopTimeout = 100 * time.Millisecond
  91. xrayForceStopTimeout = 2 * time.Second
  92. t.Cleanup(func() {
  93. xrayGracefulStopTimeout = oldGraceful
  94. xrayForceStopTimeout = oldForce
  95. })
  96. p := startProcessHelper(t, "ignore-term")
  97. if err := p.Stop(); err != nil {
  98. t.Fatalf("Stop: %v", err)
  99. }
  100. if p.IsRunning() {
  101. t.Fatal("process still reports running after forced stop")
  102. }
  103. }
  104. func initProcessTestLogger(t *testing.T) {
  105. t.Helper()
  106. t.Setenv("XUI_LOG_FOLDER", t.TempDir())
  107. xuilogger.InitLogger(logging.ERROR)
  108. }
  109. func startProcessHelper(t *testing.T, mode string) *process {
  110. t.Helper()
  111. readyPath := filepath.Join(t.TempDir(), "ready")
  112. cmd := exec.Command(os.Args[0], "-test.run=TestXrayProcessHelper", "--", mode)
  113. cmd.Env = append(os.Environ(),
  114. "XRAY_PROCESS_HELPER=1",
  115. "XRAY_PROCESS_READY="+readyPath,
  116. )
  117. p := newProcess(nil)
  118. if err := p.startCommand(cmd); err != nil {
  119. t.Fatalf("start helper process: %v", err)
  120. }
  121. waitForProcessHelperReady(t, readyPath)
  122. t.Cleanup(func() {
  123. if p.IsRunning() {
  124. p.intentionalStop.Store(true)
  125. _ = p.cmd.Process.Kill()
  126. _ = p.waitForExit(2 * time.Second)
  127. }
  128. })
  129. return p
  130. }
  131. func waitForProcessHelperReady(t *testing.T, readyPath string) {
  132. t.Helper()
  133. deadline := time.Now().Add(2 * time.Second)
  134. for time.Now().Before(deadline) {
  135. if _, err := os.Stat(readyPath); err == nil {
  136. return
  137. }
  138. time.Sleep(10 * time.Millisecond)
  139. }
  140. t.Fatalf("helper process did not become ready")
  141. }
  142. func TestXrayProcessHelper(t *testing.T) {
  143. if os.Getenv("XRAY_PROCESS_HELPER") != "1" {
  144. return
  145. }
  146. mode := ""
  147. for i, arg := range os.Args {
  148. if arg == "--" && i+1 < len(os.Args) {
  149. mode = os.Args[i+1]
  150. break
  151. }
  152. }
  153. switch mode {
  154. case "delayed-term":
  155. sigCh := make(chan os.Signal, 1)
  156. signal.Notify(sigCh, syscall.SIGTERM)
  157. markProcessHelperReady(t)
  158. <-sigCh
  159. time.Sleep(200 * time.Millisecond)
  160. os.Exit(0)
  161. case "default-term":
  162. markProcessHelperReady(t)
  163. select {}
  164. case "ignore-term":
  165. signal.Ignore(syscall.SIGTERM)
  166. markProcessHelperReady(t)
  167. select {}
  168. default:
  169. t.Fatalf("unknown helper mode %q", mode)
  170. }
  171. }
  172. func markProcessHelperReady(t *testing.T) {
  173. t.Helper()
  174. readyPath := os.Getenv("XRAY_PROCESS_READY")
  175. if readyPath == "" {
  176. t.Fatal("XRAY_PROCESS_READY is not set")
  177. }
  178. if err := os.WriteFile(readyPath, []byte("ready"), 0644); err != nil {
  179. t.Fatalf("write helper ready file: %v", err)
  180. }
  181. }