blob: d1c6922fd433cd0616246427ee4d9f0480406472 [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 (
"context"
"fmt"
"log"
"net/http/httptest"
"net/url"
"os"
"strings"
"sync"
"testing"
"time"
"golang.org/x/build/buildlet"
"golang.org/x/build/dashboard"
"golang.org/x/build/internal/coordinator/pool"
"golang.org/x/build/internal/coordinator/pool/queue"
"golang.org/x/build/internal/coordinator/remote"
"golang.org/x/build/internal/gophers"
)
type TestBuildletPool struct {
clients map[string]buildlet.Client
mu sync.Mutex
}
// GetBuildlet finds the first available buildlet for the hostType and returns
// it, or an error if no buildlets are available for that hostType.
func (tp *TestBuildletPool) GetBuildlet(ctx context.Context, hostType string, lg pool.Logger, item *queue.SchedItem) (buildlet.Client, error) {
tp.mu.Lock()
defer tp.mu.Unlock()
c, ok := tp.clients[hostType]
if ok {
return c, nil
}
return nil, fmt.Errorf("No client found for host type %s", hostType)
}
// Add sets the given client for the given hostType, overriding any previous
// entries.
func (tp *TestBuildletPool) Add(hostType string, client buildlet.Client) {
tp.mu.Lock()
if tp.clients == nil {
tp.clients = make(map[string]buildlet.Client)
}
tp.clients[hostType] = client
tp.mu.Unlock()
}
func (tp *TestBuildletPool) Remove(hostType string) {
tp.mu.Lock()
delete(tp.clients, hostType)
tp.mu.Unlock()
}
func (tp *TestBuildletPool) String() string { return "test" }
func (tp *TestBuildletPool) HasCapacity(string) bool { return true }
var testPool = &TestBuildletPool{}
func TestHandleBuildletCreateWrongMethod(t *testing.T) {
req := httptest.NewRequest("GET", "/buildlet/create", nil)
w := httptest.NewRecorder()
handleBuildletCreate(w, req)
if w.Code != 400 {
t.Fatalf("GET /buildlet/create: expected code 400, got %d", w.Code)
}
if body := w.Body.String(); !strings.Contains(body, "POST required") {
t.Fatalf("GET /buildlet/create: expected 'POST required' error, got %s", body)
}
}
func TestHandleBuildletCreateOldVersion(t *testing.T) {
data := url.Values{}
data.Set("version", "20150922")
req := httptest.NewRequest("POST", "/buildlet/create", strings.NewReader(data.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
w := httptest.NewRecorder()
handleBuildletCreate(w, req)
if w.Code != 400 {
t.Fatalf("GET /buildlet/create: expected code 400, got %d", w.Code)
}
if body := w.Body.String(); !strings.Contains(body, `client version "20150922" is too old`) {
t.Fatalf("GET /buildlet/create: expected 'version too old' error, got %s", body)
}
}
func addBuilder(name string) {
dashboard.Builders[name] = &dashboard.BuildConfig{
Name: name,
HostType: "test-host",
Notes: "Dummy client for testing",
}
dashboard.Hosts["test-host"] = &dashboard.HostConfig{
HostType: "test-host",
Owners: []*gophers.Person{{Emails: []string{"test@golang.org"}}},
}
testPool.Add("test-host", &buildlet.FakeClient{})
}
func removeBuilder(name string) {
delete(dashboard.Builders, name)
delete(dashboard.Builders, "test-host")
testPool.Remove("test-host")
}
const buildName = "linux-amd64-test"
type tlogger struct{ t *testing.T }
func (t tlogger) Write(p []byte) (int, error) {
t.t.Logf("LOG: %s", p)
return len(p), nil
}
func TestHandleBuildletCreate_PreStream(t *testing.T) {
log.SetOutput(tlogger{t})
defer log.SetOutput(os.Stderr)
addBuilder(buildName)
remoteBuildlets.M = map[string]*remote.Buildlet{}
pool.TestPoolHook = func(_ *dashboard.HostConfig) pool.Buildlet { return testPool }
defer func() {
timeNow = time.Now
removeBuilder(buildName)
pool.TestPoolHook = nil
}()
timeNow = func() time.Time { return time.Unix(123, 0).In(time.UTC) }
data := url.Values{}
data.Set("version", "20160922")
data.Set("builderType", buildName)
req := httptest.NewRequest("POST", "/buildlet/create", strings.NewReader(data.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth("gopher", "fake-not-checked-password") // auth is handled in outer wrapper
w := httptest.NewRecorder()
handleBuildletCreate(w, req)
if w.Code != 200 {
t.Fatal("bad code", w.Code, w.Body.String())
}
want := `{"User":"gopher","Name":"gopher-linux-amd64-test-0","HostType":"test-host","BuilderType":"linux-amd64-test","Created":"1970-01-01T00:02:03Z","Expires":"1970-01-01T00:32:03Z"}`
if got := strings.TrimSpace(w.Body.String()); got != want {
t.Errorf("unexpected output.\n got: %s\nwant: %s\n", got, want)
}
}
func TestHandleBuildletCreate_Stream(t *testing.T) {
log.SetOutput(tlogger{t})
defer log.SetOutput(os.Stderr)
addBuilder(buildName)
remoteBuildlets.M = map[string]*remote.Buildlet{}
pool.TestPoolHook = func(_ *dashboard.HostConfig) pool.Buildlet { return testPool }
defer func() {
timeNow = time.Now
removeBuilder(buildName)
pool.TestPoolHook = nil
}()
timeNow = func() time.Time { return time.Unix(123, 0).In(time.UTC) }
data := url.Values{}
data.Set("version", buildlet.GomoteCreateStreamVersion)
data.Set("builderType", buildName)
req := httptest.NewRequest("POST", "/buildlet/create", strings.NewReader(data.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth("gopher", "fake-not-checked-password") // auth is handled in outer wrapper
w := httptest.NewRecorder()
handleBuildletCreate(w, req)
if w.Code != 200 {
t.Fatal("bad code", w.Code, w.Body.String())
}
want := `{"buildlet":{"User":"gopher","Name":"gopher-linux-amd64-test-0","HostType":"test-host","BuilderType":"linux-amd64-test","Created":"1970-01-01T00:02:03Z","Expires":"1970-01-01T00:32:03Z"}}`
if got := strings.TrimSpace(w.Body.String()); got != want {
t.Errorf("unexpected output.\n got: %s\nwant: %s\n", got, want)
}
}