2012-06-05 06:21:10 +08:00
|
|
|
package gracehttp_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"encoding/json"
|
2013-08-20 06:39:03 +08:00
|
|
|
"flag"
|
2012-06-05 06:21:10 +08:00
|
|
|
"fmt"
|
2012-07-08 05:38:18 +08:00
|
|
|
"github.com/daaku/go.freeport"
|
|
|
|
"github.com/daaku/go.tool"
|
2012-06-05 06:21:10 +08:00
|
|
|
"io/ioutil"
|
2013-08-20 13:51:32 +08:00
|
|
|
"net"
|
2012-06-05 06:21:10 +08:00
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"sync"
|
|
|
|
"syscall"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2013-08-20 06:39:03 +08:00
|
|
|
// Debug logging.
|
|
|
|
var debugLog = flag.Bool("debug", false, "enable debug logging")
|
|
|
|
|
2013-03-26 04:33:05 +08:00
|
|
|
func debug(format string, a ...interface{}) {
|
2013-08-20 06:39:03 +08:00
|
|
|
if *debugLog {
|
2013-03-26 04:33:05 +08:00
|
|
|
println(fmt.Sprintf(format, a...))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-05 06:21:10 +08:00
|
|
|
// The response from the test server.
|
|
|
|
type response struct {
|
|
|
|
Sleep time.Duration
|
|
|
|
Pid int
|
|
|
|
}
|
|
|
|
|
|
|
|
// State for the test run.
|
|
|
|
type harness struct {
|
2013-08-20 13:51:32 +08:00
|
|
|
T *testing.T // The test instance.
|
|
|
|
ImportPath string // The import path for the server command.
|
|
|
|
ExeName string // The temp binary from the build.
|
|
|
|
Addr []string // The addresses for the http servers.
|
|
|
|
Process []*os.Process // The server commands, oldest to newest.
|
|
|
|
ProcessMutex sync.Mutex // The mutex to guard Process manipulation.
|
|
|
|
RequestWaitGroup sync.WaitGroup // The wait group for the HTTP requests.
|
|
|
|
newProcess chan bool // A bool is sent on restart.
|
|
|
|
requestCount int
|
|
|
|
requestCountMutex sync.Mutex
|
2012-06-05 06:21:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Find 3 free ports and setup addresses.
|
|
|
|
func (h *harness) SetupAddr() {
|
|
|
|
for i := 3; i > 0; i-- {
|
|
|
|
port, err := freeport.Get()
|
|
|
|
if err != nil {
|
|
|
|
h.T.Fatalf("Failed to find a free port: %s", err)
|
|
|
|
}
|
|
|
|
h.Addr = append(h.Addr, fmt.Sprintf("127.0.0.1:%d", port))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Builds the command line arguments.
|
|
|
|
func (h *harness) Args() []string {
|
|
|
|
if h.Addr == nil {
|
|
|
|
h.SetupAddr()
|
|
|
|
}
|
|
|
|
return []string{
|
|
|
|
"-gracehttp.log=false",
|
|
|
|
"-a0", h.Addr[0],
|
|
|
|
"-a1", h.Addr[1],
|
|
|
|
"-a2", h.Addr[2],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Builds the command.
|
|
|
|
func (h *harness) Build() {
|
|
|
|
basename := filepath.Base(h.ImportPath)
|
|
|
|
tempFile, err := ioutil.TempFile("", basename+"-")
|
|
|
|
if err != nil {
|
|
|
|
h.T.Fatalf("Error creating temp file: %s", err)
|
|
|
|
}
|
|
|
|
h.ExeName = tempFile.Name()
|
|
|
|
_ = os.Remove(h.ExeName) // the build tool will create this
|
|
|
|
options := tool.Options{
|
|
|
|
ImportPaths: []string{h.ImportPath},
|
|
|
|
Output: h.ExeName,
|
|
|
|
}
|
|
|
|
_, err = options.Command("build")
|
|
|
|
if err != nil {
|
|
|
|
h.T.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start a fresh server and wait for pid updates on restart.
|
|
|
|
func (h *harness) Start() {
|
|
|
|
cmd := exec.Command(h.ExeName, h.Args()...)
|
|
|
|
stderr, err := cmd.StderrPipe()
|
|
|
|
go func() {
|
|
|
|
reader := bufio.NewReader(stderr)
|
|
|
|
for {
|
|
|
|
line, isPrefix, err := reader.ReadLine()
|
|
|
|
if err != nil {
|
|
|
|
h.T.Fatalf("Failed to read line from server process: %s", err)
|
|
|
|
}
|
|
|
|
if isPrefix {
|
|
|
|
h.T.Fatalf("Deal with isPrefix for line: %s", line)
|
|
|
|
}
|
|
|
|
res := &response{}
|
|
|
|
err = json.Unmarshal([]byte(line), res)
|
|
|
|
if err != nil {
|
|
|
|
h.T.Fatalf("Could not parse json from stderr %s: %s", line, err)
|
|
|
|
}
|
|
|
|
process, err := os.FindProcess(res.Pid)
|
|
|
|
if err != nil {
|
|
|
|
h.T.Fatalf("Could not find process with pid: %d", res.Pid)
|
|
|
|
}
|
2013-08-20 06:37:41 +08:00
|
|
|
h.ProcessMutex.Lock()
|
2012-06-05 06:21:10 +08:00
|
|
|
h.Process = append(h.Process, process)
|
2013-08-20 06:37:41 +08:00
|
|
|
h.ProcessMutex.Unlock()
|
2012-06-05 06:21:10 +08:00
|
|
|
h.newProcess <- true
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
err = cmd.Start()
|
|
|
|
if err != nil {
|
|
|
|
h.T.Fatalf("Failed to start command: %s", err)
|
|
|
|
}
|
|
|
|
<-h.newProcess
|
|
|
|
}
|
|
|
|
|
|
|
|
// Restart the most recent server.
|
|
|
|
func (h *harness) Restart() {
|
|
|
|
err := h.MostRecentProcess().Signal(syscall.SIGUSR2)
|
|
|
|
if err != nil {
|
|
|
|
h.T.Fatalf("Failed to send SIGUSR2 and restart process: %s", err)
|
|
|
|
}
|
|
|
|
<-h.newProcess
|
|
|
|
}
|
|
|
|
|
|
|
|
// Graceful termination of the most recent server.
|
|
|
|
func (h *harness) Stop() {
|
|
|
|
err := h.MostRecentProcess().Signal(syscall.SIGTERM)
|
|
|
|
if err != nil {
|
|
|
|
h.T.Fatalf("Failed to send SIGTERM and stop process: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the most recent server process.
|
|
|
|
func (h *harness) MostRecentProcess() *os.Process {
|
2013-08-20 06:37:41 +08:00
|
|
|
h.ProcessMutex.Lock()
|
|
|
|
defer h.ProcessMutex.Unlock()
|
2012-06-05 06:21:10 +08:00
|
|
|
l := len(h.Process)
|
|
|
|
if l == 0 {
|
|
|
|
h.T.Fatalf("Most recent command requested before command was created.")
|
|
|
|
}
|
|
|
|
return h.Process[l-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the built executable.
|
|
|
|
func (h *harness) RemoveExe() {
|
|
|
|
err := os.Remove(h.ExeName)
|
|
|
|
if err != nil {
|
|
|
|
h.T.Fatalf("Failed to RemoveExe: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-20 13:51:32 +08:00
|
|
|
// Get the global request count.
|
|
|
|
func (h *harness) RequestCount() int {
|
|
|
|
h.requestCountMutex.Lock()
|
|
|
|
defer h.requestCountMutex.Unlock()
|
|
|
|
c := h.requestCount
|
|
|
|
h.requestCount++
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
2012-06-05 06:21:10 +08:00
|
|
|
// Helper for sending a single request.
|
2013-08-20 13:51:32 +08:00
|
|
|
func (h *harness) SendOne(dialgroup *sync.WaitGroup, duration time.Duration, addr string, pid int) {
|
|
|
|
count := h.RequestCount()
|
|
|
|
debug("Send %02d pid=%d duration=%s", count, pid, duration)
|
2012-06-05 06:21:10 +08:00
|
|
|
client := &http.Client{
|
2013-08-20 13:51:32 +08:00
|
|
|
Transport: &http.Transport{
|
|
|
|
DisableKeepAlives: true,
|
|
|
|
Dial: func(network, addr string) (net.Conn, error) {
|
|
|
|
defer dialgroup.Done()
|
|
|
|
return net.Dial(network, addr)
|
|
|
|
},
|
|
|
|
},
|
2012-06-05 06:21:10 +08:00
|
|
|
}
|
|
|
|
url := fmt.Sprintf("http://%s/sleep/?duration=%s", addr, duration.String())
|
|
|
|
r, err := client.Get(url)
|
|
|
|
if err != nil {
|
|
|
|
h.T.Fatalf("Failed request to %s: %s", url, err)
|
|
|
|
}
|
2013-08-20 13:51:32 +08:00
|
|
|
debug("Body %02d pid=%d duration=%s", count, pid, duration)
|
2012-06-05 06:21:10 +08:00
|
|
|
defer r.Body.Close()
|
|
|
|
res := &response{}
|
|
|
|
err = json.NewDecoder(r.Body).Decode(res)
|
|
|
|
if err != nil {
|
|
|
|
h.T.Fatalf("Failed to ready decode json response body: %s", err)
|
|
|
|
}
|
|
|
|
if pid != res.Pid {
|
|
|
|
h.T.Fatalf("Didn't get expected pid %d instead got %d", pid, res.Pid)
|
|
|
|
}
|
2013-08-20 13:51:32 +08:00
|
|
|
debug("Done %02d pid=%d duration=%s", count, pid, duration)
|
2012-06-05 06:21:10 +08:00
|
|
|
h.RequestWaitGroup.Done()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send test HTTP request.
|
|
|
|
func (h *harness) SendRequest() {
|
|
|
|
pid := h.MostRecentProcess().Pid
|
2013-08-20 13:51:32 +08:00
|
|
|
var dialgroup sync.WaitGroup
|
2012-06-05 06:21:10 +08:00
|
|
|
for _, addr := range h.Addr {
|
2013-03-26 04:33:05 +08:00
|
|
|
debug("Added 2 Requests")
|
2012-06-05 06:21:10 +08:00
|
|
|
h.RequestWaitGroup.Add(2)
|
2013-08-20 13:51:32 +08:00
|
|
|
dialgroup.Add(2)
|
|
|
|
go h.SendOne(&dialgroup, time.Second*0, addr, pid)
|
|
|
|
go h.SendOne(&dialgroup, time.Second*2, addr, pid)
|
2012-06-05 06:21:10 +08:00
|
|
|
}
|
2013-08-20 13:51:32 +08:00
|
|
|
dialgroup.Wait()
|
2012-06-05 06:21:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for everything.
|
|
|
|
func (h *harness) Wait() {
|
|
|
|
h.RequestWaitGroup.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
// The main test case.
|
|
|
|
func TestComplex(t *testing.T) {
|
2013-03-26 04:33:05 +08:00
|
|
|
debug("Started TestComplex")
|
2012-06-05 06:21:10 +08:00
|
|
|
h := &harness{
|
2012-07-08 05:38:18 +08:00
|
|
|
ImportPath: "github.com/daaku/go.grace/gracehttp/testserver",
|
2012-06-05 06:21:10 +08:00
|
|
|
T: t,
|
2013-08-20 13:51:32 +08:00
|
|
|
newProcess: make(chan bool),
|
2012-06-05 06:21:10 +08:00
|
|
|
}
|
2013-03-26 04:33:05 +08:00
|
|
|
debug("Building")
|
2012-06-05 06:21:10 +08:00
|
|
|
h.Build()
|
2013-03-26 04:33:05 +08:00
|
|
|
debug("Initial Start")
|
2012-06-05 06:21:10 +08:00
|
|
|
h.Start()
|
2013-03-26 04:33:05 +08:00
|
|
|
debug("Send Request 1")
|
2012-06-05 06:21:10 +08:00
|
|
|
h.SendRequest()
|
2013-03-26 04:33:05 +08:00
|
|
|
debug("Restart 1")
|
2012-06-05 06:21:10 +08:00
|
|
|
h.Restart()
|
2013-03-26 04:33:05 +08:00
|
|
|
debug("Send Request 2")
|
2012-06-05 06:21:10 +08:00
|
|
|
h.SendRequest()
|
2013-03-26 04:33:05 +08:00
|
|
|
debug("Restart 2")
|
2012-06-05 06:21:10 +08:00
|
|
|
h.Restart()
|
2013-03-26 04:33:05 +08:00
|
|
|
debug("Send Request 3")
|
2012-06-05 06:21:10 +08:00
|
|
|
h.SendRequest()
|
2013-03-26 04:33:05 +08:00
|
|
|
debug("Waiting")
|
2012-06-05 06:21:10 +08:00
|
|
|
h.Wait()
|
2013-08-20 13:51:32 +08:00
|
|
|
debug("Stopping")
|
|
|
|
h.Stop()
|
2013-03-26 04:33:05 +08:00
|
|
|
debug("Removing Executable")
|
2012-06-05 06:21:10 +08:00
|
|
|
h.RemoveExe()
|
|
|
|
}
|