| Index: milo/appengine/swarming/html_data.go
|
| diff --git a/milo/appengine/swarming/html_data.go b/milo/appengine/swarming/html_data.go
|
| index fb54fd8a9e995c9c01b1d3b4be7f23cb49c1eae9..26d53c563ab4f715319dc397bcad5c1ab03eec5d 100644
|
| --- a/milo/appengine/swarming/html_data.go
|
| +++ b/milo/appengine/swarming/html_data.go
|
| @@ -8,84 +8,129 @@ import (
|
| "encoding/json"
|
| "fmt"
|
| "io/ioutil"
|
| - "os"
|
| "path/filepath"
|
| + "sort"
|
| "strings"
|
| "time"
|
|
|
| + "github.com/golang/protobuf/proto"
|
| + "github.com/luci/gae/impl/memory"
|
| "golang.org/x/net/context"
|
| + "google.golang.org/grpc"
|
|
|
| swarming "github.com/luci/luci-go/common/api/swarming/swarming/v1"
|
| "github.com/luci/luci-go/common/clock/testclock"
|
| "github.com/luci/luci-go/common/errors"
|
| + miloProto "github.com/luci/luci-go/common/proto/milo"
|
| + "github.com/luci/luci-go/logdog/api/endpoints/coordinator/logs/v1"
|
| + "github.com/luci/luci-go/logdog/api/logpb"
|
| + "github.com/luci/luci-go/logdog/client/coordinator"
|
| "github.com/luci/luci-go/milo/api/resp"
|
| "github.com/luci/luci-go/milo/appengine/settings"
|
| "github.com/luci/luci-go/server/templates"
|
| )
|
|
|
| -var testCases = []struct {
|
| - input string
|
| - expectations string
|
| -}{
|
| - {"build-canceled", "build-canceled.json"},
|
| - {"build-exception", "build-exception.json"},
|
| - {"build-expired", "build-expired.json"},
|
| - {"build-link", "build-link.json"},
|
| - {"build-patch-failure", "build-patch-failure.json"},
|
| - {"build-pending", "build-pending.json"},
|
| - {"build-running", "build-running.json"},
|
| - {"build-timeout", "build-timeout.json"},
|
| - {"build-unicode", "build-unicode.json"},
|
| - {"build-nested", "build-nested.json"},
|
| -}
|
| -
|
| -func getTestCases() []string {
|
| +type testCase struct {
|
| + name string
|
| +
|
| + swarmResult string
|
| + swarmOutput string
|
| + annotations string
|
| +}
|
| +
|
| +func getTestCases() []*testCase {
|
| + testCases := make(map[string]*testCase)
|
| +
|
| // References "milo/appengine/swarming/testdata".
|
| testdata := filepath.Join("..", "swarming", "testdata")
|
| - results := []string{}
|
| f, err := ioutil.ReadDir(testdata)
|
| if err != nil {
|
| panic(err)
|
| }
|
| for _, fi := range f {
|
| - if strings.HasSuffix(fi.Name(), ".swarm") {
|
| - results = append(results, fi.Name()[0:len(fi.Name())-6])
|
| + fileName := fi.Name()
|
| + parts := strings.SplitN(fileName, ".", 2)
|
| +
|
| + name := parts[0]
|
| + tc := testCases[name]
|
| + if tc == nil {
|
| + tc = &testCase{name: name}
|
| + testCases[name] = tc
|
| + }
|
| +
|
| + switch {
|
| + case len(parts) == 1:
|
| + tc.swarmOutput = fileName
|
| + case parts[1] == "swarm":
|
| + tc.swarmResult = fileName
|
| + case parts[1] == "pb.txt":
|
| + tc.annotations = fileName
|
| }
|
| }
|
| +
|
| + // Order test cases by name.
|
| + names := make([]string, 0, len(testCases))
|
| + for name := range testCases {
|
| + names = append(names, name)
|
| + }
|
| + sort.Strings(names)
|
| +
|
| + results := make([]*testCase, len(names))
|
| + for i, name := range names {
|
| + results[i] = testCases[name]
|
| + }
|
| +
|
| return results
|
| }
|
|
|
| -type debugSwarmingService struct{}
|
| -
|
| -func (svc debugSwarmingService) getHost() string { return "example.com" }
|
| +func (tc *testCase) getContent(name string) []byte {
|
| + if name == "" {
|
| + return nil
|
| + }
|
|
|
| -func (svc debugSwarmingService) getContent(taskID, suffix string) ([]byte, error) {
|
| // ../swarming below assumes that
|
| // - this code is not executed by tests outside of this dir
|
| // - this dir is a sibling of frontend dir
|
| - logFilename := filepath.Join("..", "swarming", "testdata", taskID)
|
| - if suffix != "" {
|
| - logFilename += suffix
|
| + path := filepath.Join("..", "swarming", "testdata", name)
|
| + data, err := ioutil.ReadFile(path)
|
| + if err != nil {
|
| + panic(fmt.Errorf("failed to read [%s]: %s", path, err))
|
| }
|
| - return ioutil.ReadFile(logFilename)
|
| + return data
|
| }
|
|
|
| -func (svc debugSwarmingService) getSwarmingJSON(taskID, suffix string, dst interface{}) error {
|
| - content, err := svc.getContent(taskID, suffix)
|
| - if err != nil {
|
| - return err
|
| +func (tc *testCase) getSwarmingResult() *swarming.SwarmingRpcsTaskResult {
|
| + var sr swarming.SwarmingRpcsTaskResult
|
| + data := tc.getContent(tc.swarmResult)
|
| + if err := json.Unmarshal(data, &sr); err != nil {
|
| + panic(fmt.Errorf("failed to unmarshal [%s]: %s", tc.swarmResult, err))
|
| }
|
| - return json.Unmarshal(content, dst)
|
| + return &sr
|
| }
|
|
|
| +func (tc *testCase) getSwarmingOutput() string {
|
| + return string(tc.getContent(tc.swarmOutput))
|
| +}
|
| +
|
| +func (tc *testCase) getAnnotation() *miloProto.Step {
|
| + var step miloProto.Step
|
| + data := tc.getContent(tc.annotations)
|
| + if err := proto.UnmarshalText(string(data), &step); err != nil {
|
| + panic(fmt.Errorf("failed to unmarshal text protobuf [%s]: %s", tc.annotations, err))
|
| + }
|
| + return &step
|
| +}
|
| +
|
| +type debugSwarmingService struct {
|
| + tc *testCase
|
| +}
|
| +
|
| +func (svc debugSwarmingService) getHost() string { return "example.com" }
|
| +
|
| func (svc debugSwarmingService) getSwarmingResult(c context.Context, taskID string) (
|
| *swarming.SwarmingRpcsTaskResult, error) {
|
|
|
| - var sr swarming.SwarmingRpcsTaskResult
|
| - if err := svc.getSwarmingJSON(taskID, ".swarm", &sr); err != nil {
|
| - return nil, err
|
| - }
|
| - return &sr, nil
|
| + return svc.tc.getSwarmingResult(), nil
|
| }
|
|
|
| func (svc debugSwarmingService) getSwarmingRequest(c context.Context, taskID string) (
|
| @@ -95,14 +140,7 @@ func (svc debugSwarmingService) getSwarmingRequest(c context.Context, taskID str
|
| }
|
|
|
| func (svc debugSwarmingService) getTaskOutput(c context.Context, taskID string) (string, error) {
|
| - content, err := svc.getContent(taskID, "")
|
| - if err != nil {
|
| - if os.IsNotExist(err) {
|
| - err = nil
|
| - }
|
| - return "", err
|
| - }
|
| - return string(content), nil
|
| + return svc.tc.getSwarmingOutput(), nil
|
| }
|
|
|
| // TestableLog is a subclass of Log that interfaces with TestableHandler and
|
| @@ -126,6 +164,64 @@ func (l TestableLog) TestData() []settings.TestBundle {
|
| }
|
| }
|
|
|
| +// testLogDogClient is a minimal functional LogsClient implementation.
|
| +//
|
| +// It retains its latest input parameter and returns its configured err (if not
|
| +// nil) or resp.
|
| +type testLogDogClient struct {
|
| + logdog.LogsClient
|
| +
|
| + req interface{}
|
| + resp interface{}
|
| + err error
|
| +}
|
| +
|
| +func (tc *testLogDogClient) Tail(ctx context.Context, in *logdog.TailRequest, opts ...grpc.CallOption) (
|
| + *logdog.GetResponse, error) {
|
| +
|
| + tc.req = in
|
| + if tc.err != nil {
|
| + return nil, tc.err
|
| + }
|
| + return tc.resp.(*logdog.GetResponse), nil
|
| +}
|
| +
|
| +func logDogClientFunc(tc *testCase) func(context.Context, string) (*coordinator.Client, error) {
|
| + return func(c context.Context, host string) (*coordinator.Client, error) {
|
| + return &coordinator.Client{
|
| + Host: "example.com",
|
| + C: &testLogDogClient{
|
| + resp: datagramGetResponse("testproject", "foo/bar", tc.getAnnotation()),
|
| + },
|
| + }, nil
|
| + }
|
| +}
|
| +
|
| +func datagramGetResponse(project, prefix string, msg proto.Message) *logdog.GetResponse {
|
| + data, err := proto.Marshal(msg)
|
| + if err != nil {
|
| + panic(err)
|
| + }
|
| + return &logdog.GetResponse{
|
| + Project: project,
|
| + Desc: &logpb.LogStreamDescriptor{
|
| + Prefix: prefix,
|
| + ContentType: miloProto.ContentTypeAnnotations,
|
| + StreamType: logpb.StreamType_DATAGRAM,
|
| + },
|
| + State: &logdog.LogStreamState{},
|
| + Logs: []*logpb.LogEntry{
|
| + {
|
| + Content: &logpb.LogEntry_Datagram{
|
| + Datagram: &logpb.Datagram{
|
| + Data: data,
|
| + },
|
| + },
|
| + },
|
| + },
|
| + }
|
| +}
|
| +
|
| // TestData returns sample test data.
|
| func (b TestableBuild) TestData() []settings.TestBundle {
|
| basic := resp.MiloBuild{
|
| @@ -145,15 +241,20 @@ func (b TestableBuild) TestData() []settings.TestBundle {
|
| }
|
| c := context.Background()
|
| c, _ = testclock.UseTime(c, time.Date(2016, time.March, 14, 11, 0, 0, 0, time.UTC))
|
| + c = memory.Use(c)
|
|
|
| - var svc debugSwarmingService
|
| for _, tc := range getTestCases() {
|
| - build, err := swarmingBuildImpl(c, svc, "foo", tc)
|
| + svc := debugSwarmingService{tc}
|
| + bl := buildLoader{
|
| + logDogClientFunc: logDogClientFunc(tc),
|
| + }
|
| +
|
| + build, err := bl.swarmingBuildImpl(c, svc, "foo", tc.name)
|
| if err != nil {
|
| - panic(fmt.Errorf("Error while processing %s: %s", tc, err))
|
| + panic(fmt.Errorf("Error while processing %s: %s", tc.name, err))
|
| }
|
| results = append(results, settings.TestBundle{
|
| - Description: tc,
|
| + Description: tc.name,
|
| Data: templates.Args{"Build": build},
|
| })
|
| }
|
|
|