| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2013 The Swarming Authors. All rights reserved. | 2 # Copyright 2013 The Swarming Authors. All rights reserved. |
| 3 # Use of this source code is governed under the Apache License, Version 2.0 that | 3 # Use of this source code is governed under the Apache License, Version 2.0 that |
| 4 # can be found in the LICENSE file. | 4 # can be found in the LICENSE file. |
| 5 | 5 |
| 6 import datetime | 6 import datetime |
| 7 import hashlib | 7 import hashlib |
| 8 import json | 8 import json |
| 9 import logging | 9 import logging |
| 10 import os | 10 import os |
| 11 import StringIO | 11 import StringIO |
| 12 import subprocess | 12 import subprocess |
| 13 import sys | 13 import sys |
| 14 import tempfile | 14 import tempfile |
| 15 import threading | 15 import threading |
| 16 import time | 16 import time |
| 17 import unittest | 17 import unittest |
| 18 | 18 |
| 19 # net_utils adjusts sys.path. | 19 # net_utils adjusts sys.path. |
| 20 import net_utils | 20 import net_utils |
| 21 | 21 |
| 22 from depot_tools import auto_stub | 22 from depot_tools import auto_stub |
| 23 | 23 |
| 24 import auth | 24 import auth |
| 25 import isolateserver | 25 import isolateserver |
| 26 import swarming | 26 import swarming |
| 27 import test_utils | 27 import test_utils |
| 28 | 28 |
| 29 from utils import file_path | 29 from utils import file_path |
| 30 from utils import logging_utils |
| 30 from utils import tools | 31 from utils import tools |
| 31 | 32 |
| 32 import isolateserver_mock | 33 import isolateserver_mock |
| 33 | 34 |
| 34 | 35 |
| 35 FILE_HASH = u'1' * 40 | 36 FILE_HASH = u'1' * 40 |
| 36 TEST_NAME = u'unit_tests' | 37 TEST_NAME = u'unit_tests' |
| 37 | 38 |
| 38 | 39 |
| 39 OUTPUT = 'Ran stuff\n' | 40 OUTPUT = 'Ran stuff\n' |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 73 | 74 |
| 74 def main(args): | 75 def main(args): |
| 75 """Bypasses swarming.main()'s exception handling. | 76 """Bypasses swarming.main()'s exception handling. |
| 76 | 77 |
| 77 It gets in the way when debugging test failures. | 78 It gets in the way when debugging test failures. |
| 78 """ | 79 """ |
| 79 dispatcher = swarming.subcommand.CommandDispatcher('swarming') | 80 dispatcher = swarming.subcommand.CommandDispatcher('swarming') |
| 80 return dispatcher.execute(swarming.OptionParserSwarming(), args) | 81 return dispatcher.execute(swarming.OptionParserSwarming(), args) |
| 81 | 82 |
| 82 | 83 |
| 83 def gen_request_data(isolated_hash=FILE_HASH, properties=None, **kwargs): | 84 def gen_request_data(properties=None, **kwargs): |
| 84 out = { | 85 out = { |
| 85 'name': u'unit_tests', | 86 'expiration_secs': 3600, |
| 87 'name': 'unit_tests', |
| 86 'parent_task_id': '', | 88 'parent_task_id': '', |
| 87 'priority': 101, | 89 'priority': 101, |
| 88 'properties': { | 90 'properties': { |
| 89 'commands': [ | 91 'command': None, |
| 90 [ | 92 'dimensions': [ |
| 91 'python', | 93 {'key': 'foo', 'value': 'bar'}, |
| 92 'run_isolated.zip', | 94 {'key': 'os', 'value': 'Mac'}, |
| 93 '--isolated', isolated_hash, | |
| 94 '--isolate-server', 'https://localhost:2', | |
| 95 '--namespace', 'default-gzip', | |
| 96 '--', | |
| 97 '--some-arg', | |
| 98 '123', | |
| 99 ], | |
| 100 ], | 95 ], |
| 101 'data': [('https://localhost:1/fetch_url', 'swarm_data.zip')], | 96 'env': [], |
| 102 'dimensions': { | |
| 103 'foo': 'bar', | |
| 104 'os': 'Mac', | |
| 105 }, | |
| 106 'env': {}, | |
| 107 'execution_timeout_secs': 60, | 97 'execution_timeout_secs': 60, |
| 98 'extra_args': ['--some-arg', '123'], |
| 99 'grace_period_secs': 30, |
| 108 'idempotent': False, | 100 'idempotent': False, |
| 101 'inputs_ref': None, |
| 109 'io_timeout_secs': 60, | 102 'io_timeout_secs': 60, |
| 110 }, | 103 }, |
| 111 'scheduling_expiration_secs': 3600, | |
| 112 'tags': ['taga', 'tagb'], | 104 'tags': ['taga', 'tagb'], |
| 113 'user': 'joe@localhost', | 105 'user': 'joe@localhost', |
| 114 } | 106 } |
| 115 out.update(kwargs) | 107 out.update(kwargs) |
| 116 out['properties'].update(properties or {}) | 108 out['properties'].update(properties or {}) |
| 117 return out | 109 return out |
| 118 | 110 |
| 119 | 111 |
| 120 def gen_request_response(request, **kwargs): | 112 def gen_request_response(request, **kwargs): |
| 121 # As seen in services/swarming/handlers_api.py. | 113 # As seen in services/swarming/handlers_api.py. |
| 122 out = { | 114 out = { |
| 123 'request': request.copy(), | 115 'request': request.copy(), |
| 124 'task_id': '12300', | 116 'task_id': '12300', |
| 125 } | 117 } |
| 126 out.update(kwargs) | 118 out.update(kwargs) |
| 127 return out | 119 return out |
| 128 | 120 |
| 129 | 121 |
| 130 def gen_result_response(**kwargs): | 122 def gen_result_response(**kwargs): |
| 131 out = { | 123 out = { |
| 132 "abandoned_ts": None, | 124 u'bot_id': u'swarm6', |
| 133 "bot_id": "swarm6", | 125 u'completed_ts': u'2014-09-24T13:49:16.012345', |
| 134 "completed_ts": "2014-09-24 13:49:16", | 126 u'created_ts': u'2014-09-24T13:49:03.012345', |
| 135 "created_ts": "2014-09-24 13:49:03", | 127 u'duration': 0.9636809825897217, |
| 136 "durations": [0.9636809825897217], | 128 u'exit_code': 0, |
| 137 "exit_codes": [0], | 129 u'failure': False, |
| 138 "failure": False, | 130 u'internal_failure': False, |
| 139 "id": "10100", | 131 u'modified_ts': u'2014-09-24T13:49:17.012345', |
| 140 "internal_failure": False, | 132 u'name': u'heartbeat-canary-2014-09-24_13:49:01-os=Linux', |
| 141 "modified_ts": "2014-09-24 13:49:17", | 133 u'server_versions': [u'1'], |
| 142 "name": "heartbeat-canary-2014-09-24_13:49:01-os=Linux", | 134 u'started_ts': u'2014-09-24T13:49:09.012345', |
| 143 "started_ts": "2014-09-24 13:49:09", | 135 u'state': 'COMPLETED', |
| 144 "state": 112, | 136 u'tags': [u'cpu:x86', u'priority:100', u'user:joe@localhost'], |
| 145 "try_number": 1, | 137 u'task_id': u'10100', |
| 146 "user": "unknown", | 138 u'try_number': 1, |
| 139 u'user': u'joe@localhost', |
| 147 } | 140 } |
| 148 out.update(kwargs) | 141 out.update(kwargs) |
| 149 return out | 142 return out |
| 150 | 143 |
| 151 | 144 |
| 152 def gen_run_isolated_out_hack_log(isolate_server, namespace, isolated_hash): | |
| 153 data = { | |
| 154 'hash': isolated_hash, | |
| 155 'namespace': namespace, | |
| 156 'storage': isolate_server, | |
| 157 } | |
| 158 return (OUTPUT + | |
| 159 '[run_isolated_out_hack]%s[/run_isolated_out_hack]\n' % ( | |
| 160 json.dumps(data, sort_keys=True, separators=(',',':')))) | |
| 161 | |
| 162 | |
| 163 # Silence pylint 'Access to a protected member _Event of a client class'. | 145 # Silence pylint 'Access to a protected member _Event of a client class'. |
| 164 class NonBlockingEvent(threading._Event): # pylint: disable=W0212 | 146 class NonBlockingEvent(threading._Event): # pylint: disable=W0212 |
| 165 """Just like threading.Event, but a class and ignores timeout in 'wait'. | 147 """Just like threading.Event, but a class and ignores timeout in 'wait'. |
| 166 | 148 |
| 167 Intended to be used as a mock for threading.Event in tests. | 149 Intended to be used as a mock for threading.Event in tests. |
| 168 """ | 150 """ |
| 169 | 151 |
| 170 def wait(self, timeout=None): | 152 def wait(self, timeout=None): |
| 171 return super(NonBlockingEvent, self).wait(0) | 153 return super(NonBlockingEvent, self).wait(0) |
| 172 | 154 |
| 173 | 155 |
| 174 class NetTestCase(net_utils.TestCase): | 156 class NetTestCase(net_utils.TestCase): |
| 175 """Base class that defines the url_open mock.""" | 157 """Base class that defines the url_open mock.""" |
| 176 def setUp(self): | 158 def setUp(self): |
| 177 super(NetTestCase, self).setUp() | 159 super(NetTestCase, self).setUp() |
| 178 self._tempdir = None | 160 self._tempdir = None |
| 179 self.mock(auth, 'ensure_logged_in', lambda _: None) | 161 self.mock(auth, 'ensure_logged_in', lambda _: None) |
| 180 self.mock(time, 'sleep', lambda _: None) | 162 self.mock(time, 'sleep', lambda _: None) |
| 181 self.mock(subprocess, 'call', lambda *_: self.fail()) | 163 self.mock(subprocess, 'call', lambda *_: self.fail()) |
| 182 self.mock(threading, 'Event', NonBlockingEvent) | 164 self.mock(threading, 'Event', NonBlockingEvent) |
| 183 self.mock(sys, 'stdout', StringIO.StringIO()) | 165 self.mock(sys, 'stdout', StringIO.StringIO()) |
| 184 self.mock(sys, 'stderr', StringIO.StringIO()) | 166 self.mock(sys, 'stderr', StringIO.StringIO()) |
| 167 self.mock(logging_utils, 'prepare_logging', lambda *args: None) |
| 168 self.mock(logging_utils, 'set_console_level', lambda *args: None) |
| 185 | 169 |
| 186 def tearDown(self): | 170 def tearDown(self): |
| 187 try: | 171 try: |
| 188 if self._tempdir: | 172 if self._tempdir: |
| 189 file_path.rmtree(self._tempdir) | 173 file_path.rmtree(self._tempdir) |
| 190 if not self.has_failed(): | 174 if not self.has_failed(): |
| 191 self._check_output('', '') | 175 self._check_output('', '') |
| 192 finally: | 176 finally: |
| 193 super(NetTestCase, self).tearDown() | 177 super(NetTestCase, self).tearDown() |
| 194 | 178 |
| (...skipping 30 matching lines...) Expand all Loading... |
| 225 finally: | 209 finally: |
| 226 super(TestIsolated, self).tearDown() | 210 super(TestIsolated, self).tearDown() |
| 227 | 211 |
| 228 @property | 212 @property |
| 229 def server(self): | 213 def server(self): |
| 230 """Creates the Isolate Server mock on first reference.""" | 214 """Creates the Isolate Server mock on first reference.""" |
| 231 if not self._server: | 215 if not self._server: |
| 232 self._server = isolateserver_mock.MockIsolateServer() | 216 self._server = isolateserver_mock.MockIsolateServer() |
| 233 return self._server | 217 return self._server |
| 234 | 218 |
| 235 # Test isolated related code. | |
| 236 def test_isolated_get_data(self): | |
| 237 data = swarming.isolated_get_data(self.server.url) | |
| 238 self.assertEqual(['default'], self.server.contents.keys()) | |
| 239 self.assertEqual(1, len(self.server.contents['default'])) | |
| 240 h = self.server.contents['default'].popitem()[0] | |
| 241 expected = [ | |
| 242 ( | |
| 243 '%s/content-gs/retrieve/default/%s' % (self.server.url, h), | |
| 244 'swarm_data.zip', | |
| 245 ), | |
| 246 ] | |
| 247 self.assertEqual(expected, data) | |
| 248 | |
| 249 def test_isolated_get_run_commands(self): | |
| 250 actual = swarming.isolated_get_run_commands( | |
| 251 'http://foo.invalid', 'default', '1'*40, ['fo', 'ba'], True) | |
| 252 expected = [ | |
| 253 'python', | |
| 254 'run_isolated.zip', | |
| 255 '--isolated', '1111111111111111111111111111111111111111', | |
| 256 '--isolate-server', 'http://foo.invalid', | |
| 257 '--namespace', 'default', | |
| 258 '--verbose', | |
| 259 '--', | |
| 260 'fo', | |
| 261 'ba', | |
| 262 ] | |
| 263 self.assertEqual(expected, actual) | |
| 264 | |
| 265 | 219 |
| 266 class TestSwarmingTrigger(NetTestCase): | 220 class TestSwarmingTrigger(NetTestCase): |
| 267 def test_trigger_task_shards_2_shards(self): | 221 def test_trigger_task_shards_2_shards(self): |
| 268 task_request = swarming.TaskRequest( | 222 task_request = swarming.NewTaskRequest( |
| 269 command=['a', 'b'], | 223 expiration_secs=60*60, |
| 270 data=[], | |
| 271 dimensions={'foo': 'bar', 'os': 'Mac'}, | |
| 272 env={}, | |
| 273 expiration=60*60, | |
| 274 hard_timeout=60, | |
| 275 idempotent=False, | |
| 276 io_timeout=60, | |
| 277 name=TEST_NAME, | 224 name=TEST_NAME, |
| 225 parent_task_id=None, |
| 278 priority=101, | 226 priority=101, |
| 227 properties=swarming.TaskProperties( |
| 228 command=['a', 'b'], |
| 229 dimensions={'foo': 'bar', 'os': 'Mac'}, |
| 230 env={}, |
| 231 execution_timeout_secs=60, |
| 232 extra_args=[], |
| 233 grace_period_secs=30, |
| 234 idempotent=False, |
| 235 inputs_ref=None, |
| 236 io_timeout_secs=60), |
| 279 tags=['taga', 'tagb'], | 237 tags=['taga', 'tagb'], |
| 280 user='joe@localhost', | 238 user='joe@localhost') |
| 281 verbose=False) | |
| 282 | 239 |
| 283 request_1 = swarming.task_request_to_raw_request(task_request) | 240 request_1 = swarming.task_request_to_raw_request(task_request) |
| 284 request_1['name'] = u'unit_tests:0:2' | 241 request_1['name'] = u'unit_tests:0:2' |
| 285 request_1['properties']['env'] = { | 242 request_1['properties']['env'] = [ |
| 286 'GTEST_SHARD_INDEX': '0', 'GTEST_TOTAL_SHARDS': '2', | 243 {'key': 'GTEST_SHARD_INDEX', 'value': '0'}, |
| 287 } | 244 {'key': 'GTEST_TOTAL_SHARDS', 'value': '2'}, |
| 245 ] |
| 288 result_1 = gen_request_response(request_1) | 246 result_1 = gen_request_response(request_1) |
| 289 | 247 |
| 290 request_2 = swarming.task_request_to_raw_request(task_request) | 248 request_2 = swarming.task_request_to_raw_request(task_request) |
| 291 request_2['name'] = u'unit_tests:1:2' | 249 request_2['name'] = u'unit_tests:1:2' |
| 292 request_2['properties']['env'] = { | 250 request_2['properties']['env'] = [ |
| 293 'GTEST_SHARD_INDEX': '1', 'GTEST_TOTAL_SHARDS': '2', | 251 {'key': 'GTEST_SHARD_INDEX', 'value': '1'}, |
| 294 } | 252 {'key': 'GTEST_TOTAL_SHARDS', 'value': '2'}, |
| 253 ] |
| 295 result_2 = gen_request_response(request_2, task_id='12400') | 254 result_2 = gen_request_response(request_2, task_id='12400') |
| 296 self.expected_requests( | 255 self.expected_requests( |
| 297 [ | 256 [ |
| 298 ( | 257 ( |
| 299 'https://localhost:1/swarming/api/v1/client/handshake', | 258 'https://localhost:1/_ah/api/swarming/v1/tasks/new', |
| 300 {'data': {}, 'headers': {'X-XSRF-Token-Request': '1'}}, | 259 {'data': request_1}, |
| 301 {'server_version': 'v1', 'xsrf_token': 'Token'}, | |
| 302 ), | |
| 303 ( | |
| 304 'https://localhost:1/swarming/api/v1/client/request', | |
| 305 {'data': request_1, 'headers': {'X-XSRF-Token': 'Token'}}, | |
| 306 result_1, | 260 result_1, |
| 307 ), | 261 ), |
| 308 ( | 262 ( |
| 309 'https://localhost:1/swarming/api/v1/client/request', | 263 'https://localhost:1/_ah/api/swarming/v1/tasks/new', |
| 310 {'data': request_2, 'headers': {'X-XSRF-Token': 'Token'}}, | 264 {'data': request_2}, |
| 311 result_2, | 265 result_2, |
| 312 ), | 266 ), |
| 313 ]) | 267 ]) |
| 314 | 268 |
| 315 tasks = swarming.trigger_task_shards( | 269 tasks = swarming.trigger_task_shards( |
| 316 swarming='https://localhost:1', | 270 swarming='https://localhost:1', |
| 317 task_request=task_request, | 271 task_request=task_request, |
| 318 shards=2) | 272 shards=2) |
| 319 expected = { | 273 expected = { |
| 320 u'unit_tests:0:2': { | 274 u'unit_tests:0:2': { |
| 321 'shard_index': 0, | 275 'shard_index': 0, |
| 322 'task_id': '12300', | 276 'task_id': '12300', |
| 323 'view_url': 'https://localhost:1/user/task/12300', | 277 'view_url': 'https://localhost:1/user/task/12300', |
| 324 }, | 278 }, |
| 325 u'unit_tests:1:2': { | 279 u'unit_tests:1:2': { |
| 326 'shard_index': 1, | 280 'shard_index': 1, |
| 327 'task_id': '12400', | 281 'task_id': '12400', |
| 328 'view_url': 'https://localhost:1/user/task/12400', | 282 'view_url': 'https://localhost:1/user/task/12400', |
| 329 }, | 283 }, |
| 330 } | 284 } |
| 331 self.assertEqual(expected, tasks) | 285 self.assertEqual(expected, tasks) |
| 332 | 286 |
| 333 def test_trigger_task_shards_priority_override(self): | 287 def test_trigger_task_shards_priority_override(self): |
| 334 task_request = swarming.TaskRequest( | 288 task_request = swarming.NewTaskRequest( |
| 335 command=['a', 'b'], | 289 expiration_secs=60*60, |
| 336 data=[['https://foo.invalid/bar', 'bar.zip']], | |
| 337 dimensions={'foo': 'bar', 'os': 'Mac'}, | |
| 338 env={}, | |
| 339 expiration=60*60, | |
| 340 hard_timeout=60, | |
| 341 idempotent=False, | |
| 342 io_timeout=60, | |
| 343 name=TEST_NAME, | 290 name=TEST_NAME, |
| 291 parent_task_id='123', |
| 344 priority=101, | 292 priority=101, |
| 293 properties=swarming.TaskProperties( |
| 294 command=['a', 'b'], |
| 295 dimensions={'foo': 'bar', 'os': 'Mac'}, |
| 296 env={}, |
| 297 execution_timeout_secs=60, |
| 298 extra_args=[], |
| 299 grace_period_secs=30, |
| 300 idempotent=False, |
| 301 inputs_ref=None, |
| 302 io_timeout_secs=60), |
| 345 tags=['taga', 'tagb'], | 303 tags=['taga', 'tagb'], |
| 346 user='joe@localhost', | 304 user='joe@localhost') |
| 347 verbose=False) | |
| 348 | 305 |
| 349 os.environ['SWARMING_TASK_ID'] = '123' | 306 request = swarming.task_request_to_raw_request(task_request) |
| 350 try: | |
| 351 request = swarming.task_request_to_raw_request(task_request) | |
| 352 finally: | |
| 353 os.environ.pop('SWARMING_TASK_ID') | |
| 354 self.assertEqual('123', request['parent_task_id']) | 307 self.assertEqual('123', request['parent_task_id']) |
| 355 | 308 |
| 356 result = gen_request_response(request) | 309 result = gen_request_response(request) |
| 357 result['request']['priority'] = 200 | 310 result['request']['priority'] = 200 |
| 358 self.expected_requests( | 311 self.expected_requests( |
| 359 [ | 312 [ |
| 360 ( | 313 ( |
| 361 'https://localhost:1/swarming/api/v1/client/handshake', | 314 'https://localhost:1/_ah/api/swarming/v1/tasks/new', |
| 362 {'data': {}, 'headers': {'X-XSRF-Token-Request': '1'}}, | 315 {'data': request}, |
| 363 {'server_version': 'v1', 'xsrf_token': 'Token'}, | |
| 364 ), | |
| 365 ( | |
| 366 'https://localhost:1/swarming/api/v1/client/request', | |
| 367 {'data': request, 'headers': {'X-XSRF-Token': 'Token'}}, | |
| 368 result, | 316 result, |
| 369 ), | 317 ), |
| 370 ]) | 318 ]) |
| 371 | 319 |
| 372 os.environ['SWARMING_TASK_ID'] = '123' | 320 os.environ['SWARMING_TASK_ID'] = '123' |
| 373 try: | 321 try: |
| 374 tasks = swarming.trigger_task_shards( | 322 tasks = swarming.trigger_task_shards( |
| 375 swarming='https://localhost:1', | 323 swarming='https://localhost:1', |
| 376 shards=1, | 324 shards=1, |
| 377 task_request=task_request) | 325 task_request=task_request) |
| 378 finally: | 326 finally: |
| 379 os.environ.pop('SWARMING_TASK_ID') | 327 os.environ.pop('SWARMING_TASK_ID') |
| 380 expected = { | 328 expected = { |
| 381 u'unit_tests': { | 329 u'unit_tests': { |
| 382 'shard_index': 0, | 330 'shard_index': 0, |
| 383 'task_id': '12300', | 331 'task_id': '12300', |
| 384 'view_url': 'https://localhost:1/user/task/12300', | 332 'view_url': 'https://localhost:1/user/task/12300', |
| 385 } | 333 } |
| 386 } | 334 } |
| 387 self.assertEqual(expected, tasks) | 335 self.assertEqual(expected, tasks) |
| 388 self._check_output('', 'Priority was reset to 200\n') | 336 self._check_output('', 'Priority was reset to 200\n') |
| 389 | 337 |
| 390 def test_isolated_to_hash(self): | |
| 391 calls = [] | |
| 392 self.mock(subprocess, 'call', lambda *c: calls.append(c)) | |
| 393 content = '{}' | |
| 394 expected_hash = hashlib.sha1(content).hexdigest() | |
| 395 handle, isolated = tempfile.mkstemp( | |
| 396 prefix=u'swarming_test_', suffix=u'.isolated') | |
| 397 os.close(handle) | |
| 398 try: | |
| 399 with open(isolated, 'w') as f: | |
| 400 f.write(content) | |
| 401 hash_value, is_file = swarming.isolated_to_hash( | |
| 402 'https://localhost:2', 'default-gzip', isolated, hashlib.sha1, False) | |
| 403 finally: | |
| 404 os.remove(isolated) | |
| 405 self.assertEqual(expected_hash, hash_value) | |
| 406 self.assertEqual(True, is_file) | |
| 407 expected_calls = [ | |
| 408 ( | |
| 409 [ | |
| 410 sys.executable, | |
| 411 os.path.join(swarming.ROOT_DIR, 'isolate.py'), | |
| 412 'archive', | |
| 413 '--isolate-server', 'https://localhost:2', | |
| 414 '--namespace', 'default-gzip', | |
| 415 '--isolated', | |
| 416 isolated, | |
| 417 ], | |
| 418 False, | |
| 419 ), | |
| 420 ] | |
| 421 self.assertEqual(expected_calls, calls) | |
| 422 self._check_output('Archiving: %s\n' % isolated, '') | |
| 423 | |
| 424 | 338 |
| 425 class TestSwarmingCollection(NetTestCase): | 339 class TestSwarmingCollection(NetTestCase): |
| 426 def test_success(self): | 340 def test_success(self): |
| 427 self.expected_requests( | 341 self.expected_requests( |
| 428 [ | 342 [ |
| 429 ( | 343 ( |
| 430 'https://host:9001/swarming/api/v1/client/task/10100', | 344 'https://host:9001/_ah/api/swarming/v1/task/10100/result', |
| 431 {'retry_50x': False}, | 345 {'retry_50x': False}, |
| 432 gen_result_response(), | 346 gen_result_response(), |
| 433 ), | 347 ), |
| 434 ( | 348 ( |
| 435 'https://host:9001/swarming/api/v1/client/task/10100/output/all', | 349 'https://host:9001/_ah/api/swarming/v1/task/10100/stdout', |
| 436 {}, | 350 {}, |
| 437 {'outputs': [OUTPUT]}, | 351 {'output': OUTPUT}, |
| 438 ), | 352 ), |
| 439 ]) | 353 ]) |
| 440 expected = [gen_yielded_data(0, outputs=[OUTPUT])] | 354 expected = [gen_yielded_data(0, output=OUTPUT)] |
| 441 actual = get_results(['10100']) | 355 self.assertEqual(expected, get_results(['10100'])) |
| 442 self.assertEqual(expected, actual) | |
| 443 | 356 |
| 444 def test_failure(self): | 357 def test_failure(self): |
| 445 self.expected_requests( | 358 self.expected_requests( |
| 446 [ | 359 [ |
| 447 ( | 360 ( |
| 448 'https://host:9001/swarming/api/v1/client/task/10100', | 361 'https://host:9001/_ah/api/swarming/v1/task/10100/result', |
| 449 {'retry_50x': False}, | 362 {'retry_50x': False}, |
| 450 gen_result_response(exit_codes=[0, 1]), | 363 gen_result_response(exit_code=1), |
| 451 ), | 364 ), |
| 452 ( | 365 ( |
| 453 'https://host:9001/swarming/api/v1/client/task/10100/output/all', | 366 'https://host:9001/_ah/api/swarming/v1/task/10100/stdout', |
| 454 {}, | 367 {}, |
| 455 {'outputs': [OUTPUT]}, | 368 {'output': OUTPUT}, |
| 456 ), | 369 ), |
| 457 ]) | 370 ]) |
| 458 expected = [gen_yielded_data(0, outputs=[OUTPUT], exit_codes=[0, 1])] | 371 expected = [gen_yielded_data(0, output=OUTPUT, exit_code=1)] |
| 459 actual = get_results(['10100']) | 372 self.assertEqual(expected, get_results(['10100'])) |
| 460 self.assertEqual(expected, actual) | |
| 461 | 373 |
| 462 def test_no_ids(self): | 374 def test_no_ids(self): |
| 463 actual = get_results([]) | 375 actual = get_results([]) |
| 464 self.assertEqual([], actual) | 376 self.assertEqual([], actual) |
| 465 | 377 |
| 466 def test_url_errors(self): | 378 def test_url_errors(self): |
| 467 self.mock(logging, 'error', lambda *_, **__: None) | 379 self.mock(logging, 'error', lambda *_, **__: None) |
| 468 # NOTE: get_results() hardcodes timeout=10. | 380 # NOTE: get_results() hardcodes timeout=10. |
| 469 now = {} | 381 now = {} |
| 470 lock = threading.Lock() | 382 lock = threading.Lock() |
| 471 def get_now(): | 383 def get_now(): |
| 472 t = threading.current_thread() | 384 t = threading.current_thread() |
| 473 with lock: | 385 with lock: |
| 474 return now.setdefault(t, range(10)).pop(0) | 386 return now.setdefault(t, range(10)).pop(0) |
| 475 self.mock(swarming.net, 'sleep_before_retry', lambda _x, _y: None) | 387 self.mock(swarming.net, 'sleep_before_retry', lambda _x, _y: None) |
| 476 self.mock(swarming, 'now', get_now) | 388 self.mock(swarming, 'now', get_now) |
| 477 # The actual number of requests here depends on 'now' progressing to 10 | 389 # The actual number of requests here depends on 'now' progressing to 10 |
| 478 # seconds. It's called once per loop. Loop makes 9 iterations. | 390 # seconds. It's called once per loop. Loop makes 9 iterations. |
| 479 self.expected_requests( | 391 self.expected_requests( |
| 480 9 * [ | 392 9 * [ |
| 481 ( | 393 ( |
| 482 'https://host:9001/swarming/api/v1/client/task/10100', | 394 'https://host:9001/_ah/api/swarming/v1/task/10100/result', |
| 483 {'retry_50x': False}, | 395 {'retry_50x': False}, |
| 484 None, | 396 None, |
| 485 ) | 397 ) |
| 486 ]) | 398 ]) |
| 487 actual = get_results(['10100']) | 399 actual = get_results(['10100']) |
| 488 self.assertEqual([], actual) | 400 self.assertEqual([], actual) |
| 489 self.assertTrue(all(not v for v in now.itervalues()), now) | 401 self.assertTrue(all(not v for v in now.itervalues()), now) |
| 490 | 402 |
| 491 def test_many_shards(self): | 403 def test_many_shards(self): |
| 492 self.expected_requests( | 404 self.expected_requests( |
| 493 [ | 405 [ |
| 494 ( | 406 ( |
| 495 'https://host:9001/swarming/api/v1/client/task/10100', | 407 'https://host:9001/_ah/api/swarming/v1/task/10100/result', |
| 496 {'retry_50x': False}, | 408 {'retry_50x': False}, |
| 497 gen_result_response(), | 409 gen_result_response(), |
| 498 ), | 410 ), |
| 499 ( | 411 ( |
| 500 'https://host:9001/swarming/api/v1/client/task/10100/output/all', | 412 'https://host:9001/_ah/api/swarming/v1/task/10100/stdout', |
| 501 {}, | 413 {}, |
| 502 {'outputs': [SHARD_OUTPUT_1]}, | 414 {'output': SHARD_OUTPUT_1}, |
| 503 ), | 415 ), |
| 504 ( | 416 ( |
| 505 'https://host:9001/swarming/api/v1/client/task/10200', | 417 'https://host:9001/_ah/api/swarming/v1/task/10200/result', |
| 506 {'retry_50x': False}, | 418 {'retry_50x': False}, |
| 507 gen_result_response(), | 419 gen_result_response(), |
| 508 ), | 420 ), |
| 509 ( | 421 ( |
| 510 'https://host:9001/swarming/api/v1/client/task/10200/output/all', | 422 'https://host:9001/_ah/api/swarming/v1/task/10200/stdout', |
| 511 {}, | 423 {}, |
| 512 {'outputs': [SHARD_OUTPUT_2]}, | 424 {'output': SHARD_OUTPUT_2}, |
| 513 ), | 425 ), |
| 514 ( | 426 ( |
| 515 'https://host:9001/swarming/api/v1/client/task/10300', | 427 'https://host:9001/_ah/api/swarming/v1/task/10300/result', |
| 516 {'retry_50x': False}, | 428 {'retry_50x': False}, |
| 517 gen_result_response(), | 429 gen_result_response(), |
| 518 ), | 430 ), |
| 519 ( | 431 ( |
| 520 'https://host:9001/swarming/api/v1/client/task/10300/output/all', | 432 'https://host:9001/_ah/api/swarming/v1/task/10300/stdout', |
| 521 {}, | 433 {}, |
| 522 {'outputs': [SHARD_OUTPUT_3]}, | 434 {'output': SHARD_OUTPUT_3}, |
| 523 ), | 435 ), |
| 524 ]) | 436 ]) |
| 525 expected = [ | 437 expected = [ |
| 526 gen_yielded_data(0, outputs=[SHARD_OUTPUT_1]), | 438 gen_yielded_data(0, output=SHARD_OUTPUT_1), |
| 527 gen_yielded_data(1, outputs=[SHARD_OUTPUT_2]), | 439 gen_yielded_data(1, output=SHARD_OUTPUT_2), |
| 528 gen_yielded_data(2, outputs=[SHARD_OUTPUT_3]), | 440 gen_yielded_data(2, output=SHARD_OUTPUT_3), |
| 529 ] | 441 ] |
| 530 actual = get_results(['10100', '10200', '10300']) | 442 actual = get_results(['10100', '10200', '10300']) |
| 531 self.assertEqual(expected, sorted(actual)) | 443 self.assertEqual(expected, sorted(actual)) |
| 532 | 444 |
| 533 def test_output_collector_called(self): | 445 def test_output_collector_called(self): |
| 534 # Three shards, one failed. All results are passed to output collector. | 446 # Three shards, one failed. All results are passed to output collector. |
| 535 self.expected_requests( | 447 self.expected_requests( |
| 536 [ | 448 [ |
| 537 ( | 449 ( |
| 538 'https://host:9001/swarming/api/v1/client/task/10100', | 450 'https://host:9001/_ah/api/swarming/v1/task/10100/result', |
| 539 {'retry_50x': False}, | 451 {'retry_50x': False}, |
| 540 gen_result_response(), | 452 gen_result_response(), |
| 541 ), | 453 ), |
| 542 ( | 454 ( |
| 543 'https://host:9001/swarming/api/v1/client/task/10100/output/all', | 455 'https://host:9001/_ah/api/swarming/v1/task/10100/stdout', |
| 544 {}, | 456 {}, |
| 545 {'outputs': [SHARD_OUTPUT_1]}, | 457 {'output': SHARD_OUTPUT_1}, |
| 546 ), | 458 ), |
| 547 ( | 459 ( |
| 548 'https://host:9001/swarming/api/v1/client/task/10200', | 460 'https://host:9001/_ah/api/swarming/v1/task/10200/result', |
| 549 {'retry_50x': False}, | 461 {'retry_50x': False}, |
| 550 gen_result_response(), | 462 gen_result_response(), |
| 551 ), | 463 ), |
| 552 ( | 464 ( |
| 553 'https://host:9001/swarming/api/v1/client/task/10200/output/all', | 465 'https://host:9001/_ah/api/swarming/v1/task/10200/stdout', |
| 554 {}, | 466 {}, |
| 555 {'outputs': [SHARD_OUTPUT_2]}, | 467 {'output': SHARD_OUTPUT_2}, |
| 556 ), | 468 ), |
| 557 ( | 469 ( |
| 558 'https://host:9001/swarming/api/v1/client/task/10300', | 470 'https://host:9001/_ah/api/swarming/v1/task/10300/result', |
| 559 {'retry_50x': False}, | 471 {'retry_50x': False}, |
| 560 gen_result_response(exit_codes=[0, 1]), | 472 gen_result_response(exit_code=1), |
| 561 ), | 473 ), |
| 562 ( | 474 ( |
| 563 'https://host:9001/swarming/api/v1/client/task/10300/output/all', | 475 'https://host:9001/_ah/api/swarming/v1/task/10300/stdout', |
| 564 {}, | 476 {}, |
| 565 {'outputs': [SHARD_OUTPUT_3]}, | 477 {'output': SHARD_OUTPUT_3}, |
| 566 ), | 478 ), |
| 567 ]) | 479 ]) |
| 568 | 480 |
| 569 class FakeOutputCollector(object): | 481 class FakeOutputCollector(object): |
| 570 def __init__(self): | 482 def __init__(self): |
| 571 self.results = [] | 483 self.results = [] |
| 572 self._lock = threading.Lock() | 484 self._lock = threading.Lock() |
| 573 | 485 |
| 574 def process_shard_result(self, index, result): | 486 def process_shard_result(self, index, result): |
| 575 with self._lock: | 487 with self._lock: |
| 576 self.results.append((index, result)) | 488 self.results.append((index, result)) |
| 577 | 489 |
| 578 output_collector = FakeOutputCollector() | 490 output_collector = FakeOutputCollector() |
| 579 get_results(['10100', '10200', '10300'], output_collector) | 491 get_results(['10100', '10200', '10300'], output_collector) |
| 580 | 492 |
| 581 expected = [ | 493 expected = [ |
| 582 gen_yielded_data(0, outputs=[SHARD_OUTPUT_1]), | 494 gen_yielded_data(0, output=SHARD_OUTPUT_1), |
| 583 gen_yielded_data(1, outputs=[SHARD_OUTPUT_2]), | 495 gen_yielded_data(1, output=SHARD_OUTPUT_2), |
| 584 gen_yielded_data(2, outputs=[SHARD_OUTPUT_3], exit_codes=[0, 1]), | 496 gen_yielded_data(2, output=SHARD_OUTPUT_3, exit_code=1), |
| 585 ] | 497 ] |
| 586 self.assertEqual(sorted(expected), sorted(output_collector.results)) | 498 self.assertEqual(sorted(expected), sorted(output_collector.results)) |
| 587 | 499 |
| 588 def test_collect_nothing(self): | 500 def test_collect_nothing(self): |
| 589 self.mock(swarming, 'yield_results', lambda *_: []) | 501 self.mock(swarming, 'yield_results', lambda *_: []) |
| 590 self.assertEqual( | 502 self.assertEqual( |
| 591 1, collect('https://localhost:1', 'name', ['10100', '10200'])) | 503 1, collect('https://localhost:1', 'name', ['10100', '10200'])) |
| 592 self._check_output('', 'Results from some shards are missing: 0, 1\n') | 504 self._check_output('', 'Results from some shards are missing: 0, 1\n') |
| 593 | 505 |
| 594 def test_collect_success(self): | 506 def test_collect_success(self): |
| 595 data = gen_result_response(outputs=['Foo']) | 507 data = gen_result_response(output='Foo') |
| 596 self.mock(swarming, 'yield_results', lambda *_: [(0, data)]) | 508 self.mock(swarming, 'yield_results', lambda *_: [(0, data)]) |
| 597 self.assertEqual(0, collect('https://localhost:1', 'name', ['10100'])) | 509 self.assertEqual(0, collect('https://localhost:1', 'name', ['10100'])) |
| 598 expected = '\n'.join(( | 510 expected = u'\n'.join(( |
| 599 '+---------------------------------------------------------------------+', | 511 '+---------------------------------------------------------------------+', |
| 600 '| Shard 0 https://localhost:1/user/task/10100 |', | 512 '| Shard 0 https://localhost:1/user/task/10100 |', |
| 601 '+---------------------------------------------------------------------+', | 513 '+---------------------------------------------------------------------+', |
| 602 'Foo', | 514 'Foo', |
| 603 '+---------------------------------------------------------------------+', | 515 '+---------------------------------------------------------------------+', |
| 604 '| End of shard 0 Pending: 6.0s Duration: 1.0s Bot: swarm6 Exit: 0 |', | 516 '| End of shard 0 Pending: 6.0s Duration: 1.0s Bot: swarm6 Exit: 0 |', |
| 605 '+---------------------------------------------------------------------+', | 517 '+---------------------------------------------------------------------+', |
| 606 'Total duration: 1.0s', | 518 'Total duration: 1.0s', |
| 607 '')) | 519 '')) |
| 608 self._check_output(expected, '') | 520 self._check_output(expected, '') |
| 609 | 521 |
| 610 def test_collect_fail(self): | 522 def test_collect_fail(self): |
| 611 data = gen_result_response(outputs=['Foo'], exit_codes=[-9]) | 523 data = gen_result_response(output='Foo', exit_code=-9) |
| 612 data['outputs'] = ['Foo'] | 524 data['output'] = 'Foo' |
| 613 self.mock(swarming, 'yield_results', lambda *_: [(0, data)]) | 525 self.mock(swarming, 'yield_results', lambda *_: [(0, data)]) |
| 614 self.assertEqual(-9, collect('https://localhost:1', 'name', ['10100'])) | 526 self.assertEqual(-9, collect('https://localhost:1', 'name', ['10100'])) |
| 615 expected = '\n'.join(( | 527 expected = u'\n'.join(( |
| 616 '+----------------------------------------------------------------------' | 528 '+----------------------------------------------------------------------' |
| 617 '+', | 529 '+', |
| 618 '| Shard 0 https://localhost:1/user/task/10100 ' | 530 '| Shard 0 https://localhost:1/user/task/10100 ' |
| 619 '|', | 531 '|', |
| 620 '+----------------------------------------------------------------------' | 532 '+----------------------------------------------------------------------' |
| 621 '+', | 533 '+', |
| 622 'Foo', | 534 'Foo', |
| 623 '+----------------------------------------------------------------------' | 535 '+----------------------------------------------------------------------' |
| 624 '+', | 536 '+', |
| 625 '| End of shard 0 Pending: 6.0s Duration: 1.0s Bot: swarm6 Exit: -9 ' | 537 '| End of shard 0 Pending: 6.0s Duration: 1.0s Bot: swarm6 Exit: -9 ' |
| 626 '|', | 538 '|', |
| 627 '+----------------------------------------------------------------------' | 539 '+----------------------------------------------------------------------' |
| 628 '+', | 540 '+', |
| 629 'Total duration: 1.0s', | 541 'Total duration: 1.0s', |
| 630 '')) | 542 '')) |
| 631 self._check_output(expected, '') | 543 self._check_output(expected, '') |
| 632 | 544 |
| 633 def test_collect_one_missing(self): | 545 def test_collect_one_missing(self): |
| 634 data = gen_result_response(outputs=['Foo']) | 546 data = gen_result_response(output='Foo') |
| 635 data['outputs'] = ['Foo'] | 547 data['output'] = 'Foo' |
| 636 self.mock(swarming, 'yield_results', lambda *_: [(0, data)]) | 548 self.mock(swarming, 'yield_results', lambda *_: [(0, data)]) |
| 637 self.assertEqual( | 549 self.assertEqual( |
| 638 1, collect('https://localhost:1', 'name', ['10100', '10200'])) | 550 1, collect('https://localhost:1', 'name', ['10100', '10200'])) |
| 639 expected = '\n'.join(( | 551 expected = u'\n'.join(( |
| 640 '+---------------------------------------------------------------------+', | 552 '+---------------------------------------------------------------------+', |
| 641 '| Shard 0 https://localhost:1/user/task/10100 |', | 553 '| Shard 0 https://localhost:1/user/task/10100 |', |
| 642 '+---------------------------------------------------------------------+', | 554 '+---------------------------------------------------------------------+', |
| 643 'Foo', | 555 'Foo', |
| 644 '+---------------------------------------------------------------------+', | 556 '+---------------------------------------------------------------------+', |
| 645 '| End of shard 0 Pending: 6.0s Duration: 1.0s Bot: swarm6 Exit: 0 |', | 557 '| End of shard 0 Pending: 6.0s Duration: 1.0s Bot: swarm6 Exit: 0 |', |
| 646 '+---------------------------------------------------------------------+', | 558 '+---------------------------------------------------------------------+', |
| 647 '', | 559 '', |
| 648 'Total duration: 1.0s', | 560 'Total duration: 1.0s', |
| 649 '')) | 561 '')) |
| 650 self._check_output(expected, 'Results from some shards are missing: 1\n') | 562 self._check_output(expected, 'Results from some shards are missing: 1\n') |
| 651 | 563 |
| 652 def test_collect_multi(self): | 564 def test_collect_multi(self): |
| 653 actual_calls = [] | 565 actual_calls = [] |
| 654 self.mock( | 566 def fetch_isolated(isolated_hash, storage, cache, outdir, require_command): |
| 655 isolateserver, 'fetch_isolated', | 567 self.assertIs(storage.__class__, isolateserver.Storage) |
| 656 lambda *args: actual_calls.append(args)) | 568 self.assertIs(cache.__class__, isolateserver.MemoryCache) |
| 657 shards_output = [ | 569 # Ensure storage is pointing to required location. |
| 658 gen_run_isolated_out_hack_log('https://server', 'namespace', 'hash1'), | 570 self.assertEqual('https://localhost:2', storage.location) |
| 659 gen_run_isolated_out_hack_log('https://server', 'namespace', 'hash2'), | 571 self.assertEqual('default', storage.namespace) |
| 660 OUTPUT, | 572 actual_calls.append((isolated_hash, outdir, require_command)) |
| 661 ] | 573 self.mock(isolateserver, 'fetch_isolated', fetch_isolated) |
| 662 | 574 |
| 663 collector = swarming.TaskOutputCollector( | 575 collector = swarming.TaskOutputCollector(self.tempdir, 'name', 2) |
| 664 self.tempdir, 'name', len(shards_output)) | 576 for index in xrange(2): |
| 665 for index, shard_output in enumerate(shards_output): | |
| 666 collector.process_shard_result( | 577 collector.process_shard_result( |
| 667 index, gen_result_response(outputs=[shard_output])) | 578 index, |
| 579 gen_result_response( |
| 580 outputs_ref={ |
| 581 'isolated': str(index) * 40, |
| 582 'isolatedserver': 'https://localhost:2', |
| 583 'namespace': 'default', |
| 584 })) |
| 668 summary = collector.finalize() | 585 summary = collector.finalize() |
| 669 | 586 |
| 670 expected_calls = [ | 587 expected_calls = [ |
| 671 ('hash1', None, None, os.path.join(self.tempdir, '0'), False), | 588 ('0'*40, os.path.join(self.tempdir, '0'), False), |
| 672 ('hash2', None, None, os.path.join(self.tempdir, '1'), False), | 589 ('1'*40, os.path.join(self.tempdir, '1'), False), |
| 673 ] | 590 ] |
| 674 self.assertEqual(len(expected_calls), len(actual_calls)) | 591 self.assertEqual(expected_calls, actual_calls) |
| 675 storage_instances = set() | |
| 676 for expected, used in zip(expected_calls, actual_calls): | |
| 677 isolated_hash, storage, cache, outdir, require_command = used | |
| 678 storage_instances.add(storage) | |
| 679 # Compare everything but |storage| and |cache| (use None in their place). | |
| 680 self.assertEqual( | |
| 681 expected, (isolated_hash, None, None, outdir, require_command)) | |
| 682 # Ensure cache is set. | |
| 683 self.assertTrue(cache) | |
| 684 | |
| 685 # Only one instance of Storage should be used. | |
| 686 self.assertEqual(1, len(storage_instances)) | |
| 687 | |
| 688 # Ensure storage is pointing to required location. | |
| 689 storage = storage_instances.pop() | |
| 690 self.assertEqual('https://server', storage.location) | |
| 691 self.assertEqual('namespace', storage.namespace) | |
| 692 | 592 |
| 693 # Ensure collected summary is correct. | 593 # Ensure collected summary is correct. |
| 694 isolated_outs = [ | 594 outputs_refs = [ |
| 695 { | 595 { |
| 696 'hash': 'hash1', | 596 'isolated': '0'*40, |
| 697 'namespace': 'namespace', | 597 'isolatedserver': 'https://localhost:2', |
| 698 'server': 'https://server', | 598 'namespace': 'default', |
| 699 'view_url': 'https://server/browse?namespace=namespace&hash=hash1', | 599 'view_url': |
| 600 'https://localhost:2/browse?namespace=default&hash=' + '0'*40, |
| 700 }, | 601 }, |
| 701 { | 602 { |
| 702 'hash': 'hash2', | 603 'isolated': '1'*40, |
| 703 'namespace': 'namespace', | 604 'isolatedserver': 'https://localhost:2', |
| 704 'server': 'https://server', | 605 'namespace': 'default', |
| 705 'view_url': 'https://server/browse?namespace=namespace&hash=hash2', | 606 'view_url': |
| 607 'https://localhost:2/browse?namespace=default&hash=' + '1'*40, |
| 706 }, | 608 }, |
| 707 None, | |
| 708 ] | 609 ] |
| 709 expected = { | 610 expected = { |
| 710 'shards': [ | 611 'shards': [gen_result_response(outputs_ref=o) for o in outputs_refs], |
| 711 gen_result_response(isolated_out=isolated_out, outputs=[shard_output]) | |
| 712 for index, (isolated_out, shard_output) in | |
| 713 enumerate(zip(isolated_outs, shards_output)) | |
| 714 ], | |
| 715 } | 612 } |
| 716 self.assertEqual(expected, summary) | 613 self.assertEqual(expected, summary) |
| 717 | 614 |
| 718 # Ensure summary dumped to a file is correct as well. | 615 # Ensure summary dumped to a file is correct as well. |
| 719 with open(os.path.join(self.tempdir, 'summary.json'), 'r') as f: | 616 with open(os.path.join(self.tempdir, 'summary.json'), 'r') as f: |
| 720 summary_dump = json.load(f) | 617 summary_dump = json.load(f) |
| 721 self.assertEqual(expected, summary_dump) | 618 self.assertEqual(expected, summary_dump) |
| 722 | 619 |
| 723 def test_ensures_same_server(self): | 620 def test_ensures_same_server(self): |
| 724 self.mock(logging, 'error', lambda *_: None) | 621 self.mock(logging, 'error', lambda *_: None) |
| 725 # Two shard results, attempt to use different servers. | 622 # Two shard results, attempt to use different servers. |
| 726 actual_calls = [] | 623 actual_calls = [] |
| 727 self.mock( | 624 self.mock( |
| 728 isolateserver, 'fetch_isolated', | 625 isolateserver, 'fetch_isolated', |
| 729 lambda *args: actual_calls.append(args)) | 626 lambda *args: actual_calls.append(args)) |
| 730 data = [ | 627 data = [ |
| 731 gen_result_response( | 628 gen_result_response( |
| 732 outputs=[ | 629 outputs_ref={ |
| 733 gen_run_isolated_out_hack_log('https://server1', 'namespace', 'hash1') | 630 'isolatedserver': 'https://server1', |
| 734 ]), | 631 'namespace': 'namespace', |
| 632 'isolated':'hash1', |
| 633 }), |
| 735 gen_result_response( | 634 gen_result_response( |
| 736 outputs=[ | 635 outputs_ref={ |
| 737 gen_run_isolated_out_hack_log('https://server2', 'namespace', 'hash2') | 636 'isolatedserver': 'https://server2', |
| 738 ]), | 637 'namespace': 'namespace', |
| 638 'isolated':'hash1', |
| 639 }), |
| 739 ] | 640 ] |
| 740 | 641 |
| 741 # Feed them to collector. | 642 # Feed them to collector. |
| 742 collector = swarming.TaskOutputCollector(self.tempdir, 'task/name', 2) | 643 collector = swarming.TaskOutputCollector(self.tempdir, 'task/name', 2) |
| 743 for index, result in enumerate(data): | 644 for index, result in enumerate(data): |
| 744 collector.process_shard_result(index, result) | 645 collector.process_shard_result(index, result) |
| 745 collector.finalize() | 646 collector.finalize() |
| 746 | 647 |
| 747 # Only first fetch is made, second one is ignored. | 648 # Only first fetch is made, second one is ignored. |
| 748 self.assertEqual(1, len(actual_calls)) | 649 self.assertEqual(1, len(actual_calls)) |
| 749 isolated_hash, storage, _, outdir, _ = actual_calls[0] | 650 isolated_hash, storage, _, outdir, _ = actual_calls[0] |
| 750 self.assertEqual( | 651 self.assertEqual( |
| 751 ('hash1', os.path.join(self.tempdir, '0')), | 652 ('hash1', os.path.join(self.tempdir, '0')), |
| 752 (isolated_hash, outdir)) | 653 (isolated_hash, outdir)) |
| 753 self.assertEqual('https://server1', storage.location) | 654 self.assertEqual('https://server1', storage.location) |
| 754 | 655 |
| 755 def test_extract_output_files_location_ok(self): | |
| 756 task_log = '\n'.join(( | |
| 757 'some log', | |
| 758 'some more log', | |
| 759 gen_run_isolated_out_hack_log('https://fake', 'default', '12345'), | |
| 760 'more log', | |
| 761 )) | |
| 762 self.assertEqual( | |
| 763 {'hash': '12345', | |
| 764 'namespace': 'default', | |
| 765 'server': 'https://fake', | |
| 766 'view_url': 'https://fake/browse?namespace=default&hash=12345'}, | |
| 767 swarming.extract_output_files_location(task_log)) | |
| 768 | |
| 769 def test_extract_output_files_location_empty(self): | |
| 770 task_log = '\n'.join(( | |
| 771 'some log', | |
| 772 'some more log', | |
| 773 '[run_isolated_out_hack]', | |
| 774 '[/run_isolated_out_hack]', | |
| 775 )) | |
| 776 self.assertEqual( | |
| 777 None, | |
| 778 swarming.extract_output_files_location(task_log)) | |
| 779 | |
| 780 def test_extract_output_files_location_missing(self): | |
| 781 task_log = '\n'.join(( | |
| 782 'some log', | |
| 783 'some more log', | |
| 784 'more log', | |
| 785 )) | |
| 786 self.assertEqual( | |
| 787 None, | |
| 788 swarming.extract_output_files_location(task_log)) | |
| 789 | |
| 790 def test_extract_output_files_location_corrupt(self): | |
| 791 task_log = '\n'.join(( | |
| 792 'some log', | |
| 793 'some more log', | |
| 794 '[run_isolated_out_hack]', | |
| 795 '{"hash": "12345","namespace":}', | |
| 796 '[/run_isolated_out_hack]', | |
| 797 'more log', | |
| 798 )) | |
| 799 self.assertEqual( | |
| 800 None, | |
| 801 swarming.extract_output_files_location(task_log)) | |
| 802 | |
| 803 def test_extract_output_files_location_not_url(self): | |
| 804 task_log = '\n'.join(( | |
| 805 'some log', | |
| 806 'some more log', | |
| 807 gen_run_isolated_out_hack_log('/local/path', 'default', '12345'), | |
| 808 'more log', | |
| 809 )) | |
| 810 self.assertEqual( | |
| 811 None, | |
| 812 swarming.extract_output_files_location(task_log)) | |
| 813 | |
| 814 | 656 |
| 815 class TestMain(NetTestCase): | 657 class TestMain(NetTestCase): |
| 816 # Tests calling main(). | 658 # Tests calling main(). |
| 817 def test_bot_delete(self): | 659 def test_bot_delete(self): |
| 818 self.expected_requests( | 660 self.expected_requests( |
| 819 [ | 661 [ |
| 820 ( | 662 ( |
| 821 'https://localhost:1/swarming/api/v1/client/bot/foo', | 663 'https://localhost:1/_ah/api/swarming/v1/bot/foo', |
| 822 {'method': 'DELETE'}, | 664 {'method': 'DELETE'}, |
| 823 {}, | 665 {}, |
| 824 ), | 666 ), |
| 825 ]) | 667 ]) |
| 826 ret = main( | 668 ret = main( |
| 827 ['bot_delete', '--swarming', 'https://localhost:1', 'foo', '--force']) | 669 ['bot_delete', '--swarming', 'https://localhost:1', 'foo', '--force']) |
| 828 self._check_output('', '') | 670 self._check_output('', '') |
| 829 self.assertEqual(0, ret) | 671 self.assertEqual(0, ret) |
| 830 | 672 |
| 831 def test_run_raw_cmd(self): | 673 def test_run_raw_cmd(self): |
| 832 # Minimalist use. | 674 # Minimalist use. |
| 833 request = { | 675 request = { |
| 834 'name': 'None/foo=bar', | 676 'expiration_secs': 21600, |
| 677 'name': u'None/foo=bar', |
| 835 'parent_task_id': '', | 678 'parent_task_id': '', |
| 836 'priority': 100, | 679 'priority': 100, |
| 837 'properties': { | 680 'properties': { |
| 838 'commands': [['python', '-c', 'print(\'hi\')']], | 681 'command': ['python', '-c', 'print(\'hi\')'], |
| 839 'data': [], | 682 'dimensions': [ |
| 840 'dimensions': {'foo': 'bar'}, | 683 {'key': 'foo', 'value': 'bar'}, |
| 841 'env': {}, | 684 ], |
| 685 'env': [], |
| 842 'execution_timeout_secs': 3600, | 686 'execution_timeout_secs': 3600, |
| 687 'extra_args': None, |
| 688 'grace_period_secs': 30, |
| 843 'idempotent': False, | 689 'idempotent': False, |
| 690 'inputs_ref': None, |
| 844 'io_timeout_secs': 1200, | 691 'io_timeout_secs': 1200, |
| 845 }, | 692 }, |
| 846 'scheduling_expiration_secs': 21600, | |
| 847 'tags': [], | 693 'tags': [], |
| 848 'user': None, | 694 'user': None, |
| 849 } | 695 } |
| 850 result = gen_request_response(request) | 696 result = gen_request_response(request) |
| 851 self.expected_requests( | 697 self.expected_requests( |
| 852 [ | 698 [ |
| 853 ( | 699 ( |
| 854 'https://localhost:1/swarming/api/v1/client/handshake', | 700 'https://localhost:1/_ah/api/swarming/v1/tasks/new', |
| 855 {'data': {}, 'headers': {'X-XSRF-Token-Request': '1'}}, | 701 {'data': request}, |
| 856 {'server_version': 'v1', 'xsrf_token': 'Token'}, | |
| 857 ), | |
| 858 ( | |
| 859 'https://localhost:1/swarming/api/v1/client/request', | |
| 860 {'data': request, 'headers': {'X-XSRF-Token': 'Token'}}, | |
| 861 result, | 702 result, |
| 862 ), | 703 ), |
| 863 ]) | 704 ]) |
| 864 ret = main([ | 705 ret = main([ |
| 865 'trigger', | 706 'trigger', |
| 866 '--swarming', 'https://localhost:1', | 707 '--swarming', 'https://localhost:1', |
| 867 '--dimension', 'foo', 'bar', | 708 '--dimension', 'foo', 'bar', |
| 868 '--raw-cmd', | 709 '--raw-cmd', |
| 869 '--', | 710 '--', |
| 870 'python', | 711 'python', |
| 871 '-c', | 712 '-c', |
| 872 'print(\'hi\')', | 713 'print(\'hi\')', |
| 873 ]) | 714 ]) |
| 874 actual = sys.stdout.getvalue() | 715 actual = sys.stdout.getvalue() |
| 875 self.assertEqual(0, ret, (actual, sys.stderr.getvalue())) | 716 self.assertEqual(0, ret, (actual, sys.stderr.getvalue())) |
| 876 self._check_output( | 717 self._check_output( |
| 877 'Triggered task: None/foo=bar\n' | 718 'Triggered task: None/foo=bar\n' |
| 878 'To collect results, use:\n' | 719 'To collect results, use:\n' |
| 879 ' swarming.py collect -S https://localhost:1 12300\n' | 720 ' swarming.py collect -S https://localhost:1 12300\n' |
| 880 'Or visit:\n' | 721 'Or visit:\n' |
| 881 ' https://localhost:1/user/task/12300\n', | 722 ' https://localhost:1/user/task/12300\n', |
| 882 '') | 723 '') |
| 883 | 724 |
| 884 def test_run_isolated_hash(self): | 725 def test_run_isolated_hash(self): |
| 885 # pylint: disable=unused-argument | 726 # pylint: disable=unused-argument |
| 886 def isolated_upload_zip_bundle(isolate_server, bundle): | |
| 887 return 'https://localhost:1/fetch_url' | |
| 888 self.mock( | |
| 889 swarming, 'isolated_upload_zip_bundle', isolated_upload_zip_bundle) | |
| 890 self.mock(swarming, 'now', lambda: 123456) | 727 self.mock(swarming, 'now', lambda: 123456) |
| 891 | 728 |
| 892 request = gen_request_data() | 729 request = gen_request_data( |
| 730 properties={ |
| 731 'command': None, |
| 732 'inputs_ref': { |
| 733 'isolated': u'1111111111111111111111111111111111111111', |
| 734 'isolatedserver': 'https://localhost:2', |
| 735 'namespace': 'default-gzip', |
| 736 }, |
| 737 }) |
| 893 result = gen_request_response(request) | 738 result = gen_request_response(request) |
| 894 self.expected_requests( | 739 self.expected_requests( |
| 895 [ | 740 [ |
| 896 ( | 741 ( |
| 897 'https://localhost:1/swarming/api/v1/client/handshake', | 742 'https://localhost:1/_ah/api/swarming/v1/tasks/new', |
| 898 {'data': {}, 'headers': {'X-XSRF-Token-Request': '1'}}, | 743 {'data': request}, |
| 899 {'server_version': 'v1', 'xsrf_token': 'Token'}, | |
| 900 ), | |
| 901 ( | |
| 902 'https://localhost:1/swarming/api/v1/client/request', | |
| 903 {'data': request, 'headers': {'X-XSRF-Token': 'Token'}}, | |
| 904 result, | 744 result, |
| 905 ), | 745 ), |
| 906 ]) | 746 ]) |
| 907 ret = main([ | 747 ret = main([ |
| 908 'trigger', | 748 'trigger', |
| 909 '--swarming', 'https://localhost:1', | 749 '--swarming', 'https://localhost:1', |
| 910 '--isolate-server', 'https://localhost:2', | 750 '--isolate-server', 'https://localhost:2', |
| 911 '--shards', '1', | 751 '--shards', '1', |
| 912 '--priority', '101', | 752 '--priority', '101', |
| 913 '--dimension', 'foo', 'bar', | 753 '--dimension', 'foo', 'bar', |
| (...skipping 17 matching lines...) Expand all Loading... |
| 931 'To collect results, use:\n' | 771 'To collect results, use:\n' |
| 932 ' swarming.py collect -S https://localhost:1 12300\n' | 772 ' swarming.py collect -S https://localhost:1 12300\n' |
| 933 'Or visit:\n' | 773 'Or visit:\n' |
| 934 ' https://localhost:1/user/task/12300\n', | 774 ' https://localhost:1/user/task/12300\n', |
| 935 '') | 775 '') |
| 936 | 776 |
| 937 def test_run_isolated_upload_and_json(self): | 777 def test_run_isolated_upload_and_json(self): |
| 938 # pylint: disable=unused-argument | 778 # pylint: disable=unused-argument |
| 939 write_json_calls = [] | 779 write_json_calls = [] |
| 940 self.mock(tools, 'write_json', lambda *args: write_json_calls.append(args)) | 780 self.mock(tools, 'write_json', lambda *args: write_json_calls.append(args)) |
| 941 def isolated_upload_zip_bundle(isolate_server, bundle): | |
| 942 return 'https://localhost:1/fetch_url' | |
| 943 self.mock( | |
| 944 swarming, 'isolated_upload_zip_bundle', isolated_upload_zip_bundle) | |
| 945 subprocess_calls = [] | 781 subprocess_calls = [] |
| 946 self.mock(subprocess, 'call', lambda *c: subprocess_calls.append(c)) | 782 self.mock(subprocess, 'call', lambda *c: subprocess_calls.append(c)) |
| 947 self.mock(swarming, 'now', lambda: 123456) | 783 self.mock(swarming, 'now', lambda: 123456) |
| 948 | 784 |
| 949 isolated = os.path.join(self.tempdir, 'zaz.isolated') | 785 isolated = os.path.join(self.tempdir, 'zaz.isolated') |
| 950 content = '{}' | 786 content = '{}' |
| 951 with open(isolated, 'wb') as f: | 787 with open(isolated, 'wb') as f: |
| 952 f.write(content) | 788 f.write(content) |
| 953 | 789 |
| 954 isolated_hash = isolateserver_mock.hash_content(content) | 790 isolated_hash = isolateserver_mock.hash_content(content) |
| 955 request = gen_request_data( | 791 request = gen_request_data( |
| 956 isolated_hash=isolated_hash, properties=dict(idempotent=True)) | 792 properties={ |
| 793 'command': None, |
| 794 'idempotent': True, |
| 795 'inputs_ref': { |
| 796 'isolated': isolated_hash, |
| 797 'isolatedserver': 'https://localhost:2', |
| 798 'namespace': 'default-gzip', |
| 799 }, |
| 800 }) |
| 957 result = gen_request_response(request) | 801 result = gen_request_response(request) |
| 958 self.expected_requests( | 802 self.expected_requests( |
| 959 [ | 803 [ |
| 960 ( | 804 ( |
| 961 'https://localhost:1/swarming/api/v1/client/handshake', | 805 'https://localhost:1/_ah/api/swarming/v1/tasks/new', |
| 962 {'data': {}, 'headers': {'X-XSRF-Token-Request': '1'}}, | 806 {'data': request}, |
| 963 {'server_version': 'v1', 'xsrf_token': 'Token'}, | |
| 964 ), | |
| 965 ( | |
| 966 'https://localhost:1/swarming/api/v1/client/request', | |
| 967 {'data': request, 'headers': {'X-XSRF-Token': 'Token'}}, | |
| 968 result, | 807 result, |
| 969 ), | 808 ), |
| 970 ]) | 809 ]) |
| 971 ret = main([ | 810 ret = main([ |
| 972 'trigger', | 811 'trigger', |
| 973 '--swarming', 'https://localhost:1', | 812 '--swarming', 'https://localhost:1', |
| 974 '--isolate-server', 'https://localhost:2', | 813 '--isolate-server', 'https://localhost:2', |
| 975 '--shards', '1', | 814 '--shards', '1', |
| 976 '--priority', '101', | 815 '--priority', '101', |
| 977 '--dimension', 'foo', 'bar', | 816 '--dimension', 'foo', 'bar', |
| 978 '--dimension', 'os', 'Mac', | 817 '--dimension', 'os', 'Mac', |
| 979 '--expiration', '3600', | 818 '--expiration', '3600', |
| 980 '--user', 'joe@localhost', | 819 '--user', 'joe@localhost', |
| 981 '--tags', 'taga', | 820 '--tags', 'taga', |
| 982 '--tags', 'tagb', | 821 '--tags', 'tagb', |
| 983 '--hard-timeout', '60', | 822 '--hard-timeout', '60', |
| 984 '--io-timeout', '60', | 823 '--io-timeout', '60', |
| 985 '--idempotent', | 824 '--idempotent', |
| 986 '--task-name', 'unit_tests', | 825 '--task-name', 'unit_tests', |
| 987 '--dump-json', 'foo.json', | 826 '--dump-json', 'foo.json', |
| 988 isolated, | 827 isolated, |
| 989 '--', | 828 '--', |
| 990 '--some-arg', | 829 '--some-arg', |
| 991 '123', | 830 '123', |
| 992 ]) | 831 ]) |
| 993 actual = sys.stdout.getvalue() | 832 actual = sys.stdout.getvalue() |
| 994 self.assertEqual(0, ret, (actual, sys.stderr.getvalue())) | 833 self.assertEqual(0, ret, (actual, sys.stderr.getvalue())) |
| 995 expected = [ | 834 self.assertEqual([], subprocess_calls) |
| 996 ( | |
| 997 [ | |
| 998 sys.executable, | |
| 999 os.path.join(swarming.ROOT_DIR, 'isolate.py'), 'archive', | |
| 1000 '--isolate-server', 'https://localhost:2', | |
| 1001 '--namespace' ,'default-gzip', | |
| 1002 '--isolated', isolated, | |
| 1003 ], | |
| 1004 0), | |
| 1005 ] | |
| 1006 self.assertEqual(expected, subprocess_calls) | |
| 1007 self._check_output( | 835 self._check_output( |
| 1008 'Archiving: %s\n' | |
| 1009 'Triggered task: unit_tests\n' | 836 'Triggered task: unit_tests\n' |
| 1010 'To collect results, use:\n' | 837 'To collect results, use:\n' |
| 1011 ' swarming.py collect -S https://localhost:1 --json foo.json\n' | 838 ' swarming.py collect -S https://localhost:1 --json foo.json\n' |
| 1012 'Or visit:\n' | 839 'Or visit:\n' |
| 1013 ' https://localhost:1/user/task/12300\n' % isolated, | 840 ' https://localhost:1/user/task/12300\n', |
| 1014 '') | 841 '') |
| 1015 expected = [ | 842 expected = [ |
| 1016 ( | 843 ( |
| 1017 'foo.json', | 844 'foo.json', |
| 1018 { | 845 { |
| 1019 'base_task_name': 'unit_tests', | 846 'base_task_name': 'unit_tests', |
| 1020 'tasks': { | 847 'tasks': { |
| 1021 'unit_tests': { | 848 'unit_tests': { |
| 1022 'shard_index': 0, | 849 'shard_index': 0, |
| 1023 'task_id': '12300', | 850 'task_id': '12300', |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1089 '', | 916 '', |
| 1090 'Usage: swarming.py trigger [options] (hash|isolated) ' | 917 'Usage: swarming.py trigger [options] (hash|isolated) ' |
| 1091 '[-- extra_args|raw command]' | 918 '[-- extra_args|raw command]' |
| 1092 '\n\n' | 919 '\n\n' |
| 1093 'swarming.py: error: Please at least specify one --dimension\n') | 920 'swarming.py: error: Please at least specify one --dimension\n') |
| 1094 | 921 |
| 1095 def test_query_base(self): | 922 def test_query_base(self): |
| 1096 self.expected_requests( | 923 self.expected_requests( |
| 1097 [ | 924 [ |
| 1098 ( | 925 ( |
| 1099 'https://localhost:1/swarming/api/v1/client/bots/botid/tasks?' | 926 'https://localhost:1/_ah/api/swarming/v1/bot/botid/tasks?limit=200', |
| 1100 'limit=200', | |
| 1101 {}, | 927 {}, |
| 1102 {'yo': 'dawg'}, | 928 {'yo': 'dawg'}, |
| 1103 ), | 929 ), |
| 1104 ]) | 930 ]) |
| 1105 ret = main( | 931 ret = main( |
| 1106 [ | 932 [ |
| 1107 'query', '--swarming', 'https://localhost:1', 'bots/botid/tasks', | 933 'query', '--swarming', 'https://localhost:1', 'bot/botid/tasks', |
| 1108 ]) | 934 ]) |
| 1109 self._check_output('{\n "yo": "dawg"\n}\n', '') | 935 self._check_output('{\n "yo": "dawg"\n}\n', '') |
| 1110 self.assertEqual(0, ret) | 936 self.assertEqual(0, ret) |
| 1111 | 937 |
| 1112 def test_query_cursor(self): | 938 def test_query_cursor(self): |
| 1113 self.expected_requests( | 939 self.expected_requests( |
| 1114 [ | 940 [ |
| 1115 ( | 941 ( |
| 1116 'https://localhost:1/swarming/api/v1/client/bots/botid/tasks?' | 942 'https://localhost:1/_ah/api/swarming/v1/bot/botid/tasks?' |
| 1117 'limit=2', | 943 'foo=bar&limit=2', |
| 1118 {}, | 944 {}, |
| 1119 { | 945 { |
| 1120 'cursor': '%', | 946 'cursor': '%', |
| 1121 'extra': False, | 947 'extra': False, |
| 1122 'items': ['A'], | 948 'items': ['A'], |
| 1123 }, | 949 }, |
| 1124 ), | 950 ), |
| 1125 ( | 951 ( |
| 1126 'https://localhost:1/swarming/api/v1/client/bots/botid/tasks?' | 952 'https://localhost:1/_ah/api/swarming/v1/bot/botid/tasks?' |
| 1127 'cursor=%25&limit=1', | 953 'foo=bar&cursor=%25&limit=1', |
| 1128 {}, | 954 {}, |
| 1129 { | 955 { |
| 1130 'cursor': None, | 956 'cursor': None, |
| 1131 'items': ['B'], | 957 'items': ['B'], |
| 1132 'ignored': True, | 958 'ignored': True, |
| 1133 }, | 959 }, |
| 1134 ), | 960 ), |
| 1135 ]) | 961 ]) |
| 1136 ret = main( | 962 ret = main( |
| 1137 [ | 963 [ |
| 1138 'query', '--swarming', 'https://localhost:1', 'bots/botid/tasks', | 964 'query', '--swarming', 'https://localhost:1', |
| 965 'bot/botid/tasks?foo=bar', |
| 1139 '--limit', '2', | 966 '--limit', '2', |
| 1140 ]) | 967 ]) |
| 1141 expected = ( | 968 expected = ( |
| 1142 '{\n' | 969 '{\n' |
| 1143 ' "extra": false, \n' | 970 ' "extra": false, \n' |
| 1144 ' "items": [\n' | 971 ' "items": [\n' |
| 1145 ' "A", \n' | 972 ' "A", \n' |
| 1146 ' "B"\n' | 973 ' "B"\n' |
| 1147 ' ]\n' | 974 ' ]\n' |
| 1148 '}\n') | 975 '}\n') |
| (...skipping 11 matching lines...) Expand all Loading... |
| 1160 expected['aa'] = 'bb' | 987 expected['aa'] = 'bb' |
| 1161 self.assertEqual(expected, env) | 988 self.assertEqual(expected, env) |
| 1162 self.assertEqual('work', cwd) | 989 self.assertEqual('work', cwd) |
| 1163 return 0 | 990 return 0 |
| 1164 | 991 |
| 1165 self.mock(subprocess, 'call', call) | 992 self.mock(subprocess, 'call', call) |
| 1166 | 993 |
| 1167 self.expected_requests( | 994 self.expected_requests( |
| 1168 [ | 995 [ |
| 1169 ( | 996 ( |
| 1170 'https://localhost:1/swarming/api/v1/client/task/123/request', | 997 'https://localhost:1/_ah/api/swarming/v1/task/123/request', |
| 1171 {}, | 998 {}, |
| 1172 { | 999 { |
| 1173 'properties': { | 1000 'properties': { |
| 1174 'commands': [['foo']], | 1001 'command': ['foo'], |
| 1175 'data': [], | 1002 'env': [ |
| 1176 'env': {'aa': 'bb'}, | 1003 {'key': 'aa', 'value': 'bb'}, |
| 1004 ], |
| 1177 }, | 1005 }, |
| 1178 }, | 1006 }, |
| 1179 ), | 1007 ), |
| 1180 ]) | 1008 ]) |
| 1181 ret = main( | 1009 ret = main( |
| 1182 [ | 1010 [ |
| 1183 'reproduce', '--swarming', 'https://localhost:1', '123', | 1011 'reproduce', '--swarming', 'https://localhost:1', '123', |
| 1184 ]) | 1012 ]) |
| 1185 self._check_output('', '') | 1013 self._check_output('', '') |
| 1186 self.assertEqual(0, ret) | 1014 self.assertEqual(0, ret) |
| 1187 finally: | 1015 finally: |
| 1188 os.chdir(old_cwd) | 1016 os.chdir(old_cwd) |
| 1189 | 1017 |
| 1190 | 1018 |
| 1191 class TestCommandBot(NetTestCase): | 1019 class TestCommandBot(NetTestCase): |
| 1192 # Specialized test fixture for command 'bot'. | 1020 # Specialized test fixture for command 'bot'. |
| 1193 def setUp(self): | 1021 def setUp(self): |
| 1194 super(TestCommandBot, self).setUp() | 1022 super(TestCommandBot, self).setUp() |
| 1195 # Expected requests are always the same, independent of the test case. | 1023 # Expected requests are always the same, independent of the test case. |
| 1196 self.expected_requests( | 1024 self.expected_requests( |
| 1197 [ | 1025 [ |
| 1198 ( | 1026 ( |
| 1199 'https://localhost:1/swarming/api/v1/client/bots?limit=250', | 1027 'https://localhost:1/_ah/api/swarming/v1/bots/list?limit=250', |
| 1200 {}, | 1028 {}, |
| 1201 self.mock_swarming_api_v1_bots_page_1(), | 1029 self.mock_swarming_api_v1_bots_page_1(), |
| 1202 ), | 1030 ), |
| 1203 ( | 1031 ( |
| 1204 'https://localhost:1/swarming/api/v1/client/bots?limit=250&' | 1032 'https://localhost:1/_ah/api/swarming/v1/bots/list?limit=250&' |
| 1205 'cursor=opaque_cursor', | 1033 'cursor=opaque_cursor', |
| 1206 {}, | 1034 {}, |
| 1207 self.mock_swarming_api_v1_bots_page_2(), | 1035 self.mock_swarming_api_v1_bots_page_2(), |
| 1208 ), | 1036 ), |
| 1209 ]) | 1037 ]) |
| 1210 | 1038 |
| 1211 @staticmethod | 1039 @staticmethod |
| 1212 def mock_swarming_api_v1_bots_page_1(): | 1040 def mock_swarming_api_v1_bots_page_1(): |
| 1213 """Returns fake /swarming/api/v1/client/bots data.""" | 1041 """Returns fake /_ah/api/swarming/v1/bots/list data.""" |
| 1214 # Sample data retrieved from actual server. | 1042 # Sample data retrieved from actual server. |
| 1215 now = unicode(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')) | 1043 now = unicode(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')) |
| 1216 return { | 1044 return { |
| 1217 u'items': [ | 1045 u'items': [ |
| 1218 { | 1046 { |
| 1047 u'bot_id': u'swarm3', |
| 1219 u'created_ts': now, | 1048 u'created_ts': now, |
| 1220 u'dimensions': { | 1049 u'dimensions': [ |
| 1221 u'cores': u'4', | 1050 {u'key': u'cores', u'value': [u'4']}, |
| 1222 u'cpu': [u'x86', u'x86-64'], | 1051 {u'key': u'cpu', u'value': [u'x86', u'x86-64']}, |
| 1223 u'gpu': [u'15ad', u'15ad:0405'], | 1052 {u'key': u'gpu', u'value': [u'15ad', u'15ad:0405']}, |
| 1224 u'hostname': u'swarm3.example.com', | 1053 {u'key': u'id', u'value': [u'swarm3']}, |
| 1225 u'id': u'swarm3', | 1054 {u'key': u'os', u'value': [u'Mac', u'Mac-10.9']}, |
| 1226 u'os': [u'Mac', u'Mac-10.9'], | 1055 ], |
| 1227 }, | |
| 1228 u'external_ip': u'1.1.1.3', | 1056 u'external_ip': u'1.1.1.3', |
| 1229 u'hostname': u'swarm3.example.com', | 1057 u'hostname': u'swarm3.example.com', |
| 1230 u'id': u'swarm3', | |
| 1231 u'internal_ip': u'192.168.0.3', | 1058 u'internal_ip': u'192.168.0.3', |
| 1232 u'is_dead': False, | 1059 u'is_dead': False, |
| 1233 u'last_seen_ts': now, | 1060 u'last_seen_ts': now, |
| 1234 u'quarantined': False, | 1061 u'quarantined': False, |
| 1235 u'task_id': u'148569b73a89501', | 1062 u'task_id': u'148569b73a89501', |
| 1236 u'task_name': u'browser_tests', | 1063 u'task_name': u'browser_tests', |
| 1237 u'version': u'56918a2ea28a6f51751ad14cc086f118b8727905', | 1064 u'version': u'56918a2ea28a6f51751ad14cc086f118b8727905', |
| 1238 }, | 1065 }, |
| 1239 { | 1066 { |
| 1067 u'bot_id': u'swarm1', |
| 1240 u'created_ts': now, | 1068 u'created_ts': now, |
| 1241 u'dimensions': { | 1069 u'dimensions': [ |
| 1242 u'cores': u'8', | 1070 {u'key': u'cores', u'value': [u'8']}, |
| 1243 u'cpu': [u'x86', u'x86-64'], | 1071 {u'key': u'cpu', u'value': [u'x86', u'x86-64']}, |
| 1244 u'gpu': [], | 1072 {u'key': u'gpu', u'value': []}, |
| 1245 u'hostname': u'swarm1.example.com', | 1073 {u'key': u'id', u'value': [u'swarm1']}, |
| 1246 u'id': u'swarm1', | 1074 {u'key': u'os', u'value': [u'Linux', u'Linux-12.04']}, |
| 1247 u'os': [u'Linux', u'Linux-12.04'], | 1075 ], |
| 1248 }, | |
| 1249 u'external_ip': u'1.1.1.1', | 1076 u'external_ip': u'1.1.1.1', |
| 1250 u'hostname': u'swarm1.example.com', | 1077 u'hostname': u'swarm1.example.com', |
| 1251 u'id': u'swarm1', | |
| 1252 u'internal_ip': u'192.168.0.1', | 1078 u'internal_ip': u'192.168.0.1', |
| 1253 u'is_dead': True, | 1079 u'is_dead': True, |
| 1254 u'last_seen_ts': 'A long time ago', | 1080 u'last_seen_ts': 'A long time ago', |
| 1255 u'quarantined': False, | 1081 u'quarantined': False, |
| 1256 u'task_id': u'', | 1082 u'task_id': u'', |
| 1257 u'task_name': None, | 1083 u'task_name': None, |
| 1258 u'version': u'56918a2ea28a6f51751ad14cc086f118b8727905', | 1084 u'version': u'56918a2ea28a6f51751ad14cc086f118b8727905', |
| 1259 }, | 1085 }, |
| 1260 { | 1086 { |
| 1087 u'bot_id': u'swarm2', |
| 1261 u'created_ts': now, | 1088 u'created_ts': now, |
| 1262 u'dimensions': { | 1089 u'dimensions': [ |
| 1263 u'cores': u'8', | 1090 {u'key': u'cores', u'value': [u'8']}, |
| 1264 u'cpu': [u'x86', u'x86-64'], | 1091 {u'key': u'cpu', u'value': [u'x86', u'x86-64']}, |
| 1265 u'cygwin': u'0', | 1092 {u'key': u'gpu', u'value': [ |
| 1266 u'gpu': [ | |
| 1267 u'15ad', | 1093 u'15ad', |
| 1268 u'15ad:0405', | 1094 u'15ad:0405', |
| 1269 u'VMware Virtual SVGA 3D Graphics Adapter', | 1095 u'VMware Virtual SVGA 3D Graphics Adapter', |
| 1270 ], | 1096 ]}, |
| 1271 u'hostname': u'swarm2.example.com', | 1097 {u'key': u'id', u'value': [u'swarm2']}, |
| 1272 u'id': u'swarm2', | 1098 {u'key': u'os', u'value': [u'Windows', u'Windows-6.1']}, |
| 1273 u'integrity': u'high', | 1099 ], |
| 1274 u'os': [u'Windows', u'Windows-6.1'], | |
| 1275 }, | |
| 1276 u'external_ip': u'1.1.1.2', | 1100 u'external_ip': u'1.1.1.2', |
| 1277 u'hostname': u'swarm2.example.com', | 1101 u'hostname': u'swarm2.example.com', |
| 1278 u'id': u'swarm2', | |
| 1279 u'internal_ip': u'192.168.0.2', | 1102 u'internal_ip': u'192.168.0.2', |
| 1280 u'is_dead': False, | 1103 u'is_dead': False, |
| 1281 u'last_seen_ts': now, | 1104 u'last_seen_ts': now, |
| 1282 u'quarantined': False, | 1105 u'quarantined': False, |
| 1283 u'task_id': u'', | 1106 u'task_id': u'', |
| 1284 u'task_name': None, | 1107 u'task_name': None, |
| 1285 u'version': u'56918a2ea28a6f51751ad14cc086f118b8727905', | 1108 u'version': u'56918a2ea28a6f51751ad14cc086f118b8727905', |
| 1286 }, | 1109 }, |
| 1287 ], | 1110 ], |
| 1288 u'cursor': u'opaque_cursor', | 1111 u'cursor': u'opaque_cursor', |
| 1289 u'death_timeout': 1800.0, | 1112 u'death_timeout': 1800.0, |
| 1290 u'limit': 4, | 1113 u'limit': 4, |
| 1291 u'now': unicode(now), | 1114 u'now': unicode(now), |
| 1292 } | 1115 } |
| 1293 | 1116 |
| 1294 @staticmethod | 1117 @staticmethod |
| 1295 def mock_swarming_api_v1_bots_page_2(): | 1118 def mock_swarming_api_v1_bots_page_2(): |
| 1296 """Returns fake /swarming/api/v1/client/bots data.""" | 1119 """Returns fake /_ah/api/swarming/v1/bots/list data.""" |
| 1297 # Sample data retrieved from actual server. | 1120 # Sample data retrieved from actual server. |
| 1298 now = unicode(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')) | 1121 now = unicode(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')) |
| 1299 return { | 1122 return { |
| 1300 u'items': [ | 1123 u'items': [ |
| 1301 { | 1124 { |
| 1125 u'bot_id': u'swarm4', |
| 1302 u'created_ts': now, | 1126 u'created_ts': now, |
| 1303 u'dimensions': { | 1127 u'dimensions': [ |
| 1304 u'cores': u'8', | 1128 {u'key': u'cores', u'value': [u'8']}, |
| 1305 u'cpu': [u'x86', u'x86-64'], | 1129 {u'key': u'cpu', u'value': [u'x86', u'x86-64']}, |
| 1306 u'gpu': [], | 1130 {u'key': u'gpu', u'value': []}, |
| 1307 u'hostname': u'swarm4.example.com', | 1131 {u'key': u'id', u'value': [u'swarm4']}, |
| 1308 u'id': u'swarm4', | 1132 {u'key': u'os', u'value': [u'Linux', u'Linux-12.04']}, |
| 1309 u'os': [u'Linux', u'Linux-12.04'], | 1133 ], |
| 1310 }, | |
| 1311 u'external_ip': u'1.1.1.4', | 1134 u'external_ip': u'1.1.1.4', |
| 1312 u'hostname': u'swarm4.example.com', | 1135 u'hostname': u'swarm4.example.com', |
| 1313 u'id': u'swarm4', | |
| 1314 u'internal_ip': u'192.168.0.4', | 1136 u'internal_ip': u'192.168.0.4', |
| 1315 u'is_dead': False, | 1137 u'is_dead': False, |
| 1316 u'last_seen_ts': now, | 1138 u'last_seen_ts': now, |
| 1317 u'quarantined': False, | 1139 u'quarantined': False, |
| 1318 u'task_id': u'14856971a64c601', | 1140 u'task_id': u'14856971a64c601', |
| 1319 u'task_name': u'base_unittests', | 1141 u'task_name': u'base_unittests', |
| 1320 u'version': u'56918a2ea28a6f51751ad14cc086f118b8727905', | 1142 u'version': u'56918a2ea28a6f51751ad14cc086f118b8727905', |
| 1321 } | 1143 } |
| 1322 ], | 1144 ], |
| 1323 u'cursor': None, | 1145 u'cursor': None, |
| 1324 u'death_timeout': 1800.0, | 1146 u'death_timeout': 1800.0, |
| 1325 u'limit': 4, | 1147 u'limit': 4, |
| 1326 u'now': unicode(now), | 1148 u'now': unicode(now), |
| 1327 } | 1149 } |
| 1328 | 1150 |
| 1329 def test_bots(self): | 1151 def test_bots(self): |
| 1330 ret = main(['bots', '--swarming', 'https://localhost:1']) | 1152 ret = main(['bots', '--swarming', 'https://localhost:1']) |
| 1331 expected = ( | 1153 expected = ( |
| 1332 u'swarm2\n' | 1154 u'swarm2\n' |
| 1333 u' {"cores": "8", "cpu": ["x86", "x86-64"], "cygwin": "0", "gpu": ' | 1155 u' {"cores": ["8"], "cpu": ["x86", "x86-64"], "gpu": ' |
| 1334 '["15ad", "15ad:0405", "VMware Virtual SVGA 3D Graphics Adapter"], ' | 1156 '["15ad", "15ad:0405", "VMware Virtual SVGA 3D Graphics Adapter"], ' |
| 1335 '"hostname": "swarm2.example.com", "id": "swarm2", "integrity": ' | 1157 '"id": ["swarm2"], "os": ["Windows", "Windows-6.1"]}\n' |
| 1336 '"high", "os": ["Windows", "Windows-6.1"]}\n' | |
| 1337 'swarm3\n' | 1158 'swarm3\n' |
| 1338 ' {"cores": "4", "cpu": ["x86", "x86-64"], "gpu": ["15ad", ' | 1159 ' {"cores": ["4"], "cpu": ["x86", "x86-64"], "gpu": ["15ad", ' |
| 1339 '"15ad:0405"], "hostname": "swarm3.example.com", "id": "swarm3", ' | 1160 '"15ad:0405"], "id": ["swarm3"], "os": ["Mac", "Mac-10.9"]}\n' |
| 1340 '"os": ["Mac", "Mac-10.9"]}\n' | |
| 1341 u' task: 148569b73a89501\n' | 1161 u' task: 148569b73a89501\n' |
| 1342 u'swarm4\n' | 1162 u'swarm4\n' |
| 1343 u' {"cores": "8", "cpu": ["x86", "x86-64"], "gpu": [], "hostname": ' | 1163 u' {"cores": ["8"], "cpu": ["x86", "x86-64"], "gpu": [], ' |
| 1344 '"swarm4.example.com", "id": "swarm4", "os": ["Linux", ' | 1164 '"id": ["swarm4"], "os": ["Linux", "Linux-12.04"]}\n' |
| 1345 '"Linux-12.04"]}\n' | |
| 1346 u' task: 14856971a64c601\n') | 1165 u' task: 14856971a64c601\n') |
| 1347 self._check_output(expected, '') | 1166 self._check_output(expected, '') |
| 1348 self.assertEqual(0, ret) | 1167 self.assertEqual(0, ret) |
| 1349 | 1168 |
| 1350 def test_bots_bare(self): | 1169 def test_bots_bare(self): |
| 1351 ret = main(['bots', '--swarming', 'https://localhost:1', '--bare']) | 1170 ret = main(['bots', '--swarming', 'https://localhost:1', '--bare']) |
| 1352 self._check_output("swarm2\nswarm3\nswarm4\n", '') | 1171 self._check_output("swarm2\nswarm3\nswarm4\n", '') |
| 1353 self.assertEqual(0, ret) | 1172 self.assertEqual(0, ret) |
| 1354 | 1173 |
| 1355 def test_bots_filter(self): | 1174 def test_bots_filter(self): |
| 1356 ret = main( | 1175 ret = main( |
| 1357 [ | 1176 [ |
| 1358 'bots', '--swarming', 'https://localhost:1', | 1177 'bots', '--swarming', 'https://localhost:1', |
| 1359 '--dimension', 'os', 'Windows', | 1178 '--dimension', 'os', 'Windows', |
| 1360 ]) | 1179 ]) |
| 1361 expected = ( | 1180 expected = ( |
| 1362 u'swarm2\n {"cores": "8", "cpu": ["x86", "x86-64"], "cygwin": "0", ' | 1181 u'swarm2\n {"cores": ["8"], "cpu": ["x86", "x86-64"], ' |
| 1363 '"gpu": ["15ad", "15ad:0405", "VMware Virtual SVGA 3D Graphics ' | 1182 '"gpu": ["15ad", "15ad:0405", "VMware Virtual SVGA 3D Graphics ' |
| 1364 'Adapter"], "hostname": "swarm2.example.com", "id": "swarm2", ' | 1183 'Adapter"], "id": ["swarm2"], ' |
| 1365 '"integrity": "high", "os": ["Windows", "Windows-6.1"]}\n') | 1184 '"os": ["Windows", "Windows-6.1"]}\n') |
| 1366 self._check_output(expected, '') | 1185 self._check_output(expected, '') |
| 1367 self.assertEqual(0, ret) | 1186 self.assertEqual(0, ret) |
| 1368 | 1187 |
| 1369 def test_bots_filter_keep_dead(self): | 1188 def test_bots_filter_keep_dead(self): |
| 1370 ret = main( | 1189 ret = main( |
| 1371 [ | 1190 [ |
| 1372 'bots', '--swarming', 'https://localhost:1', | 1191 'bots', '--swarming', 'https://localhost:1', |
| 1373 '--dimension', 'os', 'Linux', '--keep-dead', | 1192 '--dimension', 'os', 'Linux', '--keep-dead', |
| 1374 ]) | 1193 ]) |
| 1375 expected = ( | 1194 expected = ( |
| 1376 u'swarm1\n {"cores": "8", "cpu": ["x86", "x86-64"], "gpu": [], ' | 1195 u'swarm1\n {"cores": ["8"], "cpu": ["x86", "x86-64"], "gpu": [], ' |
| 1377 '"hostname": "swarm1.example.com", "id": "swarm1", "os": ["Linux", ' | 1196 '"id": ["swarm1"], "os": ["Linux", "Linux-12.04"]}\n' |
| 1378 '"Linux-12.04"]}\n' | |
| 1379 u'swarm4\n' | 1197 u'swarm4\n' |
| 1380 u' {"cores": "8", "cpu": ["x86", "x86-64"], "gpu": [], "hostname": ' | 1198 u' {"cores": ["8"], "cpu": ["x86", "x86-64"], "gpu": [], ' |
| 1381 '"swarm4.example.com", "id": "swarm4", "os": ["Linux", ' | 1199 '"id": ["swarm4"], "os": ["Linux", "Linux-12.04"]}\n' |
| 1382 '"Linux-12.04"]}\n' | |
| 1383 u' task: 14856971a64c601\n') | 1200 u' task: 14856971a64c601\n') |
| 1384 self._check_output(expected, '') | 1201 self._check_output(expected, '') |
| 1385 self.assertEqual(0, ret) | 1202 self.assertEqual(0, ret) |
| 1386 | 1203 |
| 1387 def test_bots_filter_dead_only(self): | 1204 def test_bots_filter_dead_only(self): |
| 1388 ret = main( | 1205 ret = main( |
| 1389 [ | 1206 [ |
| 1390 'bots', '--swarming', 'https://localhost:1', | 1207 'bots', '--swarming', 'https://localhost:1', |
| 1391 '--dimension', 'os', 'Linux', '--dead-only', | 1208 '--dimension', 'os', 'Linux', '--dead-only', |
| 1392 ]) | 1209 ]) |
| 1393 expected = ( | 1210 expected = ( |
| 1394 u'swarm1\n {"cores": "8", "cpu": ["x86", "x86-64"], "gpu": [], ' | 1211 u'swarm1\n {"cores": ["8"], "cpu": ["x86", "x86-64"], "gpu": [], ' |
| 1395 '"hostname": "swarm1.example.com", "id": "swarm1", "os": ["Linux", ' | 1212 '"id": ["swarm1"], "os": ["Linux", "Linux-12.04"]}\n') |
| 1396 '"Linux-12.04"]}\n') | |
| 1397 self._check_output(expected, '') | 1213 self._check_output(expected, '') |
| 1398 self.assertEqual(0, ret) | 1214 self.assertEqual(0, ret) |
| 1399 | 1215 |
| 1400 | 1216 |
| 1401 if __name__ == '__main__': | 1217 if __name__ == '__main__': |
| 1402 logging.basicConfig( | 1218 logging.basicConfig( |
| 1403 level=logging.DEBUG if '-v' in sys.argv else logging.CRITICAL) | 1219 level=logging.DEBUG if '-v' in sys.argv else logging.CRITICAL) |
| 1404 if '-v' in sys.argv: | 1220 if '-v' in sys.argv: |
| 1405 unittest.TestCase.maxDiff = None | 1221 unittest.TestCase.maxDiff = None |
| 1406 for e in ('ISOLATE_SERVER', 'SWARMING_TASK_ID', 'SWARMING_SERVER'): | 1222 for e in ('ISOLATE_SERVER', 'SWARMING_TASK_ID', 'SWARMING_SERVER'): |
| 1407 os.environ.pop(e, None) | 1223 os.environ.pop(e, None) |
| 1408 unittest.main() | 1224 unittest.main() |
| OLD | NEW |