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