package e2e import ( "errors" "fmt" "net/http" "os/exec" "path/filepath" "strings" "syscall" "testing" "time" ) // SIGTERM is how a deploy restarts the daemon. It used to be a plain // kill: no listener closed, no request or push allowed to finish. The // daemon now stops its listeners, drains, and exits 0 (#105). func TestServeStopsOnSIGTERM(t *testing.T) { t.Parallel() inst := startInstance(t) if resp, err := http.Get(inst.base() + "/healthz"); err != nil || resp.StatusCode != 200 { t.Fatalf("healthz before shutdown: %v", err) } if err := inst.proc.Process.Signal(syscall.SIGTERM); err != nil { t.Fatal(err) } done := make(chan error, 1) go func() { done <- inst.proc.Wait() }() select { case err := <-done: if err != nil { t.Fatalf("daemon did not exit cleanly on SIGTERM: %v", err) } case <-time.After(15 * time.Second): t.Fatal("daemon still running 15s after SIGTERM") } if _, err := http.Get(inst.base() + "/healthz"); err == nil { t.Fatal("http listener still answering after shutdown") } } // A CLI keeps a shared connection open between commands. On shutdown that // idle connection is closed at once rather than holding the drain for its // full 30 s; only a session mid-command is waited for (#141). func TestShutdownClosesIdleConnections(t *testing.T) { t.Parallel() inst := startInstance(t) aliceKey := inst.newKey(t, "alice") inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub") // A control master with no session: -N, backgrounded, persistent. // A unix socket path has a short limit; t.TempDir is far past it here. sock := fmt.Sprintf("/tmp/gitbay-e2e-cm-%d", inst.port) base := []string{"-p", fmt.Sprint(inst.port), "-i", aliceKey, "-o", "IdentitiesOnly=yes", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=" + filepath.Join(inst.sshDir, "known_hosts"), "-o", "BatchMode=yes", "-o", "ControlPath=" + sock} master := exec.Command("ssh", append(append([]string{}, base...), "-o", "ControlMaster=yes", "-o", "ControlPersist=yes", "-N", "-f", "git@127.0.0.1")...) if out, err := master.CombinedOutput(); err != nil { t.Fatalf("control master: %v\n%s", err, out) } t.Cleanup(func() { exec.Command("ssh", append(append([]string{}, base...), "-O", "exit", "git@127.0.0.1")...).Run() }) // The shared connection works. if out, err := exec.Command("ssh", append(append([]string{}, base...), "git@127.0.0.1", "whoami")...).Output(); err != nil || strings.TrimSpace(string(out)) != "alice" { t.Fatalf("multiplexed whoami: %v %q", err, out) } start := time.Now() if err := inst.proc.Process.Signal(syscall.SIGTERM); err != nil { t.Fatal(err) } done := make(chan error, 1) go func() { done <- inst.proc.Wait() }() select { case err := <-done: if err != nil { t.Fatalf("daemon did not exit cleanly: %v", err) } case <-time.After(20 * time.Second): t.Fatal("daemon still running 20s after SIGTERM with only an idle connection open") } if took := time.Since(start); took > 5*time.Second { t.Fatalf("shutdown took %s with only an idle connection open", took) } } // A deploy restarts the daemon while someone follows a build. The follows // end at once with a message saying so, rather than holding the drain for // its full 30 s and then being cut off mid-page. func TestShutdownEndsFollows(t *testing.T) { t.Parallel() inst := startInstance(t) aliceKey := inst.newKey(t, "alice") runnerKey := inst.newKey(t, "ci") inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub") inst.admin(t, "admin", "user", "create", "ci", "--key", runnerKey+".pub", "--admin") queueBuild(t, inst, aliceKey) // A line in the log is how the test knows both follows are streaming // before the signal; a follow still connecting is an idle connection, // which shutdown closes without a word. id := claimBuild(t, inst, runnerKey) if _, errOut, code := inst.ssh(t, runnerKey, "started\n", "runner", "log", id); code != 0 { t.Fatalf("runner log: %s", errOut) } cmd := inst.sshCmd(aliceKey, "build", "log", "alice/app", "1", "--follow") stdout, err := cmd.StdoutPipe() if err != nil { t.Fatal(err) } var stderr strings.Builder cmd.Stderr = &stderr if err := cmd.Start(); err != nil { t.Fatal(err) } defer func() { if cmd.ProcessState == nil { cmd.Process.Kill() cmd.Wait() } }() page, err := http.Get(inst.base() + "/alice/app/builds/1") if err != nil { t.Fatal(err) } defer page.Body.Close() web := newStreamReader(page.Body) web.waitFor(t, "started") newStreamReader(stdout).waitFor(t, "started") start := time.Now() if err := inst.proc.Process.Signal(syscall.SIGTERM); err != nil { t.Fatal(err) } web.waitFor(t, `
gitbay is restarting`) web.waitFor(t, "