1# -*- python -*- 2# ex: set syntax=python: 3 4c = BuildmasterConfig = {} 5 6from buildbot.buildslave import BuildSlave 7from buildbot.changes.pb import PBChangeSource 8from buildbot.scheduler import AnyBranchScheduler, Triggerable 9from buildbot.schedulers.filter import ChangeFilter 10from buildbot.status import html 11from buildbot.status.web.authz import Authz 12from buildbot.process import buildstep, factory, properties 13from buildbot.steps import master, shell, source, transfer, trigger 14from buildbot.status.builder import SUCCESS, FAILURE, WARNINGS, SKIPPED 15 16from twisted.internet import defer 17 18import os 19import re 20import simplejson 21import urllib 22 23from webkitpy.common.config import build as wkbuild 24from webkitpy.common.net.buildbot import BuildBot as wkbuildbot 25 26WithProperties = properties.WithProperties 27 28class ConfigureBuild(buildstep.BuildStep): 29 name = "configure build" 30 description = ["configuring build"] 31 descriptionDone = ["configured build"] 32 def __init__(self, platform, configuration, architecture, buildOnly, *args, **kwargs): 33 buildstep.BuildStep.__init__(self, *args, **kwargs) 34 self.platform = platform.split('-', 1)[0] 35 self.fullPlatform = platform 36 self.configuration = configuration 37 self.architecture = architecture 38 self.buildOnly = buildOnly 39 self.addFactoryArguments(platform=platform, configuration=configuration, architecture=architecture, buildOnly=buildOnly) 40 41 def start(self): 42 self.setProperty("platform", self.platform) 43 self.setProperty("fullPlatform", self.fullPlatform) 44 self.setProperty("configuration", self.configuration) 45 self.setProperty("architecture", self.architecture) 46 self.setProperty("buildOnly", self.buildOnly) 47 self.finished(SUCCESS) 48 return defer.succeed(None) 49 50 51class CheckOutSource(source.SVN): 52 baseURL = "http://svn.webkit.org/repository/webkit/" 53 mode = "update" 54 def __init__(self, *args, **kwargs): 55 source.SVN.__init__(self, baseURL=self.baseURL, defaultBranch="trunk", mode=self.mode, *args, **kwargs) 56 57 58class InstallWin32Dependencies(shell.Compile): 59 description = ["installing dependencies"] 60 descriptionDone = ["installed dependencies"] 61 command = ["perl", "./Tools/Scripts/update-webkit-auxiliary-libs"] 62 63class KillOldProcesses(shell.Compile): 64 name = "kill old processes" 65 description = ["killing old processes"] 66 descriptionDone = ["killed old processes"] 67 command = ["python", "./Tools/BuildSlaveSupport/win/kill-old-processes"] 68 69class InstallChromiumDependencies(shell.ShellCommand): 70 name = "gclient" 71 description = ["updating chromium dependencies"] 72 descriptionDone = ["updated chromium dependencies"] 73 command = ["perl", "./Tools/Scripts/update-webkit-chromium", "--force"] 74 haltOnFailure = True 75 76class CleanupChromiumCrashLogs(shell.ShellCommand): 77 name = "cleanup crash logs" 78 description = ["removing crash logs"] 79 descriptionDone = ["removed crash logs"] 80 command = ["python", "./Tools/BuildSlaveSupport/chromium/remove-crash-logs"] 81 haltOnFailure = False 82 83 84def appendCustomBuildFlags(step, platform): 85 if platform in ('chromium', 'efl', 'gtk', 'qt', 'wincairo', 'wince', 'wx'): 86 step.setCommand(step.command + ['--' + platform]) 87 88 89class CompileWebKit(shell.Compile): 90 command = ["perl", "./Tools/Scripts/build-webkit", WithProperties("--%(configuration)s")] 91 env = {'MFLAGS':''} 92 name = "compile-webkit" 93 description = ["compiling"] 94 descriptionDone = ["compiled"] 95 warningPattern = ".*arning: .*" 96 97 def start(self): 98 platform = self.getProperty('platform') 99 buildOnly = self.getProperty('buildOnly') 100 if platform == 'mac' and buildOnly: 101 self.setCommand(self.command + ['DEBUG_INFORMATION_FORMAT=dwarf-with-dsym']) 102 103 appendCustomBuildFlags(self, platform) 104 return shell.Compile.start(self) 105 106 107class ArchiveBuiltProduct(shell.ShellCommand): 108 command = ["python", "./Tools/BuildSlaveSupport/built-product-archive", 109 WithProperties("--platform=%(platform)s"), WithProperties("--%(configuration)s"), "archive"] 110 name = "archive-built-product" 111 description = ["archiving built product"] 112 descriptionDone = ["archived built product"] 113 haltOnFailure = True 114 115 116class ExtractBuiltProduct(shell.ShellCommand): 117 command = ["python", "./Tools/BuildSlaveSupport/built-product-archive", 118 WithProperties("--platform=%(platform)s"), WithProperties("--%(configuration)s"), "extract"] 119 name = "extract-built-product" 120 description = ["extracting built product"] 121 descriptionDone = ["extracted built product"] 122 haltOnFailure = True 123 124 125class UploadBuiltProduct(transfer.FileUpload): 126 slavesrc = WithProperties("WebKitBuild/%(configuration)s.zip") 127 masterdest = WithProperties("archives/%(fullPlatform)s-%(architecture)s-%(configuration)s/%(got_revision)s.zip") 128 haltOnFailure = True 129 130 def __init__(self): 131 transfer.FileUpload.__init__(self, self.slavesrc, self.masterdest, mode=0644) 132 133 134class DownloadBuiltProduct(transfer.FileDownload): 135 slavedest = WithProperties("WebKitBuild/%(configuration)s.zip") 136 mastersrc = WithProperties("archives/%(fullPlatform)s-%(architecture)s-%(configuration)s/%(got_revision)s.zip") 137 haltOnFailure = True 138 flunkOnFailure = True 139 140 def __init__(self): 141 transfer.FileDownload.__init__(self, self.mastersrc, self.slavedest) 142 143 144class RunJavaScriptCoreTests(shell.Test): 145 name = "jscore-test" 146 description = ["jscore-tests running"] 147 descriptionDone = ["jscore-tests"] 148 command = ["perl", "./Tools/Scripts/run-javascriptcore-tests", WithProperties("--%(configuration)s")] 149 logfiles = {'actual.html (source)': 'Source/JavaScriptCore/tests/mozilla/actual.html'} 150 151 def __init__(self, skipBuild=False, *args, **kwargs): 152 self.skipBuild = skipBuild 153 shell.Test.__init__(self, *args, **kwargs) 154 self.addFactoryArguments(skipBuild=skipBuild) 155 156 def start(self): 157 appendCustomBuildFlags(self, self.getProperty('platform')) 158 if self.skipBuild: 159 self.setCommand(self.command + ['--skip-build']) 160 return shell.Test.start(self) 161 162 def commandComplete(self, cmd): 163 shell.Test.commandComplete(self, cmd) 164 165 logText = cmd.logs['stdio'].getText() 166 statusLines = [line for line in logText.splitlines() if line.find('regression') >= 0 and line.find(' found.') >= 0] 167 if statusLines and statusLines[0].split()[0] != '0': 168 self.regressionLine = statusLines[0] 169 else: 170 self.regressionLine = None 171 172 if 'actual.html (source)' in cmd.logs: 173 self.addHTMLLog('actual.html', cmd.logs['actual.html (source)'].getText()) 174 175 def evaluateCommand(self, cmd): 176 if self.regressionLine: 177 return FAILURE 178 179 if cmd.rc != 0: 180 return FAILURE 181 182 return SUCCESS 183 184 def getText(self, cmd, results): 185 return self.getText2(cmd, results) 186 187 def getText2(self, cmd, results): 188 if results != SUCCESS and self.regressionLine: 189 return [self.name, self.regressionLine] 190 191 return [self.name] 192 193 194class RunWebKitTests(shell.Test): 195 name = "layout-test" 196 description = ["layout-tests running"] 197 descriptionDone = ["layout-tests"] 198 command = ["perl", "./Tools/Scripts/run-webkit-tests", "--no-launch-safari", "--no-new-test-results", 199 "--no-sample-on-timeout", "--results-directory", "layout-test-results", "--use-remote-links-to-tests", 200 WithProperties("--%(configuration)s"), "--exit-after-n-crashes-or-timeouts", "20", "--exit-after-n-failures", "500"] 201 202 def __init__(self, skipBuild=False, *args, **kwargs): 203 self.skipBuild = skipBuild 204 shell.Test.__init__(self, *args, **kwargs) 205 self.addFactoryArguments(skipBuild=skipBuild) 206 207 def start(self): 208 platform = self.getProperty('platform') 209 appendCustomBuildFlags(self, platform) 210 if platform == "win": 211 rootArgument = ['--root=' + os.path.join("WebKitBuild", self.getProperty('configuration'), "bin")] 212 else: 213 rootArgument = ['--root=WebKitBuild/bin'] 214 if self.skipBuild: 215 self.setCommand(self.command + rootArgument) 216 return shell.Test.start(self) 217 218 def commandComplete(self, cmd): 219 shell.Test.commandComplete(self, cmd) 220 221 logText = cmd.logs['stdio'].getText() 222 incorrectLayoutLines = [] 223 for line in logText.splitlines(): 224 if line.find('had incorrect layout') >= 0 or line.find('were new') >= 0 or line.find('was new') >= 0: 225 incorrectLayoutLines.append(line) 226 elif line.find('test case') >= 0 and (line.find(' crashed') >= 0 or line.find(' timed out') >= 0): 227 incorrectLayoutLines.append(line) 228 elif line.startswith("WARNING:") and line.find(' leak') >= 0: 229 incorrectLayoutLines.append(line.replace('WARNING: ', '')) 230 elif line.find('Exiting early') >= 0: 231 incorrectLayoutLines.append(line) 232 233 # FIXME: Detect and summarize leaks of RefCounted objects 234 235 self.incorrectLayoutLines = incorrectLayoutLines 236 237 def evaluateCommand(self, cmd): 238 if self.incorrectLayoutLines: 239 if len(self.incorrectLayoutLines) == 1: 240 line = self.incorrectLayoutLines[0] 241 if line.find('were new') >= 0 or line.find('was new') >= 0 or line.find(' leak') >= 0: 242 return WARNINGS 243 244 return FAILURE 245 246 if cmd.rc != 0: 247 return FAILURE 248 249 return SUCCESS 250 251 def getText(self, cmd, results): 252 return self.getText2(cmd, results) 253 254 def getText2(self, cmd, results): 255 if results != SUCCESS and self.incorrectLayoutLines: 256 return self.incorrectLayoutLines 257 258 return [self.name] 259 260 261class NewRunWebKitTests(RunWebKitTests): 262 command = ["python", "./Tools/Scripts/new-run-webkit-tests", "--noshow-results", 263 "--verbose", "--results-directory", "layout-test-results", 264 "--builder-name", WithProperties("%(buildername)s"), 265 "--build-number", WithProperties("%(buildnumber)s"), 266 "--master-name", "webkit.org", 267 "--test-results-server", "test-results.appspot.com", 268 WithProperties("--%(configuration)s")] 269 270 271class RunPythonTests(shell.Test): 272 name = "webkitpy-test" 273 description = ["python-tests running"] 274 descriptionDone = ["python-tests"] 275 command = ["python", "./Tools/Scripts/test-webkitpy"] 276 277 278class RunPerlTests(shell.Test): 279 name = "webkitperl-test" 280 description = ["perl-tests running"] 281 descriptionDone = ["perl-tests"] 282 command = ["perl", "./Tools/Scripts/test-webkitperl"] 283 284 285class RunGtkAPITests(shell.Test): 286 name = "API tests" 287 description = ["API tests running"] 288 descriptionDone = ["API tests"] 289 command = ["perl", "./Tools/Scripts/run-gtk-tests", WithProperties("--%(configuration)s")] 290 291 def commandComplete(self, cmd): 292 shell.Test.commandComplete(self, cmd) 293 294 logText = cmd.logs['stdio'].getText() 295 incorrectLines = [] 296 for line in logText.splitlines(): 297 if line.startswith('ERROR'): 298 incorrectLines.append(line) 299 300 self.incorrectLines = incorrectLines 301 302 def evaluateCommand(self, cmd): 303 if self.incorrectLines: 304 return FAILURE 305 306 if cmd.rc != 0: 307 return FAILURE 308 309 return SUCCESS 310 311 def getText(self, cmd, results): 312 return self.getText2(cmd, results) 313 314 def getText2(self, cmd, results): 315 if results != SUCCESS and self.incorrectLines: 316 return ["%d API tests failed" % len(self.incorrectLines)] 317 318 return [self.name] 319 320class RunQtAPITests(shell.Test): 321 name = "API tests" 322 description = ["API tests running"] 323 descriptionDone = ["API tests"] 324 command = ["python", "./Tools/Scripts/run-qtwebkit-tests", 325 "--output-file=qt-unit-tests.html", "--do-not-open-results", "--timeout=120", 326 WithProperties("WebKitBuild/%(configuration_pretty)s/WebKit/qt/tests/")] 327 328 def start(self): 329 self.setProperty("configuration_pretty", self.getProperty("configuration").title()) 330 return shell.Test.start(self) 331 332 def commandComplete(self, cmd): 333 shell.Test.commandComplete(self, cmd) 334 335 logText = cmd.logs['stdio'].getText() 336 foundItems = re.findall("TOTALS: (?P<passed>\d+) passed, (?P<failed>\d+) failed, (?P<skipped>\d+) skipped", logText) 337 338 self.incorrectTests = 0 339 self.statusLine = [] 340 341 if foundItems: 342 self.incorrectTests = int(foundItems[0][1]) 343 if self.incorrectTests > 0: 344 self.statusLine = [ 345 "%s passed, %s failed, %s skipped" % (foundItems[0][0], foundItems[0][1], foundItems[0][2]) 346 ] 347 348 def evaluateCommand(self, cmd): 349 if self.incorrectTests: 350 return WARNINGS 351 352 if cmd.rc != 0: 353 return FAILURE 354 355 return SUCCESS 356 357 def getText(self, cmd, results): 358 return self.getText2(cmd, results) 359 360 def getText2(self, cmd, results): 361 if results != SUCCESS and self.incorrectTests: 362 return self.statusLine 363 364 return [self.name] 365 366class RunWebKitLeakTests(RunWebKitTests): 367 warnOnWarnings = True 368 def start(self): 369 self.setCommand(self.command + ["--leaks"]) 370 return RunWebKitTests.start(self) 371 372 373class RunWebKit2Tests(RunWebKitTests): 374 def start(self): 375 self.setCommand(self.command + ["--webkit-test-runner"]) 376 return RunWebKitTests.start(self) 377 378 379class RunChromiumWebKitUnitTests(shell.Test): 380 name = "webkit-unit-tests" 381 description = ["webkit-unit-tests running"] 382 descriptionDone = ["webkit-unit-tests"] 383 command = ["perl", "./Tools/Scripts/run-chromium-webkit-unit-tests", 384 WithProperties("--%(configuration)s")] 385 386 387class ArchiveTestResults(shell.ShellCommand): 388 command = ["python", "./Tools/BuildSlaveSupport/test-result-archive", 389 WithProperties("--platform=%(platform)s"), WithProperties("--%(configuration)s"), "archive"] 390 name = "archive-test-results" 391 description = ["archiving test results"] 392 descriptionDone = ["archived test results"] 393 haltOnFailure = True 394 395 396class UploadTestResults(transfer.FileUpload): 397 slavesrc = "layout-test-results.zip" 398 masterdest = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s).zip") 399 400 def __init__(self): 401 transfer.FileUpload.__init__(self, self.slavesrc, self.masterdest, mode=0644) 402 403 404class ExtractTestResults(master.MasterShellCommand): 405 zipFile = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s).zip") 406 resultDirectory = WithProperties("public_html/results/%(buildername)s/r%(got_revision)s (%(buildnumber)s)") 407 descriptionDone = ["uploaded results"] 408 409 def __init__(self): 410 master.MasterShellCommand.__init__(self, "") 411 412 def resultDirectoryURL(self): 413 return self.build.getProperties().render(self.resultDirectory).replace("public_html/", "/") + "/" 414 415 def start(self): 416 self.command = ["ditto", "-k", "-x", "-V", self.build.getProperties().render(self.zipFile), self.build.getProperties().render(self.resultDirectory)] 417 return master.MasterShellCommand.start(self) 418 419 def addCustomURLs(self): 420 url = self.resultDirectoryURL() + "results.html" 421 self.addURL("view results", url) 422 423 def finished(self, result): 424 self.addCustomURLs() 425 return master.MasterShellCommand.finished(self, result) 426 427 428class ExtractTestResultsAndLeaks(ExtractTestResults): 429 def addCustomURLs(self): 430 ExtractTestResults.addCustomURLs(self) 431 url = "/LeaksViewer/?url=" + urllib.quote(self.resultDirectoryURL(), safe="") 432 self.addURL("view leaks", url) 433 434 435class Factory(factory.BuildFactory): 436 def __init__(self, platform, configuration, architectures, buildOnly): 437 factory.BuildFactory.__init__(self) 438 self.addStep(ConfigureBuild, platform=platform, configuration=configuration, architecture=" ".join(architectures), buildOnly=buildOnly) 439 self.addStep(CheckOutSource) 440 if platform in ("win", "chromium-win"): 441 self.addStep(KillOldProcesses) 442 if platform == "win": 443 self.addStep(InstallWin32Dependencies) 444 if platform.startswith("chromium"): 445 self.addStep(InstallChromiumDependencies) 446 447class BuildFactory(Factory): 448 def __init__(self, platform, configuration, architectures, triggers=None): 449 Factory.__init__(self, platform, configuration, architectures, True) 450 self.addStep(CompileWebKit) 451 if triggers: 452 self.addStep(ArchiveBuiltProduct) 453 self.addStep(UploadBuiltProduct) 454 self.addStep(trigger.Trigger, schedulerNames=triggers) 455 456class TestFactory(Factory): 457 TestClass = RunWebKitTests 458 ExtractTestResultsClass = ExtractTestResults 459 def __init__(self, platform, configuration, architectures): 460 Factory.__init__(self, platform, configuration, architectures, False) 461 self.addStep(DownloadBuiltProduct) 462 self.addStep(ExtractBuiltProduct) 463 self.addStep(RunJavaScriptCoreTests, skipBuild=True) 464 self.addStep(self.TestClass, skipBuild=(platform == 'win')) 465 # Tiger's Python 2.3 is too old. WebKit Python requires 2.5+. 466 # Sadly we have no way to detect the version on the slave from here. 467 if platform != "mac-tiger": 468 self.addStep(RunPythonTests) 469 self.addStep(RunPerlTests) 470 self.addStep(ArchiveTestResults) 471 self.addStep(UploadTestResults) 472 self.addStep(self.ExtractTestResultsClass) 473 474class BuildAndTestFactory(Factory): 475 TestClass = RunWebKitTests 476 ExtractTestResultsClass = ExtractTestResults 477 def __init__(self, platform, configuration, architectures): 478 Factory.__init__(self, platform, configuration, architectures, False) 479 if platform.startswith("chromium"): 480 self.addStep(CleanupChromiumCrashLogs) 481 self.addStep(CompileWebKit) 482 if not platform.startswith("chromium"): 483 self.addStep(RunJavaScriptCoreTests) 484 if platform.startswith("chromium"): 485 self.addStep(RunChromiumWebKitUnitTests) 486 self.addStep(self.TestClass) 487 # Tiger's Python 2.3 is too old. WebKit Python requires 2.5+. 488 # Sadly we have no way to detect the version on the slave from here. 489 # Chromium Win runs in non-Cygwin environment, which is not yet fit 490 # for running tests. This can be removed once bug 48166 is fixed. 491 if platform != "mac-tiger": 492 self.addStep(RunPythonTests) 493 # Chromium Win runs in non-Cygwin environment, which is not yet fit 494 # for running tests. This can be removed once bug 48166 is fixed. 495 if platform != "chromium-win": 496 self.addStep(RunPerlTests) 497 self.addStep(ArchiveTestResults) 498 self.addStep(UploadTestResults) 499 self.addStep(self.ExtractTestResultsClass) 500 if platform == "gtk": 501 self.addStep(RunGtkAPITests) 502 if platform == "qt": 503 self.addStep(RunQtAPITests) 504 505class BuildAndTestLeaksFactory(BuildAndTestFactory): 506 TestClass = RunWebKitLeakTests 507 ExtractTestResultsClass = ExtractTestResultsAndLeaks 508 509class NewBuildAndTestFactory(BuildAndTestFactory): 510 TestClass = NewRunWebKitTests 511 512class TestWebKit2Factory(TestFactory): 513 TestClass = RunWebKit2Tests 514 515class PlatformSpecificScheduler(AnyBranchScheduler): 516 def __init__(self, platform, branch, **kwargs): 517 self.platform = platform 518 filter = ChangeFilter(branch=[branch, None], filter_fn=self.filter) 519 AnyBranchScheduler.__init__(self, name=platform, change_filter=filter, **kwargs) 520 521 def filter(self, change): 522 return wkbuild.should_build(self.platform, change.files) 523 524trunk_filter = ChangeFilter(branch=["trunk", None]) 525 526def loadBuilderConfig(c): 527 # FIXME: These file handles are leaked. 528 passwords = simplejson.load(open('passwords.json')) 529 config = simplejson.load(open('config.json')) 530 531 # use webkitpy's buildbot module to test for core builders 532 wkbb = wkbuildbot() 533 534 c['slaves'] = [BuildSlave(slave['name'], passwords[slave['name']], max_builds=1) for slave in config['slaves']] 535 536 c['schedulers'] = [] 537 for scheduler in config['schedulers']: 538 if "change_filter" in scheduler: 539 scheduler["change_filter"] = globals()[scheduler["change_filter"]] 540 kls = globals()[scheduler.pop('type')] 541 c['schedulers'].append(kls(**scheduler)) 542 543 c['builders'] = [] 544 for builder in config['builders']: 545 for slaveName in builder['slavenames']: 546 for slave in config['slaves']: 547 if slave['name'] != slaveName or slave['platform'] == '*': 548 continue 549 550 if slave['platform'] != builder['platform']: 551 raise Exception, "Builder %r is for platform %r but has slave %r for platform %r!" % (builder['name'], builder['platform'], slave['name'], slave['platform']) 552 553 break 554 555 factory = globals()["%sFactory" % builder.pop('type')] 556 factoryArgs = [] 557 for key in "platform", "configuration", "architectures", "triggers": 558 value = builder.pop(key, None) 559 if value: 560 factoryArgs.append(value) 561 562 builder["factory"] = factory(*factoryArgs) 563 564 builder["category"] = "noncore" 565 if wkbb._is_core_builder(builder['name']): 566 builder["category"] = "core" 567 568 c['builders'].append(builder) 569 570loadBuilderConfig(c) 571 572c['change_source'] = PBChangeSource() 573 574# permissions for WebStatus 575authz = Authz( 576 forceBuild=True, 577 forceAllBuilds=True, 578 pingBuilder=True, 579 gracefulShutdown=False, 580 stopBuild=True, 581 stopAllBuilds=True, 582 cancelPendingBuild=True, 583 stopChange=True, 584 cleanShutdown=False) 585 586c['status'] = [] 587c['status'].append(html.WebStatus(http_port=8710, 588 revlink="http://trac.webkit.org/changeset/%s", 589 authz=authz)) 590 591c['slavePortnum'] = 17000 592c['projectName'] = "WebKit" 593c['projectURL'] = "http://webkit.org" 594c['buildbotURL'] = "http://build.webkit.org/" 595 596c['buildHorizon'] = 1000 597c['logHorizon'] = 500 598c['eventHorizon'] = 200 599c['buildCacheSize'] = 60 600