| OLD | NEW |
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Set of utilities to add commands to a buildbot factory. | 5 """Set of utilities to add commands to a buildbot factory. |
| 6 | 6 |
| 7 This is based on commands.py and adds swarm-specific commands.""" | 7 This is based on commands.py and adds swarm-specific commands.""" |
| 8 | 8 |
| 9 from buildbot.steps import shell, source | 9 from buildbot.steps import source |
| 10 from twisted.python import log | 10 from twisted.python import log |
| 11 | 11 |
| 12 from master import chromium_step | 12 from master import chromium_step |
| 13 from master.factory import commands | 13 from master.factory import commands |
| 14 | 14 |
| 15 import config | 15 import config |
| 16 | 16 |
| 17 | 17 |
| 18 def TestStepFilterTriggerSwarm(bStep): | 18 def TestStepFilterTriggerSwarm(bStep): |
| 19 """Returns True if any swarm step is going to be run by this builder or a | 19 """Returns True if any swarm step is going to be run by this builder or a |
| 20 triggered one. | 20 triggered one. |
| 21 | 21 |
| 22 This is only useful on the Try Server, where triggering the swarm_triggered | 22 This is only useful on the Try Server, where triggering the swarm_triggered |
| 23 try builder is conditional on running at least one swarm job there. Nobody | 23 try builder is conditional on running at least one swarm job there. Nobody |
| 24 wants email for an empty job. | 24 wants email for an empty job. |
| 25 """ | 25 """ |
| 26 return bool(commands.GetSwarmTests(bStep)) | 26 return bool(commands.GetSwarmTests(bStep)) |
| 27 | 27 |
| 28 | 28 |
| 29 def TestStepFilterRetrieveSwarmResult(bStep): | |
| 30 """Returns True if the given swarm step to get results should be run. | |
| 31 | |
| 32 It should be run if the .isolated hash was calculated. | |
| 33 """ | |
| 34 # TODO(maruel): bStep.name[:-len('_swarm')] once the swarm retrieve results | |
| 35 # steps have the _swarm suffix. | |
| 36 return bStep.name in commands.GetProp(bStep, 'swarm_hashes', {}) | |
| 37 | |
| 38 | |
| 39 class SwarmClientSVN(source.SVN): | 29 class SwarmClientSVN(source.SVN): |
| 40 """Uses the revision specified by use_swarm_client_revision.""" | 30 """Uses the revision specified by use_swarm_client_revision.""" |
| 41 | 31 |
| 42 def start(self): | 32 def start(self): |
| 43 """Contrary to source.Source, ignores the branch, source stamp and patch.""" | 33 """Contrary to source.Source, ignores the branch, source stamp and patch.""" |
| 44 self.args['workdir'] = self.workdir | 34 self.args['workdir'] = self.workdir |
| 45 revision = commands.GetProp(self, 'use_swarm_client_revision', None) | 35 revision = commands.GetProp(self, 'use_swarm_client_revision', None) |
| 46 self.startVC(None, revision, None) | 36 self.startVC(None, revision, None) |
| 47 | 37 |
| 48 | 38 |
| 49 class SwarmingClientGIT(source.Git): | 39 class SwarmingClientGIT(source.Git): |
| 50 """Uses the revision specified by use_swarming_client_revision.""" | 40 """Uses the revision specified by use_swarming_client_revision.""" |
| 51 | 41 |
| 52 def start(self): | 42 def start(self): |
| 53 """Contrary to source.Source, ignores the branch, source stamp and patch.""" | 43 """Contrary to source.Source, ignores the branch, source stamp and patch.""" |
| 54 self.args['workdir'] = self.workdir | 44 self.args['workdir'] = self.workdir |
| 55 revision = commands.GetProp(self, 'use_swarming_client_revision', None) | 45 revision = commands.GetProp(self, 'use_swarming_client_revision', None) |
| 56 self.startVC(None, revision, None) | 46 self.startVC(None, revision, None) |
| 57 | 47 |
| 58 | 48 |
| 59 class SwarmShellForTriggeringTests(shell.ShellCommand): | |
| 60 """Triggers all the swarm jobs at once. | |
| 61 | |
| 62 All the tests will run concurrently on Swarm and individual steps will gather | |
| 63 the results. | |
| 64 | |
| 65 Makes sure each triggered swarm job has the proper number of shards. | |
| 66 | |
| 67 This class can be used both on the Try Server, which supports 'testfilter' or | |
| 68 on the CI, where the steps are run inconditionally. | |
| 69 """ | |
| 70 def __init__(self, *args, **kwargs): | |
| 71 self.tests = kwargs.pop('tests', []) | |
| 72 assert all(t.__class__.__name__ == 'SwarmTest' for t in self.tests) | |
| 73 shell.ShellCommand.__init__(self, *args, **kwargs) | |
| 74 | |
| 75 def start(self): | |
| 76 """Triggers the intersection of 'swarm_hashes' build property, | |
| 77 self.tests and 'testfilter' build property if set. | |
| 78 | |
| 79 'swarm_hashes' is already related to GetSwarmTests(). | |
| 80 """ | |
| 81 # Only used for pass gtest filters specified by the user via 'testfilter'. | |
| 82 swarm_tests = commands.GetSwarmTests(self) | |
| 83 # The 'swarm_hashes' build property has been set by the | |
| 84 # CalculateIsolatedSha1s build step. It will have all the steps that can be | |
| 85 # triggered. This implicitly takes account 'testfilter'. | |
| 86 swarm_tests_hash_mapping = commands.GetProp(self, 'swarm_hashes', {}) | |
| 87 | |
| 88 # TODO(maruel): Move more logic out into | |
| 89 # scripts/slave/swarming/trigger_swarm_shim.py. | |
| 90 command = self.command[:] | |
| 91 for swarm_test in self.tests: | |
| 92 if swarm_tests_hash_mapping.get(swarm_test.test_name): | |
| 93 command.extend( | |
| 94 [ | |
| 95 '--task', | |
| 96 swarm_tests_hash_mapping[swarm_test.test_name], | |
| 97 swarm_test.test_name, | |
| 98 '%d' % swarm_test.shards, | |
| 99 # '*' is a special value to mean no filter. This is used so '' is | |
| 100 # not used, as '' may be misinterpreted by the shell, especially | |
| 101 # on Windows. | |
| 102 swarm_tests.get(swarm_test.test_name) or '*', | |
| 103 ]) | |
| 104 else: | |
| 105 log.msg('Given a swarm test, %s, that has no matching hash' % | |
| 106 swarm_test.test_name) | |
| 107 | |
| 108 self.setCommand(command) | |
| 109 shell.ShellCommand.start(self) | |
| 110 | |
| 111 | |
| 112 class SwarmCommands(commands.FactoryCommands): | 49 class SwarmCommands(commands.FactoryCommands): |
| 113 """Encapsulates methods to add swarm commands to a buildbot factory""" | 50 """Encapsulates methods to add swarm commands to a buildbot factory""" |
| 114 def AddTriggerSwarmTestStep(self, swarm_server, isolation_outdir, tests, | |
| 115 doStepIf): | |
| 116 assert all(t.__class__.__name__ == 'SwarmTest' for t in tests) | |
| 117 command = [ | |
| 118 self._python, | |
| 119 self.PathJoin(self._script_dir, 'swarming', 'trigger_swarm_shim.py'), | |
| 120 '--swarming', swarm_server, | |
| 121 '--isolate-server', isolation_outdir, | |
| 122 ] | |
| 123 command = self.AddBuildProperties(command) | |
| 124 assert all(i for i in command), command | |
| 125 self._factory.addStep( | |
| 126 SwarmShellForTriggeringTests, | |
| 127 name='swarm_trigger_tests', | |
| 128 description='Trigger swarm steps', | |
| 129 command=command, | |
| 130 tests=tests, | |
| 131 doStepIf=doStepIf) | |
| 132 | |
| 133 def AddGetSwarmTestResultStep(self, swarm_server, test_name, num_shards): | |
| 134 """Adds the step to retrieve the Swarm job results asynchronously.""" | |
| 135 # TODO(maruel): assert test_name.endswith('_swarm') once swarm retrieve | |
| 136 # results steps have _swarm suffix. | |
| 137 command = [ | |
| 138 self._python, | |
| 139 self.PathJoin(self._script_dir, 'swarming', 'get_swarm_results_shim.py'), | |
| 140 '--swarming', swarm_server, | |
| 141 '--shards', '%d' % num_shards, | |
| 142 test_name, | |
| 143 ] | |
| 144 command = self.AddBuildProperties(command) | |
| 145 | |
| 146 # Swarm handles the timeouts due to no ouput being produced for 10 minutes, | |
| 147 # but we don't have access to the output until the whole test is done, which | |
| 148 # may take more than 10 minutes, so we increase the buildbot timeout. | |
| 149 timeout = 2 * 60 * 60 | |
| 150 self._factory.addStep( | |
| 151 chromium_step.AnnotatedCommand, | |
| 152 name=test_name, | |
| 153 description='%s Swarming' % test_name, | |
| 154 command=command, | |
| 155 timeout=timeout, | |
| 156 doStepIf=TestStepFilterRetrieveSwarmResult) | |
| 157 | 51 |
| 158 def AddUpdateSwarmClientStep(self): | 52 def AddUpdateSwarmClientStep(self): |
| 159 """Checks out swarming_client so it can be used at the right revision.""" | 53 """Checks out swarming_client so it can be used at the right revision.""" |
| 160 def doSwarmingStepIf(b): | 54 def doSwarmingStepIf(b): |
| 161 return bool(commands.GetProp(b, 'use_swarming_client_revision', None)) | 55 return bool(commands.GetProp(b, 'use_swarming_client_revision', None)) |
| 162 def doSwarmStepIf(b): | 56 def doSwarmStepIf(b): |
| 163 return not doSwarmingStepIf(b) | 57 return not doSwarmingStepIf(b) |
| 164 | 58 |
| 165 # Emulate the path of a src/DEPS checkout, to keep things simpler. | 59 # Emulate the path of a src/DEPS checkout, to keep things simpler. |
| 166 relpath = 'build/src/tools/swarming_client' | 60 relpath = 'build/src/tools/swarming_client' |
| 167 url = ( | 61 url = ( |
| 168 config.Master.server_url + | 62 config.Master.server_url + |
| 169 config.Master.repo_root + | 63 config.Master.repo_root + |
| 170 '/trunk/tools/swarm_client') | 64 '/trunk/tools/swarm_client') |
| 171 self._factory.addStep( | 65 self._factory.addStep( |
| 172 SwarmClientSVN, | 66 SwarmClientSVN, |
| 173 svnurl=url, | 67 svnurl=url, |
| 174 workdir=relpath, | 68 workdir=relpath, |
| 175 doStepIf=doSwarmStepIf) | 69 doStepIf=doSwarmStepIf) |
| 176 | 70 |
| 177 url = config.Master.git_server_url + '/external/swarming.client' | 71 url = config.Master.git_server_url + '/external/swarming.client' |
| 178 self._factory.addStep( | 72 self._factory.addStep( |
| 179 SwarmingClientGIT, | 73 SwarmingClientGIT, |
| 180 repourl=url, | 74 repourl=url, |
| 181 workdir=relpath, | 75 workdir=relpath, |
| 182 doStepIf=doSwarmingStepIf) | 76 doStepIf=doSwarmingStepIf) |
| 183 | 77 |
| 78 def AddSwarmingStep(self, swarming_server, isolate_server): |
| 79 """Adds the step to run and get results from Swarming.""" |
| 80 command = [ |
| 81 self._python, |
| 82 self.PathJoin(self._script_dir, 'swarming', 'swarming_run_shim.py'), |
| 83 '--swarming', swarming_server, |
| 84 '--isolate-server', isolate_server, |
| 85 ] |
| 86 command = self.AddBuildProperties(command) |
| 87 |
| 88 # Swarm handles the timeouts due to no ouput being produced for 10 minutes, |
| 89 # but we don't have access to the output until the whole test is done, which |
| 90 # may take more than 10 minutes, so we increase the buildbot timeout. |
| 91 timeout = 2 * 60 * 60 |
| 92 self._factory.addStep( |
| 93 chromium_step.AnnotatedCommand, |
| 94 name='swarming', |
| 95 description='Swarming tests', |
| 96 command=command, |
| 97 timeout=timeout) |
| 98 |
| 184 def AddIsolateTest(self, test_name): | 99 def AddIsolateTest(self, test_name): |
| 185 if not self._target: | 100 if not self._target: |
| 186 log.msg('No target specified, unable to find isolated files') | 101 log.msg('No target specified, unable to find isolated files') |
| 187 return | 102 return |
| 188 | 103 |
| 189 isolated_file = test_name + '.isolated' | 104 isolated_file = test_name + '.isolated' |
| 190 slave_script_path = self.PathJoin( | 105 slave_script_path = self.PathJoin( |
| 191 self._script_dir, 'swarming', 'isolate_shim.py') | 106 self._script_dir, 'swarming', 'isolate_shim.py') |
| 192 | 107 |
| 193 args = ['run', '--isolated', isolated_file, '--', '--no-cr'] | 108 args = ['run', '--isolated', isolated_file, '--', '--no-cr'] |
| 194 wrapper_args = [ | 109 wrapper_args = [ |
| 195 '--annotate=gtest', | 110 '--annotate=gtest', |
| 196 '--test-type=%s' % test_name, | 111 '--test-type=%s' % test_name, |
| 197 '--pass-build-dir', | 112 '--pass-build-dir', |
| 198 '--pass-target', | 113 '--pass-target', |
| 199 ] | 114 ] |
| 200 | 115 |
| 201 command = self.GetPythonTestCommand(slave_script_path, arg_list=args, | 116 command = self.GetPythonTestCommand(slave_script_path, arg_list=args, |
| 202 wrapper_args=wrapper_args) | 117 wrapper_args=wrapper_args) |
| 203 self.AddTestStep(chromium_step.AnnotatedCommand, | 118 self.AddTestStep(chromium_step.AnnotatedCommand, |
| 204 test_name, | 119 test_name, |
| 205 command) | 120 command) |
| 206 | |
| 207 def SetupWinNetworkDrive(self, drive, network_path): | |
| 208 script_path = self.PathJoin(self._script_dir, 'add_network_drive.py') | |
| 209 | |
| 210 command = [self._python, script_path, '--drive', drive, | |
| 211 '--network_path', network_path] | |
| 212 | |
| 213 self._factory.addStep( | |
| 214 shell.ShellCommand, | |
| 215 name='setup_windows_network_storage', | |
| 216 description='setup_windows_network_storage', | |
| 217 descriptionDone='setup_windows_network_storage', | |
| 218 command=command) | |
| OLD | NEW |