blob: ee46e0aec6f47e91ae14527e57854671c06bee19 [file] [log] [blame]
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.16 && (linux || darwin)
// +build go1.16
// +build linux darwin
package main
import (
"bufio"
"bytes"
"context"
"net/http"
"net/http/httptest"
"regexp"
"strings"
"testing"
"time"
"golang.org/x/build/internal/coordinator/remote"
"golang.org/x/build/internal/coordinator/schedule"
)
var durationTests = []struct {
in time.Duration
want string
}{
{10*time.Second + 555*time.Millisecond, "10.6s"},
{10*time.Second + 500*time.Millisecond, "10.5s"},
{10*time.Second + 499*time.Millisecond, "10.5s"},
{10*time.Second + 401*time.Millisecond, "10.4s"},
{9*time.Second + 401*time.Millisecond, "9.4s"},
{9*time.Second + 456*time.Millisecond, "9.46s"},
{9*time.Second + 445*time.Millisecond, "9.45s"},
{1 * time.Second, "1s"},
{859*time.Millisecond + 445*time.Microsecond, "859.4ms"},
{859*time.Millisecond + 460*time.Microsecond, "859.5ms"},
}
func TestFriendlyDuration(t *testing.T) {
for _, tt := range durationTests {
got := friendlyDuration(tt.in)
if got != tt.want {
t.Errorf("friendlyDuration(%v): got %s, want %s", tt.in, got, tt.want)
}
}
}
func TestHandleStatus_HealthFormatting(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
addHealthCheckers(ctx, http.NewServeMux(), nil)
addHealthChecker(http.NewServeMux(), &healthChecker{
ID: "allgood",
Title: "All Good Test",
Check: func(*checkWriter) {},
})
addHealthChecker(http.NewServeMux(), &healthChecker{
ID: "errortest",
Title: "Error Test",
Check: func(cw *checkWriter) {
cw.info("test-info")
cw.warn("test-warn")
cw.error("test-error")
},
})
statusMu.Lock()
for k := range status {
delete(status, k)
}
for k := range tries {
delete(tries, k)
}
tryList = nil
statusMu.Unlock()
rec := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", nil)
setSessionPool(remote.NewSessionPool(ctx))
handleStatus(rec, req)
const pre = "<h2 id=health>Health"
const suf = "<h2 id=trybots>Active Trybot Runs"
got := rec.Body.String()
if i := strings.Index(got, pre); i != -1 {
got = got[i+len(pre):]
} else {
t.Fatalf("output didn't contain %q: %s", pre, got)
}
if i := strings.Index(got, suf); i != -1 {
got = got[:i]
} else {
t.Fatalf("output didn't contain %q: %s", suf, got)
}
for _, sub := range []string{
`<a href="/status/allgood">All Good Test</a>: ok`,
`<li>test-info</li>`,
`<li><span style='color: orange'>test-warn</span></li>`,
`<li><span style='color: red'><b>test-error</b></span></li>`,
} {
if !strings.Contains(got, sub) {
t.Errorf("didn't find substring %q in output", sub)
}
}
if t.Failed() {
t.Logf("Got: %s", got)
}
}
func TestStatusSched(t *testing.T) {
data := statusData{
SchedState: schedule.SchedulerState{
HostTypes: []schedule.SchedulerHostState{
{
HostType: "no-special",
LastProgress: 5 * time.Minute,
Total: schedule.SchedulerWaitingState{Count: 10, Newest: 5 * time.Minute, Oldest: 61 * time.Minute},
Regular: schedule.SchedulerWaitingState{Count: 1},
},
{
HostType: "with-try",
LastProgress: 5 * time.Minute,
Total: schedule.SchedulerWaitingState{Count: 3},
Try: schedule.SchedulerWaitingState{Count: 1, Newest: 2 * time.Second, Oldest: 5 * time.Minute},
Regular: schedule.SchedulerWaitingState{Count: 2},
},
{
HostType: "gomote-and-try",
Total: schedule.SchedulerWaitingState{Count: 6, Newest: 3 * time.Second, Oldest: 4 * time.Minute},
Gomote: schedule.SchedulerWaitingState{Count: 2, Newest: 3 * time.Second, Oldest: 4 * time.Minute},
Try: schedule.SchedulerWaitingState{Count: 1},
Regular: schedule.SchedulerWaitingState{Count: 3},
},
},
},
}
buf := new(bytes.Buffer)
if err := statusTmpl.Execute(buf, data); err != nil {
t.Fatal(err)
}
wantMatch := []string{
`(?s)<li><b>no-special</b>: 10 waiting \(oldest 1h1m0s, newest 5m0s, progress 5m0s\)\s+</li>`,
`<li>try: 1 \(oldest 5m0s, newest 2s\)</li>`,
`(?s)<li><b>gomote-and-try</b>: 6 waiting \(oldest 4m0s, newest 3s\)`, // checks for no ", progress"
`<li>gomote: 2 \(oldest 4m0s, newest 3s\)</li>`,
}
for _, rx := range wantMatch {
matched, err := regexp.Match(rx, buf.Bytes())
if err != nil {
t.Errorf("error matching %#q: %v", rx, err)
continue
}
if !matched {
t.Errorf("didn't match %#q", rx)
}
}
if t.Failed() {
t.Logf("Got: %s", section(buf.Bytes(), "sched"))
}
}
// section returns the section of the status HTML page that starts
// with <h2 id=$section> and ends with any other ^<h2 line.
func section(in []byte, section string) []byte {
start := "<h2 id=" + section + ">"
bs := bufio.NewScanner(bytes.NewReader(in))
var out bytes.Buffer
var foundStart bool
for bs.Scan() {
if foundStart {
if strings.HasPrefix(bs.Text(), "<h2") {
break
}
} else {
if strings.HasPrefix(bs.Text(), start) {
foundStart = true
} else {
continue
}
}
out.Write(bs.Bytes())
out.WriteByte('\n')
}
return out.Bytes()
}