OLD | NEW |
| (Empty) |
1 // Copyright 2016 The LUCI Authors. All rights reserved. | |
2 // Use of this source code is governed under the Apache License, Version 2.0 | |
3 // that can be found in the LICENSE file. | |
4 | |
5 package buildbot | |
6 | |
7 import ( | |
8 "bytes" | |
9 "compress/zlib" | |
10 "encoding/base64" | |
11 "encoding/json" | |
12 "io" | |
13 "io/ioutil" | |
14 "math/rand" | |
15 "net/http" | |
16 "net/http/httptest" | |
17 "testing" | |
18 "time" | |
19 | |
20 "github.com/luci/gae/impl/memory" | |
21 ds "github.com/luci/gae/service/datastore" | |
22 "github.com/luci/luci-go/common/clock/testclock" | |
23 memcfg "github.com/luci/luci-go/common/config/impl/memory" | |
24 "github.com/luci/luci-go/common/logging/gologger" | |
25 "github.com/luci/luci-go/luci_config/server/cfgclient/backend/testconfig
" | |
26 "github.com/luci/luci-go/milo/appengine/common" | |
27 "github.com/luci/luci-go/server/auth" | |
28 "github.com/luci/luci-go/server/auth/authtest" | |
29 "github.com/luci/luci-go/server/auth/identity" | |
30 "github.com/luci/luci-go/server/router" | |
31 | |
32 "github.com/julienschmidt/httprouter" | |
33 "golang.org/x/net/context" | |
34 | |
35 . "github.com/luci/luci-go/common/testing/assertions" | |
36 . "github.com/smartystreets/goconvey/convey" | |
37 ) | |
38 | |
39 var ( | |
40 fakeTime = time.Date(2001, time.February, 3, 4, 5, 6, 7, time.UTC) | |
41 letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX
YZ") | |
42 ) | |
43 | |
44 func RandStringRunes(n int) string { | |
45 b := make([]rune, n) | |
46 for i := range b { | |
47 b[i] = letterRunes[rand.Intn(len(letterRunes))] | |
48 } | |
49 return string(b) | |
50 } | |
51 | |
52 func buildbotTimesFinished(start, end float64) []*float64 { | |
53 return []*float64{&start, &end} | |
54 } | |
55 | |
56 func buildbotTimesPending(start float64) []*float64 { | |
57 return []*float64{&start, nil} | |
58 } | |
59 | |
60 func newCombinedPsBody(bs []*buildbotBuild, m *buildbotMaster, internal bool) io
.ReadCloser { | |
61 bmsg := buildMasterMsg{ | |
62 Master: m, | |
63 Builds: bs, | |
64 } | |
65 bm, _ := json.Marshal(bmsg) | |
66 var b bytes.Buffer | |
67 zw := zlib.NewWriter(&b) | |
68 zw.Write(bm) | |
69 zw.Close() | |
70 sub := "projects/luci-milo/subscriptions/buildbot-public" | |
71 if internal { | |
72 sub = "projects/luci-milo/subscriptions/buildbot-private" | |
73 } | |
74 msg := pubSubSubscription{ | |
75 Subscription: sub, | |
76 Message: pubSubMessage{ | |
77 Data: base64.StdEncoding.EncodeToString(b.Bytes()), | |
78 }, | |
79 } | |
80 jmsg, _ := json.Marshal(msg) | |
81 return ioutil.NopCloser(bytes.NewReader(jmsg)) | |
82 } | |
83 | |
84 func TestPubSub(t *testing.T) { | |
85 Convey(`A test Environment`, t, func() { | |
86 c := memory.UseWithAppID(context.Background(), "dev~luci-milo") | |
87 c = gologger.StdConfig.Use(c) | |
88 c, _ = testclock.UseTime(c, fakeTime) | |
89 c = testconfig.WithCommonClient(c, memcfg.New(bbAclConfigs)) | |
90 c = auth.WithState(c, &authtest.FakeState{ | |
91 Identity: identity.AnonymousIdentity, | |
92 IdentityGroups: []string{"all"}, | |
93 }) | |
94 // Update the service config so that the settings are loaded. | |
95 err := common.UpdateServiceConfig(c) | |
96 So(err, ShouldBeNil) | |
97 | |
98 rand.Seed(5) | |
99 | |
100 Convey("Remove source changes", func() { | |
101 m := &buildbotMaster{ | |
102 Name: "fake", | |
103 Builders: map[string]*buildbotBuilder{ | |
104 "fake builder": { | |
105 PendingBuildStates: []*buildbotP
ending{ | |
106 { | |
107 Source: buildbot
SourceStamp{ | |
108 Changes:
[]buildbotChange{{Comments: "foo"}}, | |
109 }, | |
110 }, | |
111 }, | |
112 }, | |
113 }, | |
114 } | |
115 So(putDSMasterJSON(c, m, false), ShouldBeNil) | |
116 lm, _, _, err := getMasterJSON(c, "fake") | |
117 So(err, ShouldBeNil) | |
118 So(lm.Builders["fake builder"].PendingBuildStates[0].Sou
rce.Changes[0].Comments, ShouldResemble, "") | |
119 }) | |
120 | |
121 Convey("Save build entry", func() { | |
122 build := &buildbotBuild{ | |
123 Master: "Fake Master", | |
124 Buildername: "Fake buildername", | |
125 Number: 1234, | |
126 Currentstep: "this is a string", | |
127 Finished: true, | |
128 } | |
129 err := ds.Put(c, build) | |
130 ds.GetTestable(c).CatchupIndexes() | |
131 | |
132 So(err, ShouldBeNil) | |
133 Convey("Load build entry", func() { | |
134 loadB := &buildbotBuild{ | |
135 Master: "Fake Master", | |
136 Buildername: "Fake buildername", | |
137 Number: 1234, | |
138 } | |
139 err = ds.Get(c, loadB) | |
140 So(err, ShouldBeNil) | |
141 So(loadB.Master, ShouldEqual, "Fake Master") | |
142 So(loadB.Internal, ShouldEqual, false) | |
143 So(loadB.Currentstep.(string), ShouldEqual, "thi
s is a string") | |
144 So(loadB.Finished, ShouldEqual, true) | |
145 }) | |
146 | |
147 Convey("Query build entry", func() { | |
148 q := ds.NewQuery("buildbotBuild") | |
149 buildbots := []*buildbotBuild{} | |
150 err = ds.GetAll(c, q, &buildbots) | |
151 So(err, ShouldBeNil) | |
152 | |
153 So(len(buildbots), ShouldEqual, 1) | |
154 So(buildbots[0].Currentstep.(string), ShouldEqua
l, "this is a string") | |
155 Convey("Query for finished entries should be 1",
func() { | |
156 q = q.Eq("finished", true) | |
157 buildbots = []*buildbotBuild{} | |
158 err = ds.GetAll(c, q, &buildbots) | |
159 So(err, ShouldBeNil) | |
160 So(len(buildbots), ShouldEqual, 1) | |
161 }) | |
162 Convey("Query for unfinished entries should be 0
", func() { | |
163 q = q.Eq("finished", false) | |
164 buildbots = []*buildbotBuild{} | |
165 err = ds.GetAll(c, q, &buildbots) | |
166 So(err, ShouldBeNil) | |
167 So(len(buildbots), ShouldEqual, 0) | |
168 }) | |
169 }) | |
170 | |
171 Convey("Save a few more entries", func() { | |
172 ds.GetTestable(c).Consistent(true) | |
173 ds.GetTestable(c).AutoIndex(true) | |
174 for i := 1235; i < 1240; i++ { | |
175 build := &buildbotBuild{ | |
176 Master: "Fake Master", | |
177 Buildername: "Fake buildername", | |
178 Number: i, | |
179 Currentstep: "this is a string", | |
180 Finished: i%2 == 0, | |
181 } | |
182 err := ds.Put(c, build) | |
183 So(err, ShouldBeNil) | |
184 } | |
185 q := ds.NewQuery("buildbotBuild") | |
186 q = q.Eq("finished", true) | |
187 q = q.Eq("master", "Fake Master") | |
188 q = q.Eq("builder", "Fake buildername") | |
189 q = q.Order("-number") | |
190 buildbots := []*buildbotBuild{} | |
191 err = ds.GetAll(c, q, &buildbots) | |
192 So(err, ShouldBeNil) | |
193 So(len(buildbots), ShouldEqual, 3) // 1235, 1237
, 1239 | |
194 }) | |
195 }) | |
196 | |
197 Convey("Build panics on invalid query", func() { | |
198 build := &buildbotBuild{ | |
199 Master: "Fake Master", | |
200 } | |
201 So(func() { ds.Put(c, build) }, ShouldPanicLike, "No Mas
ter or Builder found") | |
202 }) | |
203 | |
204 ts := 555 | |
205 b := &buildbotBuild{ | |
206 Master: "Fake Master", | |
207 Buildername: "Fake buildername", | |
208 Number: 1234, | |
209 Currentstep: "this is a string", | |
210 Times: buildbotTimesPending(123.0), | |
211 TimeStamp: &ts, | |
212 } | |
213 | |
214 Convey("Basic master + build pusbsub subscription", func() { | |
215 h := httptest.NewRecorder() | |
216 slaves := map[string]*buildbotSlave{} | |
217 ft := 1234.0 | |
218 slaves["testslave"] = &buildbotSlave{ | |
219 Name: "testslave", | |
220 Connected: true, | |
221 Runningbuilds: []*buildbotBuild{ | |
222 { | |
223 Master: "Fake Master", | |
224 Buildername: "Fake buildername", | |
225 Number: 2222, | |
226 Times: []*float64{&ft, nil
}, | |
227 }, | |
228 }, | |
229 } | |
230 ms := buildbotMaster{ | |
231 Name: "Fake Master", | |
232 Project: buildbotProject{Title: "some title"}, | |
233 Slaves: slaves, | |
234 Builders: map[string]*buildbotBuilder{}, | |
235 } | |
236 | |
237 ms.Builders["Fake buildername"] = &buildbotBuilder{ | |
238 CurrentBuilds: []int{1234}, | |
239 } | |
240 r := &http.Request{ | |
241 Body: newCombinedPsBody([]*buildbotBuild{b}, &ms
, false), | |
242 } | |
243 p := httprouter.Params{} | |
244 PubSubHandler(&router.Context{ | |
245 Context: c, | |
246 Writer: h, | |
247 Request: r, | |
248 Params: p, | |
249 }) | |
250 So(h.Code, ShouldEqual, 200) | |
251 Convey("And stores correctly", func() { | |
252 loadB := &buildbotBuild{ | |
253 Master: "Fake Master", | |
254 Buildername: "Fake buildername", | |
255 Number: 1234, | |
256 } | |
257 err := ds.Get(c, loadB) | |
258 So(err, ShouldBeNil) | |
259 So(loadB.Master, ShouldEqual, "Fake Master") | |
260 So(loadB.Currentstep.(string), ShouldEqual, "thi
s is a string") | |
261 m, _, t, err := getMasterJSON(c, "Fake Master") | |
262 So(err, ShouldBeNil) | |
263 So(t.Unix(), ShouldEqual, 981173106) | |
264 So(m.Name, ShouldEqual, "Fake Master") | |
265 So(m.Project.Title, ShouldEqual, "some title") | |
266 So(m.Slaves["testslave"].Name, ShouldEqual, "tes
tslave") | |
267 So(len(m.Slaves["testslave"].Runningbuilds), Sho
uldEqual, 0) | |
268 So(len(m.Slaves["testslave"].RunningbuildsMap),
ShouldEqual, 1) | |
269 So(m.Slaves["testslave"].RunningbuildsMap["Fake
buildername"][0], | |
270 ShouldEqual, 2222) | |
271 }) | |
272 | |
273 Convey("And a new master overwrites", func() { | |
274 c, _ = testclock.UseTime(c, fakeTime.Add(time.Du
ration(1*time.Second))) | |
275 ms.Project.Title = "some other title" | |
276 h = httptest.NewRecorder() | |
277 r := &http.Request{ | |
278 Body: newCombinedPsBody([]*buildbotBuild
{b}, &ms, false)} | |
279 p = httprouter.Params{} | |
280 PubSubHandler(&router.Context{ | |
281 Context: c, | |
282 Writer: h, | |
283 Request: r, | |
284 Params: p, | |
285 }) | |
286 So(h.Code, ShouldEqual, 200) | |
287 m, _, t, err := getMasterJSON(c, "Fake Master") | |
288 So(err, ShouldBeNil) | |
289 So(m.Project.Title, ShouldEqual, "some other tit
le") | |
290 So(t.Unix(), ShouldEqual, 981173107) | |
291 So(m.Name, ShouldEqual, "Fake Master") | |
292 }) | |
293 Convey("And a new build overwrites", func() { | |
294 b.Times = buildbotTimesFinished(123.0, 124.0) | |
295 h = httptest.NewRecorder() | |
296 r = &http.Request{ | |
297 Body: newCombinedPsBody([]*buildbotBuild
{b}, &ms, false), | |
298 } | |
299 p = httprouter.Params{} | |
300 PubSubHandler(&router.Context{ | |
301 Context: c, | |
302 Writer: h, | |
303 Request: r, | |
304 Params: p, | |
305 }) | |
306 So(h.Code, ShouldEqual, 200) | |
307 loadB := &buildbotBuild{ | |
308 Master: "Fake Master", | |
309 Buildername: "Fake buildername", | |
310 Number: 1234, | |
311 } | |
312 err := ds.Get(c, loadB) | |
313 So(err, ShouldBeNil) | |
314 So(*loadB.Times[0], ShouldEqual, 123.0) | |
315 So(*loadB.Times[1], ShouldEqual, 124.0) | |
316 Convey("And another pending build is rejected",
func() { | |
317 b.Times = buildbotTimesPending(123.0) | |
318 h = httptest.NewRecorder() | |
319 r = &http.Request{ | |
320 Body: newCombinedPsBody([]*build
botBuild{b}, &ms, false), | |
321 } | |
322 p = httprouter.Params{} | |
323 PubSubHandler(&router.Context{ | |
324 Context: c, | |
325 Writer: h, | |
326 Request: r, | |
327 Params: p, | |
328 }) | |
329 So(h.Code, ShouldEqual, 200) | |
330 loadB := &buildbotBuild{ | |
331 Master: "Fake Master", | |
332 Buildername: "Fake buildername", | |
333 Number: 1234, | |
334 } | |
335 err := ds.Get(c, loadB) | |
336 So(err, ShouldBeNil) | |
337 So(*loadB.Times[0], ShouldEqual, 123.0) | |
338 So(*loadB.Times[1], ShouldEqual, 124.0) | |
339 }) | |
340 }) | |
341 Convey("Don't Expire non-existant current build under 20
min", func() { | |
342 b.Number = 1235 | |
343 ts := int(fakeTime.Unix()) - 1000 | |
344 b.TimeStamp = &ts | |
345 h = httptest.NewRecorder() | |
346 r = &http.Request{ | |
347 Body: newCombinedPsBody([]*buildbotBuild
{b}, &ms, false), | |
348 } | |
349 p = httprouter.Params{} | |
350 ds.GetTestable(c).Consistent(true) | |
351 PubSubHandler(&router.Context{ | |
352 Context: c, | |
353 Writer: h, | |
354 Request: r, | |
355 Params: p, | |
356 }) | |
357 So(h.Code, ShouldEqual, 200) | |
358 loadB := &buildbotBuild{ | |
359 Master: "Fake Master", | |
360 Buildername: "Fake buildername", | |
361 Number: 1235, | |
362 } | |
363 err := ds.Get(c, loadB) | |
364 So(err, ShouldBeNil) | |
365 So(loadB.Finished, ShouldEqual, false) | |
366 So(*loadB.Times[0], ShouldEqual, 123.0) | |
367 So(loadB.Times[1], ShouldBeNil) | |
368 }) | |
369 Convey("Expire non-existant current build", func() { | |
370 b.Number = 1235 | |
371 ts := int(fakeTime.Unix()) - 1201 | |
372 b.TimeStamp = &ts | |
373 h = httptest.NewRecorder() | |
374 r = &http.Request{ | |
375 Body: newCombinedPsBody([]*buildbotBuild
{b}, &ms, false), | |
376 } | |
377 p = httprouter.Params{} | |
378 ds.GetTestable(c).Consistent(true) | |
379 PubSubHandler(&router.Context{ | |
380 Context: c, | |
381 Writer: h, | |
382 Request: r, | |
383 Params: p, | |
384 }) | |
385 So(h.Code, ShouldEqual, 200) | |
386 loadB := &buildbotBuild{ | |
387 Master: "Fake Master", | |
388 Buildername: "Fake buildername", | |
389 Number: 1235, | |
390 } | |
391 err := ds.Get(c, loadB) | |
392 So(err, ShouldBeNil) | |
393 So(loadB.Finished, ShouldEqual, true) | |
394 So(*loadB.Times[0], ShouldEqual, 123.0) | |
395 So(loadB.Times[1], ShouldNotEqual, nil) | |
396 So(*loadB.Times[1], ShouldEqual, ts) | |
397 So(*loadB.Results, ShouldEqual, 4) | |
398 }) | |
399 Convey("Large pubsub message", func() { | |
400 // This has to be a random string, so that after
gzip compresses it | |
401 // it remains larger than 950KB | |
402 b.Text = append(b.Text, RandStringRunes(1500000)
) | |
403 h := httptest.NewRecorder() | |
404 r := &http.Request{ | |
405 Body: newCombinedPsBody([]*buildbotBuild
{b}, &ms, false), | |
406 } | |
407 p := httprouter.Params{} | |
408 PubSubHandler(&router.Context{ | |
409 Context: c, | |
410 Writer: h, | |
411 Request: r, | |
412 Params: p, | |
413 }) | |
414 So(h.Code, ShouldEqual, 200) | |
415 }) | |
416 | |
417 }) | |
418 | |
419 Convey("Empty pubsub message", func() { | |
420 h := httptest.NewRecorder() | |
421 r := &http.Request{Body: ioutil.NopCloser(bytes.NewReade
r([]byte{}))} | |
422 p := httprouter.Params{} | |
423 PubSubHandler(&router.Context{ | |
424 Context: c, | |
425 Writer: h, | |
426 Request: r, | |
427 Params: p, | |
428 }) | |
429 So(h.Code, ShouldEqual, 200) | |
430 }) | |
431 | |
432 Convey("Internal master + build pusbsub subscription", func() { | |
433 h := httptest.NewRecorder() | |
434 slaves := map[string]*buildbotSlave{} | |
435 ft := 1234.0 | |
436 slaves["testslave"] = &buildbotSlave{ | |
437 Name: "testslave", | |
438 Connected: true, | |
439 Runningbuilds: []*buildbotBuild{ | |
440 { | |
441 Master: "Fake Master", | |
442 Buildername: "Fake buildername", | |
443 Number: 2222, | |
444 Times: []*float64{&ft, nil
}, | |
445 }, | |
446 }, | |
447 } | |
448 ms := buildbotMaster{ | |
449 Name: "Fake Master", | |
450 Project: buildbotProject{Title: "some title"}, | |
451 Slaves: slaves, | |
452 } | |
453 r := &http.Request{ | |
454 Body: newCombinedPsBody([]*buildbotBuild{b}, &ms
, true), | |
455 } | |
456 p := httprouter.Params{} | |
457 PubSubHandler(&router.Context{ | |
458 Context: c, | |
459 Writer: h, | |
460 Request: r, | |
461 Params: p, | |
462 }) | |
463 So(h.Code, ShouldEqual, 200) | |
464 Convey("And stores correctly", func() { | |
465 err := common.UpdateProjectConfigs(c) | |
466 So(err, ShouldBeNil) | |
467 c = auth.WithState(c, &authtest.FakeState{ | |
468 Identity: "user:alicebob@google.co
m", | |
469 IdentityGroups: []string{"googlers", "al
l"}, | |
470 }) | |
471 loadB := &buildbotBuild{ | |
472 Master: "Fake Master", | |
473 Buildername: "Fake buildername", | |
474 Number: 1234, | |
475 } | |
476 err = ds.Get(c, loadB) | |
477 So(err, ShouldBeNil) | |
478 So(loadB.Master, ShouldEqual, "Fake Master") | |
479 So(loadB.Internal, ShouldEqual, true) | |
480 So(loadB.Currentstep.(string), ShouldEqual, "thi
s is a string") | |
481 m, _, t, err := getMasterJSON(c, "Fake Master") | |
482 So(err, ShouldBeNil) | |
483 So(t.Unix(), ShouldEqual, 981173106) | |
484 So(m.Name, ShouldEqual, "Fake Master") | |
485 So(m.Project.Title, ShouldEqual, "some title") | |
486 So(m.Slaves["testslave"].Name, ShouldEqual, "tes
tslave") | |
487 So(len(m.Slaves["testslave"].Runningbuilds), Sho
uldEqual, 0) | |
488 So(len(m.Slaves["testslave"].RunningbuildsMap),
ShouldEqual, 1) | |
489 So(m.Slaves["testslave"].RunningbuildsMap["Fake
buildername"][0], | |
490 ShouldEqual, 2222) | |
491 }) | |
492 }) | |
493 }) | |
494 } | |
OLD | NEW |