| OLD | NEW |
| 1 // Copyright 2015 The LUCI Authors. All rights reserved. | 1 // Copyright 2015 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package swarming | 5 package swarming |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "encoding/json" | 8 "encoding/json" |
| 9 "fmt" | 9 "fmt" |
| 10 "io/ioutil" | 10 "io/ioutil" |
| 11 "os" | |
| 12 "path/filepath" | 11 "path/filepath" |
| 12 "sort" |
| 13 "strings" | 13 "strings" |
| 14 "time" | 14 "time" |
| 15 | 15 |
| 16 "github.com/golang/protobuf/proto" |
| 17 "github.com/luci/gae/impl/memory" |
| 16 "golang.org/x/net/context" | 18 "golang.org/x/net/context" |
| 19 "google.golang.org/grpc" |
| 17 | 20 |
| 18 swarming "github.com/luci/luci-go/common/api/swarming/swarming/v1" | 21 swarming "github.com/luci/luci-go/common/api/swarming/swarming/v1" |
| 19 "github.com/luci/luci-go/common/clock/testclock" | 22 "github.com/luci/luci-go/common/clock/testclock" |
| 20 "github.com/luci/luci-go/common/errors" | 23 "github.com/luci/luci-go/common/errors" |
| 24 miloProto "github.com/luci/luci-go/common/proto/milo" |
| 25 "github.com/luci/luci-go/logdog/api/endpoints/coordinator/logs/v1" |
| 26 "github.com/luci/luci-go/logdog/api/logpb" |
| 27 "github.com/luci/luci-go/logdog/client/coordinator" |
| 21 "github.com/luci/luci-go/milo/api/resp" | 28 "github.com/luci/luci-go/milo/api/resp" |
| 22 "github.com/luci/luci-go/milo/appengine/settings" | 29 "github.com/luci/luci-go/milo/appengine/settings" |
| 23 "github.com/luci/luci-go/server/templates" | 30 "github.com/luci/luci-go/server/templates" |
| 24 ) | 31 ) |
| 25 | 32 |
| 26 var testCases = []struct { | 33 type testCase struct { |
| 27 » input string | 34 » name string |
| 28 » expectations string | 35 |
| 29 }{ | 36 » swarmResult string |
| 30 » {"build-canceled", "build-canceled.json"}, | 37 » swarmOutput string |
| 31 » {"build-exception", "build-exception.json"}, | 38 » annotations string |
| 32 » {"build-expired", "build-expired.json"}, | |
| 33 » {"build-link", "build-link.json"}, | |
| 34 » {"build-patch-failure", "build-patch-failure.json"}, | |
| 35 » {"build-pending", "build-pending.json"}, | |
| 36 » {"build-running", "build-running.json"}, | |
| 37 » {"build-timeout", "build-timeout.json"}, | |
| 38 » {"build-unicode", "build-unicode.json"}, | |
| 39 » {"build-nested", "build-nested.json"}, | |
| 40 } | 39 } |
| 41 | 40 |
| 42 func getTestCases() []string { | 41 func getTestCases() []*testCase { |
| 42 » testCases := make(map[string]*testCase) |
| 43 |
| 43 // References "milo/appengine/swarming/testdata". | 44 // References "milo/appengine/swarming/testdata". |
| 44 testdata := filepath.Join("..", "swarming", "testdata") | 45 testdata := filepath.Join("..", "swarming", "testdata") |
| 45 results := []string{} | |
| 46 f, err := ioutil.ReadDir(testdata) | 46 f, err := ioutil.ReadDir(testdata) |
| 47 if err != nil { | 47 if err != nil { |
| 48 panic(err) | 48 panic(err) |
| 49 } | 49 } |
| 50 for _, fi := range f { | 50 for _, fi := range f { |
| 51 » » if strings.HasSuffix(fi.Name(), ".swarm") { | 51 » » fileName := fi.Name() |
| 52 » » » results = append(results, fi.Name()[0:len(fi.Name())-6]) | 52 » » parts := strings.SplitN(fileName, ".", 2) |
| 53 |
| 54 » » name := parts[0] |
| 55 » » tc := testCases[name] |
| 56 » » if tc == nil { |
| 57 » » » tc = &testCase{name: name} |
| 58 » » » testCases[name] = tc |
| 59 » » } |
| 60 |
| 61 » » switch { |
| 62 » » case len(parts) == 1: |
| 63 » » » tc.swarmOutput = fileName |
| 64 » » case parts[1] == "swarm": |
| 65 » » » tc.swarmResult = fileName |
| 66 » » case parts[1] == "pb.txt": |
| 67 » » » tc.annotations = fileName |
| 53 } | 68 } |
| 54 } | 69 } |
| 70 |
| 71 // Order test cases by name. |
| 72 names := make([]string, 0, len(testCases)) |
| 73 for name := range testCases { |
| 74 names = append(names, name) |
| 75 } |
| 76 sort.Strings(names) |
| 77 |
| 78 results := make([]*testCase, len(names)) |
| 79 for i, name := range names { |
| 80 results[i] = testCases[name] |
| 81 } |
| 82 |
| 55 return results | 83 return results |
| 56 } | 84 } |
| 57 | 85 |
| 58 type debugSwarmingService struct{} | 86 func (tc *testCase) getContent(name string) []byte { |
| 87 » if name == "" { |
| 88 » » return nil |
| 89 » } |
| 59 | 90 |
| 60 func (svc debugSwarmingService) getHost() string { return "example.com" } | |
| 61 | |
| 62 func (svc debugSwarmingService) getContent(taskID, suffix string) ([]byte, error
) { | |
| 63 // ../swarming below assumes that | 91 // ../swarming below assumes that |
| 64 // - this code is not executed by tests outside of this dir | 92 // - this code is not executed by tests outside of this dir |
| 65 // - this dir is a sibling of frontend dir | 93 // - this dir is a sibling of frontend dir |
| 66 » logFilename := filepath.Join("..", "swarming", "testdata", taskID) | 94 » path := filepath.Join("..", "swarming", "testdata", name) |
| 67 » if suffix != "" { | 95 » data, err := ioutil.ReadFile(path) |
| 68 » » logFilename += suffix | 96 » if err != nil { |
| 97 » » panic(fmt.Errorf("failed to read [%s]: %s", path, err)) |
| 69 } | 98 } |
| 70 » return ioutil.ReadFile(logFilename) | 99 » return data |
| 71 } | 100 } |
| 72 | 101 |
| 73 func (svc debugSwarmingService) getSwarmingJSON(taskID, suffix string, dst inter
face{}) error { | 102 func (tc *testCase) getSwarmingResult() *swarming.SwarmingRpcsTaskResult { |
| 74 » content, err := svc.getContent(taskID, suffix) | 103 » var sr swarming.SwarmingRpcsTaskResult |
| 75 » if err != nil { | 104 » data := tc.getContent(tc.swarmResult) |
| 76 » » return err | 105 » if err := json.Unmarshal(data, &sr); err != nil { |
| 106 » » panic(fmt.Errorf("failed to unmarshal [%s]: %s", tc.swarmResult,
err)) |
| 77 } | 107 } |
| 78 » return json.Unmarshal(content, dst) | 108 » return &sr |
| 79 } | 109 } |
| 80 | 110 |
| 111 func (tc *testCase) getSwarmingOutput() string { |
| 112 return string(tc.getContent(tc.swarmOutput)) |
| 113 } |
| 114 |
| 115 func (tc *testCase) getAnnotation() *miloProto.Step { |
| 116 var step miloProto.Step |
| 117 data := tc.getContent(tc.annotations) |
| 118 if err := proto.UnmarshalText(string(data), &step); err != nil { |
| 119 panic(fmt.Errorf("failed to unmarshal text protobuf [%s]: %s", t
c.annotations, err)) |
| 120 } |
| 121 return &step |
| 122 } |
| 123 |
| 124 type debugSwarmingService struct { |
| 125 tc *testCase |
| 126 } |
| 127 |
| 128 func (svc debugSwarmingService) getHost() string { return "example.com" } |
| 129 |
| 81 func (svc debugSwarmingService) getSwarmingResult(c context.Context, taskID stri
ng) ( | 130 func (svc debugSwarmingService) getSwarmingResult(c context.Context, taskID stri
ng) ( |
| 82 *swarming.SwarmingRpcsTaskResult, error) { | 131 *swarming.SwarmingRpcsTaskResult, error) { |
| 83 | 132 |
| 84 » var sr swarming.SwarmingRpcsTaskResult | 133 » return svc.tc.getSwarmingResult(), nil |
| 85 » if err := svc.getSwarmingJSON(taskID, ".swarm", &sr); err != nil { | |
| 86 » » return nil, err | |
| 87 » } | |
| 88 » return &sr, nil | |
| 89 } | 134 } |
| 90 | 135 |
| 91 func (svc debugSwarmingService) getSwarmingRequest(c context.Context, taskID str
ing) ( | 136 func (svc debugSwarmingService) getSwarmingRequest(c context.Context, taskID str
ing) ( |
| 92 *swarming.SwarmingRpcsTaskRequest, error) { | 137 *swarming.SwarmingRpcsTaskRequest, error) { |
| 93 | 138 |
| 94 return nil, errors.New("not implemented") | 139 return nil, errors.New("not implemented") |
| 95 } | 140 } |
| 96 | 141 |
| 97 func (svc debugSwarmingService) getTaskOutput(c context.Context, taskID string)
(string, error) { | 142 func (svc debugSwarmingService) getTaskOutput(c context.Context, taskID string)
(string, error) { |
| 98 » content, err := svc.getContent(taskID, "") | 143 » return svc.tc.getSwarmingOutput(), nil |
| 99 » if err != nil { | |
| 100 » » if os.IsNotExist(err) { | |
| 101 » » » err = nil | |
| 102 » » } | |
| 103 » » return "", err | |
| 104 » } | |
| 105 » return string(content), nil | |
| 106 } | 144 } |
| 107 | 145 |
| 108 // TestableLog is a subclass of Log that interfaces with TestableHandler and | 146 // TestableLog is a subclass of Log that interfaces with TestableHandler and |
| 109 // includes sample test data. | 147 // includes sample test data. |
| 110 type TestableLog struct{ Log } | 148 type TestableLog struct{ Log } |
| 111 | 149 |
| 112 // TestableBuild is a subclass of Build that interfaces with TestableHandler and | 150 // TestableBuild is a subclass of Build that interfaces with TestableHandler and |
| 113 // includes sample test data. | 151 // includes sample test data. |
| 114 type TestableBuild struct{ Build } | 152 type TestableBuild struct{ Build } |
| 115 | 153 |
| 116 // TestData returns sample test data. | 154 // TestData returns sample test data. |
| 117 func (l TestableLog) TestData() []settings.TestBundle { | 155 func (l TestableLog) TestData() []settings.TestBundle { |
| 118 return []settings.TestBundle{ | 156 return []settings.TestBundle{ |
| 119 { | 157 { |
| 120 Description: "Basic log", | 158 Description: "Basic log", |
| 121 Data: templates.Args{ | 159 Data: templates.Args{ |
| 122 "Log": "This is the log", | 160 "Log": "This is the log", |
| 123 "Closed": true, | 161 "Closed": true, |
| 124 }, | 162 }, |
| 125 }, | 163 }, |
| 126 } | 164 } |
| 127 } | 165 } |
| 128 | 166 |
| 167 // testLogDogClient is a minimal functional LogsClient implementation. |
| 168 // |
| 169 // It retains its latest input parameter and returns its configured err (if not |
| 170 // nil) or resp. |
| 171 type testLogDogClient struct { |
| 172 logdog.LogsClient |
| 173 |
| 174 req interface{} |
| 175 resp interface{} |
| 176 err error |
| 177 } |
| 178 |
| 179 func (tc *testLogDogClient) Tail(ctx context.Context, in *logdog.TailRequest, op
ts ...grpc.CallOption) ( |
| 180 *logdog.GetResponse, error) { |
| 181 |
| 182 tc.req = in |
| 183 if tc.err != nil { |
| 184 return nil, tc.err |
| 185 } |
| 186 return tc.resp.(*logdog.GetResponse), nil |
| 187 } |
| 188 |
| 189 func logDogClientFunc(tc *testCase) func(context.Context, string) (*coordinator.
Client, error) { |
| 190 return func(c context.Context, host string) (*coordinator.Client, error)
{ |
| 191 return &coordinator.Client{ |
| 192 Host: "example.com", |
| 193 C: &testLogDogClient{ |
| 194 resp: datagramGetResponse("testproject", "foo/ba
r", tc.getAnnotation()), |
| 195 }, |
| 196 }, nil |
| 197 } |
| 198 } |
| 199 |
| 200 func datagramGetResponse(project, prefix string, msg proto.Message) *logdog.GetR
esponse { |
| 201 data, err := proto.Marshal(msg) |
| 202 if err != nil { |
| 203 panic(err) |
| 204 } |
| 205 return &logdog.GetResponse{ |
| 206 Project: project, |
| 207 Desc: &logpb.LogStreamDescriptor{ |
| 208 Prefix: prefix, |
| 209 ContentType: miloProto.ContentTypeAnnotations, |
| 210 StreamType: logpb.StreamType_DATAGRAM, |
| 211 }, |
| 212 State: &logdog.LogStreamState{}, |
| 213 Logs: []*logpb.LogEntry{ |
| 214 { |
| 215 Content: &logpb.LogEntry_Datagram{ |
| 216 Datagram: &logpb.Datagram{ |
| 217 Data: data, |
| 218 }, |
| 219 }, |
| 220 }, |
| 221 }, |
| 222 } |
| 223 } |
| 224 |
| 129 // TestData returns sample test data. | 225 // TestData returns sample test data. |
| 130 func (b TestableBuild) TestData() []settings.TestBundle { | 226 func (b TestableBuild) TestData() []settings.TestBundle { |
| 131 basic := resp.MiloBuild{ | 227 basic := resp.MiloBuild{ |
| 132 Summary: resp.BuildComponent{ | 228 Summary: resp.BuildComponent{ |
| 133 Label: "Test swarming build", | 229 Label: "Test swarming build", |
| 134 Status: resp.Success, | 230 Status: resp.Success, |
| 135 Started: time.Date(2016, 1, 2, 15, 4, 5, 999999999, tim
e.UTC), | 231 Started: time.Date(2016, 1, 2, 15, 4, 5, 999999999, tim
e.UTC), |
| 136 Finished: time.Date(2016, 1, 2, 15, 4, 6, 999999999, tim
e.UTC), | 232 Finished: time.Date(2016, 1, 2, 15, 4, 6, 999999999, tim
e.UTC), |
| 137 Duration: time.Second, | 233 Duration: time.Second, |
| 138 }, | 234 }, |
| 139 } | 235 } |
| 140 results := []settings.TestBundle{ | 236 results := []settings.TestBundle{ |
| 141 { | 237 { |
| 142 Description: "Basic successful build", | 238 Description: "Basic successful build", |
| 143 Data: templates.Args{"Build": basic}, | 239 Data: templates.Args{"Build": basic}, |
| 144 }, | 240 }, |
| 145 } | 241 } |
| 146 c := context.Background() | 242 c := context.Background() |
| 147 c, _ = testclock.UseTime(c, time.Date(2016, time.March, 14, 11, 0, 0, 0,
time.UTC)) | 243 c, _ = testclock.UseTime(c, time.Date(2016, time.March, 14, 11, 0, 0, 0,
time.UTC)) |
| 244 c = memory.Use(c) |
| 148 | 245 |
| 149 var svc debugSwarmingService | |
| 150 for _, tc := range getTestCases() { | 246 for _, tc := range getTestCases() { |
| 151 » » build, err := swarmingBuildImpl(c, svc, "foo", tc) | 247 » » svc := debugSwarmingService{tc} |
| 248 » » bl := buildLoader{ |
| 249 » » » logDogClientFunc: logDogClientFunc(tc), |
| 250 » » } |
| 251 |
| 252 » » build, err := bl.swarmingBuildImpl(c, svc, "foo", tc.name) |
| 152 if err != nil { | 253 if err != nil { |
| 153 » » » panic(fmt.Errorf("Error while processing %s: %s", tc, er
r)) | 254 » » » panic(fmt.Errorf("Error while processing %s: %s", tc.nam
e, err)) |
| 154 } | 255 } |
| 155 results = append(results, settings.TestBundle{ | 256 results = append(results, settings.TestBundle{ |
| 156 » » » Description: tc, | 257 » » » Description: tc.name, |
| 157 Data: templates.Args{"Build": build}, | 258 Data: templates.Args{"Build": build}, |
| 158 }) | 259 }) |
| 159 } | 260 } |
| 160 return results | 261 return results |
| 161 } | 262 } |
| OLD | NEW |