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 |