Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(219)

Side by Side Diff: third_party/buildbot_7_12/buildbot/test/test_web.py

Issue 12207158: Bye bye buildbot 0.7.12. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # -*- test-case-name: buildbot.test.test_web -*-
2 # -*- coding: utf-8 -*-
3
4 import os, time, shutil
5 import warnings
6 from HTMLParser import HTMLParser
7 from twisted.python import components
8
9 from twisted.trial import unittest
10 from buildbot.test.runutils import RunMixin
11
12 from twisted.internet import reactor, defer, protocol
13 from twisted.internet.interfaces import IReactorUNIX
14 from twisted.web import client
15
16 from buildbot import master, interfaces, sourcestamp
17 from buildbot.status import html, builder
18 from buildbot.status.web import waterfall, xmlrpc
19 from buildbot.changes.changes import Change
20 from buildbot.process import base
21 from buildbot.process.buildstep import BuildStep
22 from buildbot.test.runutils import setupBuildStepStatus
23
24 class ConfiguredMaster(master.BuildMaster):
25 """This BuildMaster variant has a static config file, provided as a
26 string when it is created."""
27
28 def __init__(self, basedir, config):
29 self.config = config
30 master.BuildMaster.__init__(self, basedir)
31
32 def loadTheConfigFile(self):
33 self.loadConfig(self.config)
34
35 components.registerAdapter(master.Control, ConfiguredMaster,
36 interfaces.IControl)
37
38
39 base_config = """
40 from buildbot.changes.pb import PBChangeSource
41 from buildbot.status import html
42 from buildbot.buildslave import BuildSlave
43 from buildbot.scheduler import Scheduler
44 from buildbot.process.factory import BuildFactory
45 from buildbot.config import BuilderConfig
46
47 BuildmasterConfig = c = {
48 'change_source': PBChangeSource(),
49 'slaves': [BuildSlave('bot1name', 'bot1passwd')],
50 'schedulers': [Scheduler('name', None, 60, ['builder1'])],
51 'slavePortnum': 0,
52 }
53 c['builders'] = [
54 BuilderConfig(name='builder1', slavename='bot1name', factory=BuildFactory()) ,
55 ]
56 """
57
58
59
60 class DistribUNIX:
61 def __init__(self, unixpath):
62 from twisted.web import server, resource, distrib
63 root = resource.Resource()
64 self.r = r = distrib.ResourceSubscription("unix", unixpath)
65 root.putChild('remote', r)
66 self.p = p = reactor.listenTCP(0, server.Site(root))
67 self.portnum = p.getHost().port
68 def shutdown(self):
69 d = defer.maybeDeferred(self.p.stopListening)
70 return d
71
72 class DistribTCP:
73 def __init__(self, port):
74 from twisted.web import server, resource, distrib
75 root = resource.Resource()
76 self.r = r = distrib.ResourceSubscription("localhost", port)
77 root.putChild('remote', r)
78 self.p = p = reactor.listenTCP(0, server.Site(root))
79 self.portnum = p.getHost().port
80 def shutdown(self):
81 d = defer.maybeDeferred(self.p.stopListening)
82 d.addCallback(self._shutdown_1)
83 return d
84 def _shutdown_1(self, res):
85 return self.r.publisher.broker.transport.loseConnection()
86
87 class SlowReader(protocol.Protocol):
88 didPause = False
89 count = 0
90 data = ""
91 def __init__(self, req):
92 self.req = req
93 self.d = defer.Deferred()
94 def connectionMade(self):
95 self.transport.write(self.req)
96 def dataReceived(self, data):
97 self.data += data
98 self.count += len(data)
99 if not self.didPause and self.count > 10*1000:
100 self.didPause = True
101 self.transport.pauseProducing()
102 reactor.callLater(2, self.resume)
103 def resume(self):
104 self.transport.resumeProducing()
105 def connectionLost(self, why):
106 self.d.callback(None)
107
108 class CFactory(protocol.ClientFactory):
109 def __init__(self, p):
110 self.p = p
111 def buildProtocol(self, addr):
112 self.p.factory = self
113 return self.p
114
115 def stopHTTPLog():
116 # grr.
117 from twisted.web import http
118 http._logDateTimeStop()
119
120 class BaseWeb:
121 master = None
122
123 def failUnlessIn(self, substr, string, note=None):
124 self.failUnless(string.find(substr) != -1, note)
125
126 def tearDown(self):
127 stopHTTPLog()
128 if self.master:
129 d = self.master.stopService()
130 return d
131
132 def find_webstatus(self, master):
133 for child in list(master):
134 if isinstance(child, html.WebStatus):
135 return child
136
137 def find_waterfall(self, master):
138 for child in list(master):
139 if isinstance(child, html.Waterfall):
140 return child
141
142 class Ports(BaseWeb, unittest.TestCase):
143
144 def test_webPortnum(self):
145 # run a regular web server on a TCP socket
146 config = base_config + "c['status'] = [html.WebStatus(http_port=0)]\n"
147 os.mkdir("test_web1")
148 self.master = m = ConfiguredMaster("test_web1", config)
149 m.startService()
150 # hack to find out what randomly-assigned port it is listening on
151 port = self.find_webstatus(m).getPortnum()
152
153 d = client.getPage("http://localhost:%d/waterfall" % port)
154 def _check(page):
155 #print page
156 self.failUnless(page)
157 d.addCallback(_check)
158 return d
159 test_webPortnum.timeout = 10
160
161 def test_webPathname(self):
162 # running a t.web.distrib server over a UNIX socket
163 if not IReactorUNIX.providedBy(reactor):
164 raise unittest.SkipTest("UNIX sockets not supported here")
165 config = (base_config +
166 "c['status'] = [html.WebStatus(distrib_port='.web-pb')]\n")
167 os.mkdir("test_web2")
168 self.master = m = ConfiguredMaster("test_web2", config)
169 m.startService()
170
171 p = DistribUNIX("test_web2/.web-pb")
172
173 d = client.getPage("http://localhost:%d/remote/waterfall" % p.portnum)
174 def _check(page):
175 self.failUnless(page)
176 d.addCallback(_check)
177 def _done(res):
178 d1 = p.shutdown()
179 d1.addCallback(lambda x: res)
180 return d1
181 d.addBoth(_done)
182 return d
183 test_webPathname.timeout = 10
184
185
186 def test_webPathname_port(self):
187 # running a t.web.distrib server over TCP
188 config = (base_config +
189 "c['status'] = [html.WebStatus(distrib_port=0)]\n")
190 os.mkdir("test_web3")
191 self.master = m = ConfiguredMaster("test_web3", config)
192 m.startService()
193 dport = self.find_webstatus(m).getPortnum()
194
195 p = DistribTCP(dport)
196
197 d = client.getPage("http://localhost:%d/remote/waterfall" % p.portnum)
198 def _check(page):
199 self.failUnlessIn("BuildBot", page)
200 d.addCallback(_check)
201 def _done(res):
202 d1 = p.shutdown()
203 d1.addCallback(lambda x: res)
204 return d1
205 d.addBoth(_done)
206 return d
207 test_webPathname_port.timeout = 10
208
209
210 class Waterfall(BaseWeb, unittest.TestCase):
211 def setUp(self):
212 warnings.filterwarnings("ignore", category=DeprecationWarning)
213
214 def tearDown(self):
215 BaseWeb.tearDown(self)
216 warnings.resetwarnings()
217
218 def test_waterfall(self):
219 os.mkdir("test_web4")
220 os.mkdir("my-maildir"); os.mkdir("my-maildir/new")
221 self.robots_txt = os.path.abspath(os.path.join("test_web4",
222 "robots.txt"))
223 self.robots_txt_contents = "User-agent: *\nDisallow: /\n"
224 f = open(self.robots_txt, "w")
225 f.write(self.robots_txt_contents)
226 f.close()
227 # this is the right way to configure the Waterfall status
228 config1 = base_config + """
229 from buildbot.changes import mail
230 c['change_source'] = mail.SyncmailMaildirSource('my-maildir')
231 c['status'] = [html.Waterfall(http_port=0, robots_txt=%s)]
232 """ % repr(self.robots_txt)
233
234 self.master = m = ConfiguredMaster("test_web4", config1)
235 m.startService()
236 port = self.find_waterfall(m).getPortnum()
237 self.port = port
238 # insert an event
239 m.change_svc.addChange(Change("user", ["foo.c"], "comments"))
240
241 d = client.getPage("http://localhost:%d/" % port)
242
243 def _check1(page):
244 self.failUnless(page)
245 self.failUnlessIn("current activity", page)
246 self.failUnlessIn("<html", page)
247 TZ = time.tzname[time.localtime()[-1]]
248 self.failUnlessIn("time (%s)" % TZ, page)
249
250 # phase=0 is really for debugging the waterfall layout
251 return client.getPage("http://localhost:%d/?phase=0" % self.port)
252 d.addCallback(_check1)
253
254 def _check2(page):
255 self.failUnless(page)
256 self.failUnlessIn("<html", page)
257
258 return client.getPage("http://localhost:%d/changes" % self.port)
259 d.addCallback(_check2)
260
261 def _check3(changes):
262 self.failUnlessIn("<li>Syncmail mailing list in maildir " +
263 "my-maildir</li>", changes)
264
265 return client.getPage("http://localhost:%d/robots.txt" % self.port)
266 d.addCallback(_check3)
267
268 def _check4(robotstxt):
269 self.failUnless(robotstxt == self.robots_txt_contents)
270 d.addCallback(_check4)
271
272 return d
273
274 test_waterfall.timeout = 10
275
276 class WaterfallSteps(unittest.TestCase):
277
278 # failUnlessSubstring copied from twisted-2.1.0, because this helps us
279 # maintain compatibility with python2.2.
280 def failUnlessSubstring(self, substring, astring, msg=None):
281 """a python2.2 friendly test to assert that substring is found in
282 astring parameters follow the semantics of failUnlessIn
283 """
284 if astring.find(substring) == -1:
285 raise self.failureException(msg or "%r not found in %r"
286 % (substring, astring))
287 return substring
288 assertSubstring = failUnlessSubstring
289
290 def test_urls(self):
291 s = setupBuildStepStatus("test_web.test_urls")
292 s.addURL("coverage", "http://coverage.example.org/target")
293 s.addURL("icon", "http://coverage.example.org/icon.png")
294 class FakeRequest:
295 prepath = []
296 postpath = []
297 def childLink(self, name):
298 return name
299 req = FakeRequest()
300 box = waterfall.IBox(s).getBox(req)
301 td = box.td()
302 e1 = '[<a href="http://coverage.example.org/target" class="BuildStep ext ernal">coverage</a>]'
303 self.failUnlessSubstring(e1, td)
304 e2 = '[<a href="http://coverage.example.org/icon.png" class="BuildStep e xternal">icon</a>]'
305 self.failUnlessSubstring(e2, td)
306
307
308
309 geturl_config = """
310 from buildbot.status import html
311 from buildbot.changes import mail
312 from buildbot.process import factory
313 from buildbot.steps import dummy
314 from buildbot.scheduler import Scheduler
315 from buildbot.changes.base import ChangeSource
316 from buildbot.buildslave import BuildSlave
317 from buildbot.config import BuilderConfig
318 s = factory.s
319
320 class DiscardScheduler(Scheduler):
321 def addChange(self, change):
322 pass
323 class DummyChangeSource(ChangeSource):
324 pass
325
326 BuildmasterConfig = c = {}
327 c['slaves'] = [BuildSlave('bot1', 'sekrit'), BuildSlave('bot2', 'sekrit')]
328 c['change_source'] = DummyChangeSource()
329 c['schedulers'] = [DiscardScheduler('discard', None, 60, ['b1'])]
330 c['slavePortnum'] = 0
331 c['status'] = [html.Waterfall(http_port=0)]
332
333 f = factory.BuildFactory([s(dummy.RemoteDummy, timeout=1)])
334
335 c['builders'] = [
336 BuilderConfig(name='b1', slavenames=['bot1', 'bot2'], factory=f),
337 ]
338 c['buildbotURL'] = 'http://dummy.example.org:8010/'
339
340 """
341
342 class GetURL(RunMixin, unittest.TestCase):
343 def setUp(self):
344 warnings.filterwarnings("ignore", category=DeprecationWarning)
345 RunMixin.setUp(self)
346 self.master.loadConfig(geturl_config)
347 self.master.startService()
348 d = self.connectSlave(["b1"])
349 return d
350
351 def tearDown(self):
352 stopHTTPLog()
353 warnings.resetwarnings()
354 return RunMixin.tearDown(self)
355
356 def doBuild(self, buildername):
357 br = base.BuildRequest("forced", sourcestamp.SourceStamp(), 'test_builde r')
358 d = br.waitUntilFinished()
359 self.control.getBuilder(buildername).requestBuild(br)
360 return d
361
362 def assertNoURL(self, target):
363 self.failUnlessIdentical(self.status.getURLForThing(target), None)
364
365 def assertURLEqual(self, target, expected):
366 got = self.status.getURLForThing(target)
367 full_expected = "http://dummy.example.org:8010/" + expected
368 self.failUnlessEqual(got, full_expected)
369
370 def testMissingBase(self):
371 noweb_config1 = geturl_config + "del c['buildbotURL']\n"
372 d = self.master.loadConfig(noweb_config1)
373 d.addCallback(self._testMissingBase_1)
374 return d
375 def _testMissingBase_1(self, res):
376 s = self.status
377 self.assertNoURL(s)
378 builder_s = s.getBuilder("b1")
379 self.assertNoURL(builder_s)
380
381 def testBase(self):
382 s = self.status
383 self.assertURLEqual(s, "")
384 builder_s = s.getBuilder("b1")
385 self.assertURLEqual(builder_s, "builders/b1")
386
387 def testChange(self):
388 s = self.status
389 c = Change("user", ["foo.c"], "comments")
390 self.master.change_svc.addChange(c)
391 # TODO: something more like s.getChanges(), requires IChange and
392 # an accessor in IStatus. The HTML page exists already, though
393 self.assertURLEqual(c, "changes/1")
394
395 def testBuild(self):
396 # first we do some stuff so we'll have things to look at.
397 s = self.status
398 d = self.doBuild("b1")
399 # maybe check IBuildSetStatus here?
400 d.addCallback(self._testBuild_1)
401 return d
402
403 def _testBuild_1(self, res):
404 s = self.status
405 builder_s = s.getBuilder("b1")
406 build_s = builder_s.getLastFinishedBuild()
407 self.assertURLEqual(build_s, "builders/b1/builds/0")
408 # no page for builder.getEvent(-1)
409 step = build_s.getSteps()[0]
410 self.assertURLEqual(step, "builders/b1/builds/0/steps/remote%20dummy")
411 # maybe page for build.getTestResults?
412 self.assertURLEqual(step.getLogs()[0],
413 "builders/b1/builds/0/steps/remote%20dummy/logs/stdi o")
414
415
416
417 class Logfile(BaseWeb, RunMixin, unittest.TestCase):
418 def setUp(self):
419 config = """
420 from buildbot.status import html
421 from buildbot.process.factory import BasicBuildFactory
422 from buildbot.buildslave import BuildSlave
423 from buildbot.config import BuilderConfig
424
425 f1 = BasicBuildFactory('cvsroot', 'cvsmodule')
426 BuildmasterConfig = c = {
427 'slaves': [BuildSlave('bot1', 'passwd1')],
428 'schedulers': [],
429 'slavePortnum': 0,
430 'status': [html.WebStatus(http_port=0)],
431 }
432 c['builders'] = [
433 BuilderConfig(name='builder1', slavename='bot1', factory=f1),
434 ]
435 """
436 if os.path.exists("test_logfile"):
437 shutil.rmtree("test_logfile")
438 os.mkdir("test_logfile")
439 self.master = m = ConfiguredMaster("test_logfile", config)
440 m.startService()
441 # hack to find out what randomly-assigned port it is listening on
442 port = self.find_webstatus(m).getPortnum()
443 self.port = port
444 # insert an event
445
446 req = base.BuildRequest("reason", sourcestamp.SourceStamp(), 'test_build er')
447 build1 = base.Build([req])
448 bs = m.status.getBuilder("builder1").newBuild()
449 bs.setReason("reason")
450 bs.buildStarted(build1)
451
452 step1 = BuildStep(name="setup")
453 step1.setBuild(build1)
454 bss = bs.addStepWithName("setup")
455 step1.setStepStatus(bss)
456 bss.stepStarted()
457
458 log1 = step1.addLog("output")
459 log1.addStdout(u"sÒme stdout\n")
460 log1.finish()
461
462 log2 = step1.addHTMLLog("error", "<html>ouch</html>")
463
464 log3 = step1.addLog("big")
465 log3.addStdout("big log\n")
466 for i in range(1000):
467 log3.addStdout("a" * 500)
468 log3.addStderr("b" * 500)
469 log3.finish()
470
471 log4 = step1.addCompleteLog("bigcomplete",
472 "big2 log\n" + "a" * 1*1000*1000)
473
474 log5 = step1.addLog("mixed")
475 log5.addHeader("header content")
476 log5.addStdout("this is stdout content")
477 log5.addStderr("errors go here")
478 log5.addEntry(5, "non-standard content on channel 5")
479 log5.addStderr(" and some trailing stderr")
480
481 d = defer.maybeDeferred(step1.step_status.stepFinished,
482 builder.SUCCESS)
483 bs.buildFinished()
484 return d
485
486 def getLogPath(self, stepname, logname):
487 return ("/builders/builder1/builds/0/steps/%s/logs/%s" %
488 (stepname, logname))
489
490 def getLogURL(self, stepname, logname):
491 return ("http://localhost:%d" % self.port
492 + self.getLogPath(stepname, logname))
493
494 def test_logfile1(self):
495 d = client.getPage("http://localhost:%d/" % self.port)
496 def _check(page):
497 self.failUnless(page)
498 d.addCallback(_check)
499 return d
500
501 def test_logfile2(self):
502 logurl = self.getLogURL("setup", "output")
503 d = client.getPage(logurl)
504 def _check(logbody):
505 self.failUnless(logbody)
506 d.addCallback(_check)
507 return d
508
509 def test_logfile3(self):
510 logurl = self.getLogURL("setup", "output")
511 d = client.getPage(logurl + "/text")
512 def _check(logtext):
513 # verify utf-8 encoding.
514 self.failUnlessEqual(logtext, "sÒme stdout\n")
515 d.addCallback(_check)
516 return d
517
518 def test_logfile4(self):
519 logurl = self.getLogURL("setup", "error")
520 d = client.getPage(logurl)
521 def _check(logbody):
522 self.failUnlessEqual(logbody, "<html>ouch</html>")
523 d.addCallback(_check)
524 return d
525
526 def test_logfile5(self):
527 # this is log3, which is about 1MB in size, made up of alternating
528 # stdout/stderr chunks. buildbot-0.6.6, when run against
529 # twisted-1.3.0, fails to resume sending chunks after the client
530 # stalls for a few seconds, because of a recursive doWrite() call
531 # that was fixed in twisted-2.0.0
532 p = SlowReader("GET %s HTTP/1.0\r\n\r\n"
533 % self.getLogPath("setup", "big"))
534 cf = CFactory(p)
535 c = reactor.connectTCP("localhost", self.port, cf)
536 d = p.d
537 def _check(res):
538 self.failUnlessIn("big log", p.data)
539 self.failUnlessIn("a"*100, p.data)
540 self.failUnless(p.count > 1*1000*1000)
541 d.addCallback(_check)
542 return d
543
544 def test_logfile6(self):
545 # this is log4, which is about 1MB in size, one big chunk.
546 # buildbot-0.6.6 dies as the NetstringReceiver barfs on the
547 # saved logfile, because it was using one big chunk and exceeding
548 # NetstringReceiver.MAX_LENGTH
549 p = SlowReader("GET %s HTTP/1.0\r\n\r\n"
550 % self.getLogPath("setup", "bigcomplete"))
551 cf = CFactory(p)
552 c = reactor.connectTCP("localhost", self.port, cf)
553 d = p.d
554 def _check(res):
555 self.failUnlessIn("big2 log", p.data)
556 self.failUnlessIn("a"*100, p.data)
557 self.failUnless(p.count > 1*1000*1000)
558 d.addCallback(_check)
559 return d
560
561 def test_logfile7(self):
562 # this is log5, with mixed content on the tree standard channels
563 # as well as on channel 5
564
565 class SpanParser(HTMLParser):
566 '''Parser subclass to gather all the log spans from the log page'''
567 def __init__(self, test):
568 self.spans = []
569 self.test = test
570 self.inSpan = False
571 HTMLParser.__init__(self)
572
573 def handle_starttag(self, tag, attrs):
574 if tag == 'span':
575 self.inSpan = True
576 cls = attrs[0]
577 self.test.failUnless(cls[0] == 'class')
578 self.spans.append([cls[1],''])
579
580 def handle_data(self, data):
581 if self.inSpan:
582 self.spans[-1][1] += data
583
584 def handle_endtag(self, tag):
585 if tag == 'span':
586 self.inSpan = False
587
588 logurl = self.getLogURL("setup", "mixed")
589 d = client.getPage(logurl, timeout=2)
590 def _check(logbody):
591 try:
592 p = SpanParser(self)
593 p.feed(logbody)
594 p.close
595 except Exception, e:
596 print e
597 self.failUnlessEqual(len(p.spans), 4)
598 self.failUnlessEqual(p.spans[0][0], 'header')
599 self.failUnlessEqual(p.spans[0][1], 'header content')
600 self.failUnlessEqual(p.spans[1][0], 'stdout')
601 self.failUnlessEqual(p.spans[1][1], 'this is stdout content')
602 self.failUnlessEqual(p.spans[2][0], 'stderr')
603 self.failUnlessEqual(p.spans[2][1], 'errors go here')
604 self.failUnlessEqual(p.spans[3][0], 'stderr')
605 self.failUnlessEqual(p.spans[3][1], ' and some trailing stderr')
606 def _fail(err):
607 pass
608 d.addCallback(_check)
609 d.addErrback(_fail)
610 return d
611
612 class XMLRPC(unittest.TestCase):
613 def test_init(self):
614 server = xmlrpc.XMLRPCServer()
615 self.assert_(server)
616
617 def test_render(self):
618 self.assertRaises(NameError,
619 lambda:
620 xmlrpc.XMLRPCServer().render(Request()))
OLDNEW
« no previous file with comments | « third_party/buildbot_7_12/buildbot/test/test_vc.py ('k') | third_party/buildbot_7_12/buildbot/test/test_web_status_json.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698