server_test.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package daemon
  2. import (
  3. "os"
  4. "syscall"
  5. "testing"
  6. "time"
  7. "src.elv.sh/pkg/daemon/daemondefs"
  8. "src.elv.sh/pkg/daemon/internal/api"
  9. "src.elv.sh/pkg/must"
  10. . "src.elv.sh/pkg/prog/progtest"
  11. "src.elv.sh/pkg/store/storetest"
  12. "src.elv.sh/pkg/testutil"
  13. )
  14. func TestProgram_TerminatesIfCannotListen(t *testing.T) {
  15. setup(t)
  16. must.CreateEmpty("sock")
  17. Test(t, &Program{},
  18. ThatElvish("-daemon", "-sock", "sock", "-db", "db").
  19. ExitsWith(2).
  20. WritesStdoutContaining("failed to listen on sock"),
  21. )
  22. }
  23. func TestProgram_ServesClientRequests(t *testing.T) {
  24. setup(t)
  25. startServer(t, cli("sock", "db"))
  26. client := startClient(t, "sock")
  27. // Test server state requests.
  28. gotVersion, err := client.Version()
  29. if gotVersion != api.Version || err != nil {
  30. t.Errorf(".Version() -> (%v, %v), want (%v, nil)", gotVersion, err, api.Version)
  31. }
  32. gotPid, err := client.Pid()
  33. wantPid := syscall.Getpid()
  34. if gotPid != wantPid || err != nil {
  35. t.Errorf(".Pid() -> (%v, %v), want (%v, nil)", gotPid, err, wantPid)
  36. }
  37. // Test store requests.
  38. storetest.TestCmd(t, client)
  39. storetest.TestDir(t, client)
  40. }
  41. func TestProgram_StillServesIfCannotOpenDB(t *testing.T) {
  42. setup(t)
  43. must.WriteFile("db", "not a valid bolt database")
  44. startServer(t, cli("sock", "db"))
  45. client := startClient(t, "sock")
  46. _, err := client.AddCmd("cmd")
  47. if err == nil {
  48. t.Errorf("got nil error, want non-nil")
  49. }
  50. }
  51. func TestProgram_QuitsOnSignalChannelWithNoClient(t *testing.T) {
  52. setup(t)
  53. sigCh := make(chan os.Signal)
  54. startServerOpts(t, cli("sock", "db"), ServeOpts{Signals: sigCh})
  55. close(sigCh)
  56. // startServerSigCh will wait for server to terminate at cleanup
  57. }
  58. func TestProgram_QuitsOnSignalChannelWithClients(t *testing.T) {
  59. setup(t)
  60. sigCh := make(chan os.Signal)
  61. server := startServerOpts(t, cli("sock", "db"), ServeOpts{Signals: sigCh})
  62. client := startClient(t, "sock")
  63. close(sigCh)
  64. server.WaitQuit()
  65. _, err := client.Version()
  66. if err == nil {
  67. t.Errorf("client.Version() returns nil error, want non-nil")
  68. }
  69. }
  70. func TestProgram_BadCLI(t *testing.T) {
  71. Test(t, &Program{},
  72. ThatElvish().
  73. ExitsWith(2).
  74. WritesStderr("internal error: no suitable subprogram\n"),
  75. ThatElvish("-daemon", "x").
  76. ExitsWith(2).
  77. WritesStderrContaining("arguments are not allowed with -daemon"),
  78. )
  79. }
  80. func setup(t *testing.T) {
  81. testutil.Umask(t, 0)
  82. testutil.InTempDir(t)
  83. }
  84. // Calls startServerOpts with a Signals channel that gets closed during cleanup.
  85. func startServer(t *testing.T, args []string) server {
  86. t.Helper()
  87. sigCh := make(chan os.Signal)
  88. s := startServerOpts(t, args, ServeOpts{Signals: sigCh})
  89. // Cleanup functions added later are run earlier. This will be run before
  90. // the cleanup function added by startServerOpts that waits for the server
  91. // to terminate.
  92. t.Cleanup(func() { close(sigCh) })
  93. return s
  94. }
  95. // Start server with custom ServeOpts (opts.Ready is ignored). Makes sure that
  96. // the server terminates during cleanup.
  97. func startServerOpts(t *testing.T, args []string, opts ServeOpts) server {
  98. t.Helper()
  99. readyCh := make(chan struct{})
  100. opts.Ready = readyCh
  101. doneCh := make(chan serverResult)
  102. go func() {
  103. exit, stdout, stderr := Run(&Program{serveOpts: opts}, args...)
  104. doneCh <- serverResult{exit, stdout, stderr}
  105. close(doneCh)
  106. }()
  107. select {
  108. case <-readyCh:
  109. case <-time.After(testutil.Scaled(2 * time.Second)):
  110. t.Fatal("timed out waiting for daemon to start")
  111. }
  112. s := server{t, doneCh}
  113. t.Cleanup(func() { s.WaitQuit() })
  114. return s
  115. }
  116. type server struct {
  117. t *testing.T
  118. ch <-chan serverResult
  119. }
  120. type serverResult struct {
  121. exit int
  122. stdout, stderr string
  123. }
  124. func (s server) WaitQuit() (serverResult, bool) {
  125. s.t.Helper()
  126. select {
  127. case r := <-s.ch:
  128. return r, true
  129. case <-time.After(testutil.Scaled(2 * time.Second)):
  130. s.t.Error("timed out waiting for daemon to quit")
  131. return serverResult{}, false
  132. }
  133. }
  134. func cli(sock, db string) []string {
  135. return []string{"elvish", "-daemon", "-sock", sock, "-db", db}
  136. }
  137. func startClient(t *testing.T, sock string) daemondefs.Client {
  138. cl := NewClient("sock")
  139. if _, err := cl.Version(); err != nil {
  140. t.Errorf("failed to start client: %v", err)
  141. }
  142. t.Cleanup(func() { cl.Close() })
  143. return cl
  144. }