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
4 # can be found in the LICENSE file.
20 # net_utils adjusts sys.path.
23 from depot_tools import auto_stub
29 from utils import zip_package
33 FILE_NAME = u'test.isolated'
35 TEST_NAME = u'unit_tests'
39 '[----------] 2 tests from StaticCookiePolicyTest\n'
40 '[ RUN ] StaticCookiePolicyTest.AllowAllCookiesTest\n'
41 '[ OK ] StaticCookiePolicyTest.AllowAllCookiesTest (0 ms)\n'
42 '[ RUN ] StaticCookiePolicyTest.BlockAllCookiesTest\n'
43 '[ OK ] StaticCookiePolicyTest.BlockAllCookiesTest (0 ms)\n'
44 '[----------] 2 tests from StaticCookiePolicyTest (0 ms total)\n'
46 '[----------] 1 test from TCPListenSocketTest\n'
47 '[ RUN ] TCPListenSocketTest.ServerSend\n'
48 '[ OK ] TCPListenSocketTest.ServerSend (1 ms)\n'
49 '[----------] 1 test from TCPListenSocketTest (1 ms total)\n')
53 '[----------] 2 tests from StaticCookiePolicyTest\n'
54 '[ RUN ] StaticCookiePolicyTest.AllowAllCookiesTest\n'
55 '[ OK ] StaticCookiePolicyTest.AllowAllCookiesTest (0 ms)\n'
56 '[ RUN ] StaticCookiePolicyTest.BlockAllCookiesTest\n'
57 'C:\\win\\build\\src\\chrome\\test.cc: error: Value of: result()\n'
60 '[ FAILED ] StaticCookiePolicyTest.BlockAllCookiesTest (0 ms)\n'
61 '[----------] 2 tests from StaticCookiePolicyTest (0 ms total)\n'
63 '[----------] 1 test from TCPListenSocketTest\n'
64 '[ RUN ] TCPListenSocketTest.ServerSend\n'
65 '[ OK ] TCPListenSocketTest.ServerSend (1 ms)\n'
66 '[----------] 1 test from TCPListenSocketTest (1 ms total)\n')
69 SWARM_OUTPUT_SUCCESS = (
70 '[ RUN ] unittests.Run Test\n' +
72 '[ OK ] unittests.Run Test (2549 ms)\n'
73 '[ RUN ] unittests.Clean Up\n'
75 '[ OK ] unittests.Clean Up (6 ms)\n'
77 '[----------] unittests summary\n'
78 '[==========] 2 tests ran. (2556 ms total)\n')
81 SWARM_OUTPUT_FAILURE = (
82 '[ RUN ] unittests.Run Test\n' +
84 '[ OK ] unittests.Run Test (2549 ms)\n'
85 '[ RUN ] unittests.Clean Up\n'
87 '[ OK ] unittests.Clean Up (6 ms)\n'
89 '[----------] unittests summary\n'
90 '[==========] 2 tests ran. (2556 ms total)\n')
93 SWARM_OUTPUT_WITH_NO_TEST_OUTPUT = (
95 'Unable to connection to swarm machine.\n')
98 TEST_SHARD_1 = 'Note: This is test shard 1 of 3.'
99 TEST_SHARD_2 = 'Note: This is test shard 2 of 3.'
100 TEST_SHARD_3 = 'Note: This is test shard 3 of 3.'
103 SWARM_SHARD_OUTPUT = (
104 '[ RUN ] unittests.Run Test\n'
106 '[ OK ] unittests.Run Test (2549 ms)\n'
107 '[ RUN ] unittests.Clean Up\n'
109 '[ OK ] unittests.Clean Up (6 ms)\n'
111 '[----------] unittests summary\n'
112 '[==========] 2 tests ran. (2556 ms total)\n')
115 TEST_SHARD_OUTPUT_1 = SWARM_SHARD_OUTPUT % TEST_SHARD_1
116 TEST_SHARD_OUTPUT_2 = SWARM_SHARD_OUTPUT % TEST_SHARD_2
117 TEST_SHARD_OUTPUT_3 = SWARM_SHARD_OUTPUT % TEST_SHARD_3
119 FAKE_BUNDLE_URL = 'http://localhost:8081/fetch_url'
122 def gen_data(shard_output, exit_codes):
124 u'config_instance_index': 0,
125 u'exit_codes': unicode(exit_codes),
126 u'machine_id': u'host',
127 u'machine_tag': u'localhost',
128 u'output': unicode(shard_output),
129 u'isolated_out': swarming.extract_output_files_location(shard_output),
133 def gen_yielded_data(index, shard_output, exit_codes):
134 """Returns an entry as it would be yielded by yield_results()."""
135 return index, gen_data(shard_output, exit_codes)
138 def generate_url_response(shard_output, exit_codes):
139 return json.dumps(gen_data(shard_output, exit_codes))
142 def get_swarm_results(keys, output_collector=None):
143 """Simplifies the call to yield_results().
145 The timeout is hard-coded to 10 seconds.
148 swarming.yield_results(
149 'http://host:9001', keys, 10., None, True, output_collector))
152 def collect(url, task_name, shards):
153 """Simplifies the call to swarming.collect()."""
154 return swarming.collect(
160 print_status_updates=True,
161 task_summary_json=None,
162 task_output_dir=None)
165 def gen_trigger_response(priority=101):
166 # As seen in services/swarming/handlers_frontend.py.
168 'priority': priority,
169 'test_case_name': 'foo',
172 'config_name': 'foo',
182 """Bypassies swarming.main()'s exception handling.
184 It gets in the way when debugging test failures.
186 dispatcher = swarming.subcommand.CommandDispatcher('swarming')
187 return dispatcher.execute(swarming.OptionParserSwarming(), args)
190 # Silence pylint 'Access to a protected member _Event of a client class'.
191 class NonBlockingEvent(threading._Event): # pylint: disable=W0212
192 """Just like threading.Event, but a class and ignores timeout in 'wait'.
194 Intended to be used as a mock for threading.Event in tests.
197 def wait(self, timeout=None):
198 return super(NonBlockingEvent, self).wait(0)
201 class TestCase(net_utils.TestCase):
202 """Base class that defines the url_open mock."""
204 super(TestCase, self).setUp()
205 self._lock = threading.Lock()
206 self.mock(swarming.auth, 'ensure_logged_in', lambda _: None)
207 self.mock(swarming.time, 'sleep', lambda _: None)
208 self.mock(swarming.subprocess, 'call', lambda *_: self.fail())
209 self.mock(swarming.threading, 'Event', NonBlockingEvent)
210 self.mock(sys, 'stdout', StringIO.StringIO())
211 self.mock(sys, 'stderr', StringIO.StringIO())
215 if not self.has_failed():
216 self._check_output('', '')
218 super(TestCase, self).tearDown()
220 def _check_output(self, out, err):
221 self.assertEqual(out, sys.stdout.getvalue())
222 self.assertEqual(err, sys.stderr.getvalue())
224 # Flush their content by mocking them again.
225 self.mock(sys, 'stdout', StringIO.StringIO())
226 self.mock(sys, 'stderr', StringIO.StringIO())
229 class TestGetTestKeys(TestCase):
230 def test_no_keys(self):
231 self.mock(swarming.time, 'sleep', lambda x: x)
232 self.expected_requests(
235 'http://host:9001/get_matching_test_cases?name=my_test',
237 'No matching Test Cases',
239 ) for _ in range(net.URL_OPEN_MAX_ATTEMPTS)
242 swarming.get_task_keys('http://host:9001', 'my_test')
244 except swarming.Failure as e:
246 'Error: Unable to find any task with the name, my_test, on swarming '
248 self.assertEqual(msg, e.args[0])
250 def test_no_keys_on_first_attempt(self):
251 self.mock(swarming.time, 'sleep', lambda x: x)
252 keys = ['key_1', 'key_2']
253 self.expected_requests(
256 'http://host:9001/get_matching_test_cases?name=my_test',
258 'No matching Test Cases',
262 'http://host:9001/get_matching_test_cases?name=my_test',
268 actual = swarming.get_task_keys('http://host:9001', 'my_test')
269 self.assertEqual(keys, actual)
271 def test_find_keys(self):
272 keys = ['key_1', 'key_2']
273 self.expected_requests(
276 'http://host:9001/get_matching_test_cases?name=my_test',
282 actual = swarming.get_task_keys('http://host:9001', 'my_test')
283 self.assertEqual(keys, actual)
286 class TestGetSwarmResults(TestCase):
287 def test_success(self):
288 self.expected_requests(
291 'http://host:9001/get_result?r=key1',
292 {'retry_404': False, 'retry_50x': False},
293 generate_url_response(SWARM_OUTPUT_SUCCESS, '0, 0'),
297 expected = [gen_yielded_data(0, SWARM_OUTPUT_SUCCESS, '0, 0')]
298 actual = get_swarm_results(['key1'])
299 self.assertEqual(expected, actual)
301 def test_failure(self):
302 self.expected_requests(
305 'http://host:9001/get_result?r=key1',
306 {'retry_404': False, 'retry_50x': False},
307 generate_url_response(SWARM_OUTPUT_FAILURE, '0, 1'),
311 expected = [gen_yielded_data(0, SWARM_OUTPUT_FAILURE, '0, 1')]
312 actual = get_swarm_results(['key1'])
313 self.assertEqual(expected, actual)
315 def test_no_test_output(self):
316 self.expected_requests(
319 'http://host:9001/get_result?r=key1',
320 {'retry_404': False, 'retry_50x': False},
321 generate_url_response(SWARM_OUTPUT_WITH_NO_TEST_OUTPUT, '0, 0'),
325 expected = [gen_yielded_data(0, SWARM_OUTPUT_WITH_NO_TEST_OUTPUT, '0, 0')]
326 actual = get_swarm_results(['key1'])
327 self.assertEqual(expected, actual)
329 def test_no_keys(self):
330 actual = get_swarm_results([])
331 self.assertEqual([], actual)
333 def test_url_errors(self):
334 self.mock(logging, 'error', lambda *_, **__: None)
335 # NOTE: get_swarm_results() hardcodes timeout=10.
337 lock = threading.Lock()
339 t = threading.current_thread()
341 return now.setdefault(t, range(10)).pop(0)
342 self.mock(swarming.net, 'sleep_before_retry', lambda _x, _y: None)
343 self.mock(swarming, 'now', get_now)
344 # The actual number of requests here depends on 'now' progressing to 10
345 # seconds. It's called once per loop. Loop makes 9 iterations.
346 self.expected_requests(
349 'http://host:9001/get_result?r=key1',
350 {'retry_404': False, 'retry_50x': False},
355 actual = get_swarm_results(['key1'])
356 self.assertEqual([], actual)
357 self.assertTrue(all(not v for v in now.itervalues()), now)
359 def test_many_shards(self):
360 self.expected_requests(
363 'http://host:9001/get_result?r=key1',
364 {'retry_404': False, 'retry_50x': False},
365 generate_url_response(TEST_SHARD_OUTPUT_1, '0, 0'),
369 'http://host:9001/get_result?r=key2',
370 {'retry_404': False, 'retry_50x': False},
371 generate_url_response(TEST_SHARD_OUTPUT_2, '0, 0'),
375 'http://host:9001/get_result?r=key3',
376 {'retry_404': False, 'retry_50x': False},
377 generate_url_response(TEST_SHARD_OUTPUT_3, '0, 0'),
382 gen_yielded_data(0, TEST_SHARD_OUTPUT_1, '0, 0'),
383 gen_yielded_data(1, TEST_SHARD_OUTPUT_2, '0, 0'),
384 gen_yielded_data(2, TEST_SHARD_OUTPUT_3, '0, 0'),
386 actual = get_swarm_results(['key1', 'key2', 'key3'])
387 self.assertEqual(expected, sorted(actual))
389 def test_output_collector_called(self):
390 # Three shards, one failed. All results are passed to output collector.
391 self.expected_requests(
394 'http://host:9001/get_result?r=key1',
395 {'retry_404': False, 'retry_50x': False},
396 generate_url_response(TEST_SHARD_OUTPUT_1, '0, 0'),
400 'http://host:9001/get_result?r=key2',
401 {'retry_404': False, 'retry_50x': False},
402 generate_url_response(TEST_SHARD_OUTPUT_2, '0, 0'),
406 'http://host:9001/get_result?r=key3',
407 {'retry_404': False, 'retry_50x': False},
408 generate_url_response(SWARM_OUTPUT_FAILURE, '0, 1'),
413 class FakeOutputCollector(object):
416 self._lock = threading.Lock()
418 def process_shard_result(self, index, result):
420 self.results.append((index, result))
422 output_collector = FakeOutputCollector()
423 get_swarm_results(['key1', 'key2', 'key3'], output_collector)
426 (0, gen_data(TEST_SHARD_OUTPUT_1, '0, 0')),
427 (1, gen_data(TEST_SHARD_OUTPUT_2, '0, 0')),
428 (2, gen_data(SWARM_OUTPUT_FAILURE, '0, 1')),
430 self.assertEqual(sorted(expected), sorted(output_collector.results))
432 def test_collect_nothing(self):
433 self.mock(swarming, 'get_task_keys', lambda *_: ['task_key'])
434 self.mock(swarming, 'yield_results', lambda *_: [])
435 self.assertEqual(1, collect('url', 'name', 2))
436 self._check_output('', 'Results from some shards are missing: 0, 1\n')
438 def test_collect_success(self):
439 self.mock(swarming, 'get_task_keys', lambda *_: ['task_key'])
441 'config_instance_index': 0,
446 self.mock(swarming, 'yield_results', lambda *_: [(0, data)])
447 self.assertEqual(0, collect('url', 'name', 1))
449 '\n================================================================\n'
450 'Begin output from shard index 0 (machine tag: 0, id: unknown)\n'
451 '================================================================\n\n'
453 '================================================================\n'
454 'End output from shard index 0 (machine tag: 0, id: unknown).\n'
455 'Exit code 0 (0x0).\n'
456 '================================================================\n\n',
459 def test_collect_fail(self):
460 self.mock(swarming, 'get_task_keys', lambda *_: ['task_key'])
462 'config_instance_index': 0,
467 self.mock(swarming, 'yield_results', lambda *_: [(0, data)])
468 self.assertEqual(1, collect('url', 'name', 1))
470 '\n================================================================\n'
471 'Begin output from shard index 0 (machine tag: 0, id: unknown)\n'
472 '================================================================\n\n'
474 '================================================================\n'
475 'End output from shard index 0 (machine tag: 0, id: unknown).\n'
476 'Exit code 8 (0x8).\n'
477 '================================================================\n\n',
480 def test_collect_negative_exit_code(self):
481 self.mock(swarming, 'get_task_keys', lambda *_: ['task_key'])
483 'config_instance_index': 0,
484 'exit_codes': '-1073741515,0',
488 self.mock(swarming, 'yield_results', lambda *_: [(0, data)])
489 self.assertEqual(1, collect('url', 'name', 1))
491 '\n================================================================\n'
492 'Begin output from shard index 0 (machine tag: 0, id: unknown)\n'
493 '================================================================\n\n'
495 '================================================================\n'
496 'End output from shard index 0 (machine tag: 0, id: unknown).\n'
497 'Exit code -1073741515 (0xc0000135).\n'
498 '================================================================\n\n',
501 def test_collect_one_missing(self):
502 self.mock(swarming, 'get_task_keys', lambda *_: ['task_key'])
504 'config_instance_index': 0,
509 self.mock(swarming, 'yield_results', lambda *_: [(0, data)])
510 self.assertEqual(1, collect('url', 'name', 2))
512 '\n================================================================\n'
513 'Begin output from shard index 0 (machine tag: 0, id: unknown)\n'
514 '================================================================\n\n'
516 '================================================================\n'
517 'End output from shard index 0 (machine tag: 0, id: unknown).\n'
518 'Exit code 0 (0x0).\n'
519 '================================================================\n\n',
520 'Results from some shards are missing: 1\n')
523 def chromium_tasks(retrieval_url, file_hash, extra_args):
527 u'python', u'run_isolated.zip',
528 u'--hash', file_hash,
529 u'--namespace', u'default-gzip',
530 u'--isolate-server', retrieval_url,
531 ] + (['--'] + list(extra_args) if extra_args else []),
532 u'decorate_output': False,
533 u'test_name': u'Run Test',
534 u'hard_time_out': 2*60*60,
538 u'python', u'swarm_cleanup.py',
540 u'decorate_output': False,
541 u'test_name': u'Clean Up',
542 u'hard_time_out': 2*60*60,
547 def generate_expected_json(
554 test_case_name=TEST_NAME,
561 u'config_name': u'isolated',
562 u'deadline_to_run': 60*60,
563 u'dimensions': dimensions,
568 u'env_vars': env.copy(),
569 u'test_case_name': test_case_name,
570 u'tests': chromium_tasks(isolate_server, file_hash, extra_args),
573 expected[u'env_vars'][u'GTEST_SHARD_INDEX'] = u'%d' % shard_index
574 expected[u'env_vars'][u'GTEST_TOTAL_SHARDS'] = u'%d' % shards
576 expected[u'tests'][0][u'action'].append(u'--verbose')
580 class MockedStorage(object):
581 def __init__(self, warm_cache):
582 self._warm_cache = warm_cache
587 def __exit__(self, *_args):
590 def upload_items(self, items):
591 return [] if self._warm_cache else items
593 def get_fetch_url(self, _item): # pylint: disable=R0201
594 return FAKE_BUNDLE_URL
597 class TriggerTaskShardsTest(TestCase):
598 def test_zip_bundle_files(self):
599 manifest = swarming.Manifest(
600 isolate_server='http://localhost:8081',
601 namespace='default-gzip',
602 isolated_hash=FILE_HASH,
606 dimensions={'os': 'Linux'},
612 bundle = zip_package.ZipPackage(swarming.ROOT_DIR)
613 swarming.setup_run_isolated(manifest, bundle)
616 set(['run_isolated.zip', 'swarm_cleanup.py']), set(bundle.files))
618 def test_basic(self):
619 manifest = swarming.Manifest(
620 isolate_server='http://localhost:8081',
621 namespace='default-gzip',
622 isolated_hash=FILE_HASH,
626 dimensions={'os': 'Linux'},
632 swarming.setup_run_isolated(manifest, None)
633 manifest_json = json.loads(manifest.to_json())
635 expected = generate_expected_json(
638 dimensions={u'os': u'Linux'},
640 isolate_server=u'http://localhost:8081',
642 self.assertEqual(expected, manifest_json)
644 def test_basic_profile(self):
645 manifest = swarming.Manifest(
646 isolate_server='http://localhost:8081',
647 namespace='default-gzip',
648 isolated_hash=FILE_HASH,
652 dimensions={'os': 'Linux'},
658 swarming.setup_run_isolated(manifest, None)
659 manifest_json = json.loads(manifest.to_json())
661 expected = generate_expected_json(
664 dimensions={u'os': u'Linux'},
666 isolate_server=u'http://localhost:8081',
668 self.assertEqual(expected, manifest_json)
670 def test_manifest_with_extra_args(self):
671 manifest = swarming.Manifest(
672 isolate_server='http://localhost:8081',
673 namespace='default-gzip',
674 isolated_hash=FILE_HASH,
676 extra_args=['--extra-cmd-arg=1234', 'some more'],
678 dimensions={'os': 'Windows'},
684 swarming.setup_run_isolated(manifest, None)
685 manifest_json = json.loads(manifest.to_json())
687 expected = generate_expected_json(
690 dimensions={u'os': u'Windows'},
692 isolate_server=u'http://localhost:8081',
694 extra_args=['--extra-cmd-arg=1234', 'some more'])
695 self.assertEqual(expected, manifest_json)
697 def test_manifest_for_shard(self):
698 manifest = swarming.Manifest(
699 isolate_server='http://localhost:8081',
700 namespace='default-gzip',
701 isolated_hash=FILE_HASH,
704 env=swarming.setup_googletest({}, 5, 3),
705 dimensions={'os': 'Linux'},
711 swarming.setup_run_isolated(manifest, None)
712 manifest_json = json.loads(manifest.to_json())
714 expected = generate_expected_json(
717 dimensions={u'os': u'Linux'},
719 isolate_server=u'http://localhost:8081',
721 self.assertEqual(expected, manifest_json)
723 def test_trigger_task_shards_success(self):
725 swarming.net, 'url_read',
726 lambda url, data=None: json.dumps(gen_trigger_response()))
727 self.mock(swarming.isolateserver, 'get_storage',
728 lambda *_: MockedStorage(warm_cache=False))
730 tasks = swarming.trigger_task_shards(
731 swarming='http://localhost:8082',
732 isolate_server='http://localhost:8081',
734 isolated_hash=FILE_HASH,
736 extra_args=['--some-arg', '123'],
748 'view_url': 'http://localhost:8082/user/task/123',
751 self.assertEqual(expected, tasks)
753 def test_trigger_task_shards_priority_override(self):
755 swarming.net, 'url_read',
756 lambda url, data=None: json.dumps(gen_trigger_response(priority=200)))
757 self.mock(swarming.isolateserver, 'get_storage',
758 lambda *_: MockedStorage(warm_cache=False))
760 tasks = swarming.trigger_task_shards(
761 swarming='http://localhost:8082',
762 isolate_server='http://localhost:8081',
764 isolated_hash=FILE_HASH,
766 extra_args=['--some-arg', '123'],
778 u'view_url': u'http://localhost:8082/user/task/123',
783 u'view_url': u'http://localhost:8082/user/task/123',
786 self.assertEqual(expected, tasks)
787 self._check_output('', 'Priority was reset to 200\n')
789 def test_trigger_task_shards_success_zip_already_uploaded(self):
791 swarming.net, 'url_read',
792 lambda url, data=None: json.dumps(gen_trigger_response()))
793 self.mock(swarming.isolateserver, 'get_storage',
794 lambda *_: MockedStorage(warm_cache=True))
796 dimensions = {'os': 'linux2'}
797 tasks = swarming.trigger_task_shards(
798 swarming='http://localhost:8082',
799 isolate_server='http://localhost:8081',
801 isolated_hash=FILE_HASH,
803 extra_args=['--some-arg', '123'],
805 dimensions=dimensions,
816 'view_url': 'http://localhost:8082/user/task/123',
819 self.assertEqual(expected, tasks)
821 def test_isolated_to_hash(self):
823 self.mock(swarming.subprocess, 'call', lambda *c: calls.append(c))
825 expected_hash = hashlib.sha1(content).hexdigest()
826 handle, isolated = tempfile.mkstemp(
827 prefix='swarming_test_', suffix='.isolated')
830 with open(isolated, 'w') as f:
832 hash_value, is_file = swarming.isolated_to_hash(
833 'http://localhost:1', 'default', isolated, hashlib.sha1, False)
836 self.assertEqual(expected_hash, hash_value)
837 self.assertEqual(True, is_file)
842 os.path.join(swarming.ROOT_DIR, 'isolate.py'),
844 '--isolate-server', 'http://localhost:1',
845 '--namespace', 'default',
852 self.assertEqual(expected_calls, calls)
853 self._check_output('Archiving: %s\n' % isolated, '')
856 class MainTest(TestCase):
858 super(MainTest, self).setUp()
864 shutil.rmtree(self._tmpdir)
866 super(MainTest, self).tearDown()
871 self._tmpdir = tempfile.mkdtemp(prefix='swarming')
874 def test_run_hash(self):
875 self.mock(swarming.isolateserver, 'get_storage',
876 lambda *_: MockedStorage(warm_cache=False))
877 self.mock(swarming, 'now', lambda: 123456)
880 '%s/foo=bar_os=Mac/1111111111111111111111111111111111111111/123456000' %
882 j = generate_expected_json(
885 dimensions={'foo': 'bar', 'os': 'Mac'},
887 isolate_server='https://host2',
889 test_case_name=task_name)
890 j['data'] = [[FAKE_BUNDLE_URL, 'swarm_data.zip']]
892 'request': json.dumps(j, sort_keys=True, separators=(',',':')),
894 self.expected_requests(
897 'https://host1/test',
899 json.dumps(gen_trigger_response()),
905 '--swarming', 'https://host1',
906 '--isolate-server', 'https://host2',
909 '--dimension', 'foo', 'bar',
910 '--dimension', 'os', 'Mac',
911 '--deadline', '3600',
914 actual = sys.stdout.getvalue()
915 self.assertEqual(0, ret, (actual, sys.stderr.getvalue()))
916 self._check_output('Triggered task: %s\n' % task_name, '')
918 def test_run_isolated(self):
919 self.mock(swarming.isolateserver, 'get_storage',
920 lambda *_: MockedStorage(warm_cache=False))
922 self.mock(swarming.subprocess, 'call', lambda *c: calls.append(c))
923 self.mock(swarming, 'now', lambda: 123456)
925 isolated = os.path.join(self.tmpdir, 'zaz.isolated')
927 with open(isolated, 'wb') as f:
930 isolated_hash = ALGO(content).hexdigest()
931 task_name = 'zaz/foo=bar_os=Mac/%s/123456000' % isolated_hash
932 j = generate_expected_json(
935 dimensions={'foo': 'bar', 'os': 'Mac'},
937 isolate_server='https://host2',
939 test_case_name=task_name,
940 file_hash=isolated_hash)
941 j['data'] = [[FAKE_BUNDLE_URL, 'swarm_data.zip']]
943 'request': json.dumps(j, sort_keys=True, separators=(',',':')),
945 self.expected_requests(
948 'https://host1/test',
950 json.dumps(gen_trigger_response()),
956 '--swarming', 'https://host1',
957 '--isolate-server', 'https://host2',
960 '--dimension', 'foo', 'bar',
961 '--dimension', 'os', 'Mac',
962 '--deadline', '3600',
965 actual = sys.stdout.getvalue()
966 self.assertEqual(0, ret, (actual, sys.stderr.getvalue()))
971 os.path.join(swarming.ROOT_DIR, 'isolate.py'), 'archive',
972 '--isolate-server', 'https://host2',
973 '--namespace' ,'default-gzip',
974 '--isolated', isolated,
978 self.assertEqual(expected, calls)
980 'Archiving: %s\nTriggered task: %s\n' % (isolated, task_name), '')
982 def test_trigger_no_request(self):
983 with self.assertRaises(SystemExit):
985 'trigger', '--swarming', 'https://host',
986 '--isolate-server', 'https://host', '-T', 'foo',
990 'Usage: swarming.py trigger [options] (hash|isolated) [-- extra_args]'
992 'swarming.py: error: Must pass one .isolated file or its hash (sha1).'
995 def test_trigger_no_env_vars(self):
996 with self.assertRaises(SystemExit):
1000 'Usage: swarming.py trigger [options] (hash|isolated) [-- extra_args]'
1002 'swarming.py: error: --swarming is required.'
1005 def test_trigger_no_swarming_env_var(self):
1006 with self.assertRaises(SystemExit):
1007 with test_utils.EnvVars({'ISOLATE_SERVER': 'https://host'}):
1008 main(['trigger', '-T' 'foo', 'foo.isolated'])
1011 'Usage: swarming.py trigger [options] (hash|isolated) [-- extra_args]'
1013 'swarming.py: error: --swarming is required.'
1016 def test_trigger_no_isolate_env_var(self):
1017 with self.assertRaises(SystemExit):
1018 with test_utils.EnvVars({'SWARMING_SERVER': 'https://host'}):
1019 main(['trigger', 'T', 'foo', 'foo.isolated'])
1022 'Usage: swarming.py trigger [options] (hash|isolated) [-- extra_args]'
1024 'swarming.py: error: Use one of --indir or --isolate-server.'
1027 def test_trigger_env_var(self):
1028 with self.assertRaises(SystemExit):
1029 with test_utils.EnvVars({'ISOLATE_SERVER': 'https://host',
1030 'SWARMING_SERVER': 'https://host'}):
1031 main(['trigger', '-T', 'foo'])
1034 'Usage: swarming.py trigger [options] (hash|isolated) [-- extra_args]'
1036 'swarming.py: error: Must pass one .isolated file or its hash (sha1).'
1039 def test_trigger_no_task(self):
1040 with self.assertRaises(SystemExit):
1042 'trigger', '--swarming', 'https://host',
1043 '--isolate-server', 'https://host', 'foo.isolated',
1047 'Usage: swarming.py trigger [options] (hash|isolated) [-- extra_args]'
1049 'swarming.py: error: Please at least specify one --dimension\n')
1051 def test_trigger_env(self):
1052 self.mock(swarming.isolateserver, 'get_storage',
1053 lambda *_: MockedStorage(warm_cache=False))
1054 j = generate_expected_json(
1057 dimensions={'os': 'Mac'},
1059 isolate_server='https://host2',
1061 j['data'] = [[FAKE_BUNDLE_URL, 'swarm_data.zip']]
1063 'request': json.dumps(j, sort_keys=True, separators=(',',':')),
1065 self.expected_requests(
1068 'https://host1/test',
1070 json.dumps(gen_trigger_response()),
1076 '--swarming', 'https://host1',
1077 '--isolate-server', 'https://host2',
1079 '--priority', '101',
1080 '--env', 'foo', 'bar',
1081 '--dimension', 'os', 'Mac',
1082 '--task-name', TEST_NAME,
1083 '--deadline', '3600',
1086 actual = sys.stdout.getvalue()
1087 self.assertEqual(0, ret, (actual, sys.stderr.getvalue()))
1089 def test_trigger_dimension_filter(self):
1090 self.mock(swarming.isolateserver, 'get_storage',
1091 lambda *_: MockedStorage(warm_cache=False))
1092 j = generate_expected_json(
1095 dimensions={'foo': 'bar', 'os': 'Mac'},
1097 isolate_server='https://host2',
1099 j['data'] = [[FAKE_BUNDLE_URL, 'swarm_data.zip']]
1101 'request': json.dumps(j, sort_keys=True, separators=(',',':')),
1103 self.expected_requests(
1106 'https://host1/test',
1108 json.dumps(gen_trigger_response()),
1114 '--swarming', 'https://host1',
1115 '--isolate-server', 'https://host2',
1117 '--priority', '101',
1118 '--dimension', 'foo', 'bar',
1119 '--dimension', 'os', 'Mac',
1120 '--task-name', TEST_NAME,
1121 '--deadline', '3600',
1124 actual = sys.stdout.getvalue()
1125 self.assertEqual(0, ret, (actual, sys.stderr.getvalue()))
1127 def test_trigger_dump_json(self):
1129 self.mock(swarming.tools, 'write_json', lambda *args: called.append(args))
1130 self.mock(swarming.isolateserver, 'get_storage',
1131 lambda *_: MockedStorage(warm_cache=False))
1132 j = generate_expected_json(
1135 dimensions={'foo': 'bar', 'os': 'Mac'},
1137 isolate_server='https://host2',
1139 j['data'] = [[FAKE_BUNDLE_URL, 'swarm_data.zip']]
1141 'request': json.dumps(j, sort_keys=True, separators=(',',':')),
1143 self.expected_requests(
1146 'https://host1/test',
1148 json.dumps(gen_trigger_response()),
1154 '--swarming', 'https://host1',
1155 '--isolate-server', 'https://host2',
1157 '--priority', '101',
1158 '--dimension', 'foo', 'bar',
1159 '--dimension', 'os', 'Mac',
1160 '--task-name', TEST_NAME,
1161 '--deadline', '3600',
1162 '--dump-json', 'foo.json',
1165 actual = sys.stdout.getvalue()
1166 self.assertEqual(0, ret, (actual, sys.stderr.getvalue()))
1171 u'base_task_name': u'unit_tests',
1176 u'view_url': u'https://host1/user/task/123',
1183 self.assertEqual(expected, called)
1185 def test_query_base(self):
1186 self.expected_requests(
1189 'https://localhost:1/swarming/api/v1/client/bots/botid/tasks?'
1197 'query', '--swarming', 'https://localhost:1', 'bots/botid/tasks',
1199 self._check_output('{\n "yo": "dawg"\n}\n', '')
1201 def test_query_cursor(self):
1202 self.expected_requests(
1205 'https://localhost:1/swarming/api/v1/client/bots/botid/tasks?'
1215 'https://localhost:1/swarming/api/v1/client/bots/botid/tasks?'
1216 'cursor=%25&limit=1',
1227 'query', '--swarming', 'https://localhost:1', 'bots/botid/tasks',
1232 ' "extra": false, \n'
1238 self._check_output(expected, '')
1241 class BotTestCase(TestCase):
1243 super(BotTestCase, self).setUp()
1244 # Expected requests are always the same, independent of the test case.
1245 self.expected_requests(
1248 'https://localhost:1/swarming/api/v1/client/bots?limit=250',
1250 self.mock_swarming_api_v1_bots_page_1(),
1253 'https://localhost:1/swarming/api/v1/client/bots?limit=250&'
1254 'cursor=opaque_cursor',
1256 self.mock_swarming_api_v1_bots_page_2(),
1261 def mock_swarming_api_v1_bots_page_1():
1262 """Returns fake /swarming/api/v1/client/bots data."""
1263 # Sample data retrieved from actual server.
1264 now = unicode(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'))
1271 u'cpu': [u'x86', u'x86-64'],
1272 u'gpu': [u'15ad', u'15ad:0405'],
1273 u'hostname': u'swarm3.example.com',
1275 u'os': [u'Mac', u'Mac-10.9'],
1277 u'external_ip': u'1.1.1.3',
1278 u'hostname': u'swarm3.example.com',
1280 u'internal_ip': u'192.168.0.3',
1282 u'last_seen_ts': now,
1283 u'quarantined': False,
1284 u'task': u'148569b73a89501',
1285 u'version': u'56918a2ea28a6f51751ad14cc086f118b8727905',
1291 u'cpu': [u'x86', u'x86-64'],
1293 u'hostname': u'swarm1.example.com',
1295 u'os': [u'Linux', u'Linux-12.04'],
1297 u'external_ip': u'1.1.1.1',
1298 u'hostname': u'swarm1.example.com',
1300 u'internal_ip': u'192.168.0.1',
1302 u'last_seen_ts': 'A long time ago',
1303 u'quarantined': False,
1305 u'version': u'56918a2ea28a6f51751ad14cc086f118b8727905',
1311 u'cpu': [u'x86', u'x86-64'],
1316 u'VMware Virtual SVGA 3D Graphics Adapter',
1318 u'hostname': u'swarm2.example.com',
1320 u'integrity': u'high',
1321 u'os': [u'Windows', u'Windows-6.1'],
1323 u'external_ip': u'1.1.1.2',
1324 u'hostname': u'swarm2.example.com',
1326 u'internal_ip': u'192.168.0.2',
1328 u'last_seen_ts': now,
1329 u'quarantined': False,
1331 u'version': u'56918a2ea28a6f51751ad14cc086f118b8727905',
1334 u'cursor': u'opaque_cursor',
1335 u'death_timeout': 1800.0,
1337 u'now': unicode(now),
1341 def mock_swarming_api_v1_bots_page_2():
1342 """Returns fake /swarming/api/v1/client/bots data."""
1343 # Sample data retrieved from actual server.
1344 now = unicode(datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S'))
1351 u'cpu': [u'x86', u'x86-64'],
1353 u'hostname': u'swarm4.example.com',
1355 u'os': [u'Linux', u'Linux-12.04'],
1357 u'external_ip': u'1.1.1.4',
1358 u'hostname': u'swarm4.example.com',
1360 u'internal_ip': u'192.168.0.4',
1362 u'last_seen_ts': now,
1363 u'quarantined': False,
1364 u'task': u'14856971a64c601',
1365 u'version': u'56918a2ea28a6f51751ad14cc086f118b8727905',
1369 u'death_timeout': 1800.0,
1371 u'now': unicode(now),
1374 def test_bots(self):
1375 main(['bots', '--swarming', 'https://localhost:1'])
1378 u' {"cores": "8", "cpu": ["x86", "x86-64"], "cygwin": "0", "gpu": '
1379 '["15ad", "15ad:0405", "VMware Virtual SVGA 3D Graphics Adapter"], '
1380 '"hostname": "swarm2.example.com", "id": "swarm2", "integrity": '
1381 '"high", "os": ["Windows", "Windows-6.1"]}\n'
1383 ' {"cores": "4", "cpu": ["x86", "x86-64"], "gpu": ["15ad", '
1384 '"15ad:0405"], "hostname": "swarm3.example.com", "id": "swarm3", '
1385 '"os": ["Mac", "Mac-10.9"]}\n'
1386 u' task: 148569b73a89501\n'
1388 u' {"cores": "8", "cpu": ["x86", "x86-64"], "gpu": [], "hostname": '
1389 '"swarm4.example.com", "id": "swarm4", "os": ["Linux", '
1391 u' task: 14856971a64c601\n')
1392 self._check_output(expected, '')
1394 def test_bots_bare(self):
1395 main(['bots', '--swarming', 'https://localhost:1', '--bare'])
1396 self._check_output("swarm2\nswarm3\nswarm4\n", '')
1398 def test_bots_filter(self):
1401 'bots', '--swarming', 'https://localhost:1',
1402 '--dimension', 'os', 'Windows',
1405 u'swarm2\n {"cores": "8", "cpu": ["x86", "x86-64"], "cygwin": "0", '
1406 '"gpu": ["15ad", "15ad:0405", "VMware Virtual SVGA 3D Graphics '
1407 'Adapter"], "hostname": "swarm2.example.com", "id": "swarm2", '
1408 '"integrity": "high", "os": ["Windows", "Windows-6.1"]}\n')
1409 self._check_output(expected, '')
1411 def test_bots_filter_keep_dead(self):
1414 'bots', '--swarming', 'https://localhost:1',
1415 '--dimension', 'os', 'Linux', '--keep-dead',
1418 u'swarm1\n {"cores": "8", "cpu": ["x86", "x86-64"], "gpu": [], '
1419 '"hostname": "swarm1.example.com", "id": "swarm1", "os": ["Linux", '
1422 u' {"cores": "8", "cpu": ["x86", "x86-64"], "gpu": [], "hostname": '
1423 '"swarm4.example.com", "id": "swarm4", "os": ["Linux", '
1425 u' task: 14856971a64c601\n')
1426 self._check_output(expected, '')
1428 def test_bots_filter_dead_only(self):
1431 'bots', '--swarming', 'https://localhost:1',
1432 '--dimension', 'os', 'Linux', '--dead-only',
1435 u'swarm1\n {"cores": "8", "cpu": ["x86", "x86-64"], "gpu": [], '
1436 '"hostname": "swarm1.example.com", "id": "swarm1", "os": ["Linux", '
1437 '"Linux-12.04"]}\n')
1438 self._check_output(expected, '')
1441 def gen_run_isolated_out_hack_log(isolate_server, namespace, isolated_hash):
1443 'hash': isolated_hash,
1444 'namespace': namespace,
1445 'storage': isolate_server,
1447 return (SWARM_OUTPUT_SUCCESS +
1448 '[run_isolated_out_hack]%s[/run_isolated_out_hack]\n' % (
1449 json.dumps(data, sort_keys=True, separators=(',',':'))))
1452 class ExtractOutputFilesLocationTest(auto_stub.TestCase):
1454 task_log = '\n'.join((
1457 gen_run_isolated_out_hack_log('https://fake', 'default', '12345'),
1462 'namespace': 'default',
1463 'server': 'https://fake',
1464 'view_url': 'https://fake/browse?namespace=default&hash=12345'},
1465 swarming.extract_output_files_location(task_log))
1467 def test_empty(self):
1468 task_log = '\n'.join((
1471 '[run_isolated_out_hack]',
1472 '[/run_isolated_out_hack]',
1476 swarming.extract_output_files_location(task_log))
1478 def test_missing(self):
1479 task_log = '\n'.join((
1486 swarming.extract_output_files_location(task_log))
1488 def test_corrupt(self):
1489 task_log = '\n'.join((
1492 '[run_isolated_out_hack]',
1493 '{"hash": "12345","namespace":}',
1494 '[/run_isolated_out_hack]',
1499 swarming.extract_output_files_location(task_log))
1501 def test_not_url(self):
1502 task_log = '\n'.join((
1505 gen_run_isolated_out_hack_log('/local/path', 'default', '12345'),
1510 swarming.extract_output_files_location(task_log))
1513 class TaskOutputCollectorTest(auto_stub.TestCase):
1515 super(TaskOutputCollectorTest, self).setUp()
1517 # Silence error log.
1518 self.mock(logging, 'error', lambda *_, **__: None)
1520 # Collect calls to 'isolateserver.fetch_isolated'.
1521 self.fetch_isolated_calls = []
1522 def fetch_isolated(isolated_hash, storage, cache, outdir, require_command):
1523 self.fetch_isolated_calls.append(
1524 (isolated_hash, storage, cache, outdir, require_command))
1525 # Ensure mock has exact same signature as the original, otherwise tests may
1526 # miss changes to real 'fetch_isolated' arg list.
1528 inspect.getargspec(swarming.isolateserver.fetch_isolated),
1529 inspect.getargspec(fetch_isolated))
1530 self.mock(swarming.isolateserver, 'fetch_isolated', fetch_isolated)
1532 # TaskOutputCollector creates directories. Put them in a temp directory.
1533 self.tempdir = tempfile.mkdtemp(prefix='swarming_test')
1536 shutil.rmtree(self.tempdir)
1537 super(TaskOutputCollectorTest, self).tearDown()
1539 def test_works(self):
1540 # Output logs of shards.
1542 gen_run_isolated_out_hack_log('https://server', 'namespace', 'hash1'),
1543 gen_run_isolated_out_hack_log('https://server', 'namespace', 'hash2'),
1544 SWARM_OUTPUT_SUCCESS,
1547 # Feed three shard results to collector, last one without output files.
1548 collector = swarming.TaskOutputCollector(
1549 self.tempdir, 'task/name', len(logs))
1550 for index, log in enumerate(logs):
1551 collector.process_shard_result(index, gen_data(log, '0, 0'))
1552 summary = collector.finalize()
1554 # Ensure it fetches the files from first two shards only.
1556 ('hash1', None, None, os.path.join(self.tempdir, '0'), False),
1557 ('hash2', None, None, os.path.join(self.tempdir, '1'), False),
1559 self.assertEqual(len(expected_calls), len(self.fetch_isolated_calls))
1560 storage_instances = set()
1561 for expected, used in zip(expected_calls, self.fetch_isolated_calls):
1562 isolated_hash, storage, cache, outdir, require_command = used
1563 storage_instances.add(storage)
1564 # Compare everything but |storage| and |cache| (use None in their place).
1566 expected, (isolated_hash, None, None, outdir, require_command))
1567 # Ensure cache is set.
1568 self.assertTrue(cache)
1570 # Only one instance of Storage should be used.
1571 self.assertEqual(1, len(storage_instances))
1573 # Ensure storage is pointing to required location.
1574 storage = storage_instances.pop()
1575 self.assertEqual('https://server', storage.location)
1576 self.assertEqual('namespace', storage.namespace)
1578 # Ensure collected summary is correct.
1579 expected_summary = {
1580 'task_name': 'task/name',
1582 gen_data(log, '0, 0') for index, log in enumerate(logs)
1585 self.assertEqual(expected_summary, summary)
1587 # Ensure summary dumped to a file is correct as well.
1588 with open(os.path.join(self.tempdir, 'summary.json'), 'r') as f:
1589 summary_dump = json.load(f)
1590 self.assertEqual(expected_summary, summary_dump)
1592 def test_ensures_same_server(self):
1593 # Two shard results, attempt to use different servers.
1596 gen_run_isolated_out_hack_log('https://server1', 'namespace', 'hash1'),
1599 gen_run_isolated_out_hack_log('https://server2', 'namespace', 'hash2'),
1603 # Feed them to collector.
1604 collector = swarming.TaskOutputCollector(self.tempdir, 'task/name', 2)
1605 for index, result in enumerate(data):
1606 collector.process_shard_result(index, result)
1607 collector.finalize()
1609 # Only first fetch is made, second one is ignored.
1610 self.assertEqual(1, len(self.fetch_isolated_calls))
1611 isolated_hash, storage, _, outdir, _ = self.fetch_isolated_calls[0]
1613 ('hash1', os.path.join(self.tempdir, '0')),
1614 (isolated_hash, outdir))
1615 self.assertEqual('https://server1', storage.location)
1618 def clear_env_vars():
1619 for e in ('ISOLATE_SERVER', 'SWARMING_SERVER'):
1620 os.environ.pop(e, None)
1623 if __name__ == '__main__':
1624 logging.basicConfig(
1625 level=logging.DEBUG if '-v' in sys.argv else logging.ERROR)
1626 if '-v' in sys.argv:
1627 unittest.TestCase.maxDiff = None