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.
15 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
16 sys.path.insert(0, ROOT_DIR)
17 sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party'))
19 from depot_tools import auto_stub
25 FILE_NAME = u'test.isolated'
27 TEST_NAME = u'unit_tests'
31 '[----------] 2 tests from StaticCookiePolicyTest\n'
32 '[ RUN ] StaticCookiePolicyTest.AllowAllCookiesTest\n'
33 '[ OK ] StaticCookiePolicyTest.AllowAllCookiesTest (0 ms)\n'
34 '[ RUN ] StaticCookiePolicyTest.BlockAllCookiesTest\n'
35 '[ OK ] StaticCookiePolicyTest.BlockAllCookiesTest (0 ms)\n'
36 '[----------] 2 tests from StaticCookiePolicyTest (0 ms total)\n'
38 '[----------] 1 test from TCPListenSocketTest\n'
39 '[ RUN ] TCPListenSocketTest.ServerSend\n'
40 '[ OK ] TCPListenSocketTest.ServerSend (1 ms)\n'
41 '[----------] 1 test from TCPListenSocketTest (1 ms total)\n')
45 '[----------] 2 tests from StaticCookiePolicyTest\n'
46 '[ RUN ] StaticCookiePolicyTest.AllowAllCookiesTest\n'
47 '[ OK ] StaticCookiePolicyTest.AllowAllCookiesTest (0 ms)\n'
48 '[ RUN ] StaticCookiePolicyTest.BlockAllCookiesTest\n'
49 'C:\\win\\build\\src\\chrome\\test.cc: error: Value of: result()\n'
52 '[ FAILED ] StaticCookiePolicyTest.BlockAllCookiesTest (0 ms)\n'
53 '[----------] 2 tests from StaticCookiePolicyTest (0 ms total)\n'
55 '[----------] 1 test from TCPListenSocketTest\n'
56 '[ RUN ] TCPListenSocketTest.ServerSend\n'
57 '[ OK ] TCPListenSocketTest.ServerSend (1 ms)\n'
58 '[----------] 1 test from TCPListenSocketTest (1 ms total)\n')
61 SWARM_OUTPUT_SUCCESS = (
62 '[ RUN ] unittests.Run Test\n' +
64 '[ OK ] unittests.Run Test (2549 ms)\n'
65 '[ RUN ] unittests.Clean Up\n'
67 '[ OK ] unittests.Clean Up (6 ms)\n'
69 '[----------] unittests summary\n'
70 '[==========] 2 tests ran. (2556 ms total)\n')
73 SWARM_OUTPUT_FAILURE = (
74 '[ RUN ] unittests.Run Test\n' +
76 '[ OK ] unittests.Run Test (2549 ms)\n'
77 '[ RUN ] unittests.Clean Up\n'
79 '[ OK ] unittests.Clean Up (6 ms)\n'
81 '[----------] unittests summary\n'
82 '[==========] 2 tests ran. (2556 ms total)\n')
85 SWARM_OUTPUT_WITH_NO_TEST_OUTPUT = (
87 'Unable to connection to swarm machine.\n')
90 TEST_SHARD_1 = 'Note: This is test shard 1 of 3.'
91 TEST_SHARD_2 = 'Note: This is test shard 2 of 3.'
92 TEST_SHARD_3 = 'Note: This is test shard 3 of 3.'
95 SWARM_SHARD_OUTPUT = (
96 '[ RUN ] unittests.Run Test\n'
98 '[ OK ] unittests.Run Test (2549 ms)\n'
99 '[ RUN ] unittests.Clean Up\n'
101 '[ OK ] unittests.Clean Up (6 ms)\n'
103 '[----------] unittests summary\n'
104 '[==========] 2 tests ran. (2556 ms total)\n')
107 TEST_SHARD_OUTPUT_1 = SWARM_SHARD_OUTPUT % TEST_SHARD_1
108 TEST_SHARD_OUTPUT_2 = SWARM_SHARD_OUTPUT % TEST_SHARD_2
109 TEST_SHARD_OUTPUT_3 = SWARM_SHARD_OUTPUT % TEST_SHARD_3
112 def gen_data(index, shard_output, exit_codes):
114 u'config_instance_index': index,
115 u'exit_codes': unicode(exit_codes),
116 u'machine_id': u'host',
117 u'machine_tag': u'localhost',
118 u'output': unicode(shard_output),
122 def gen_yielded_data(index, shard_output, exit_codes):
123 """Returns an entry as it would be yielded by yield_results()."""
124 return index, gen_data(index, shard_output, exit_codes)
127 def generate_url_response(index, shard_output, exit_codes):
128 return net.HttpResponse.get_fake_response(
129 json.dumps(gen_data(index, shard_output, exit_codes)), 'mocked_url')
132 def get_swarm_results(keys):
133 """Simplifies the call to yield_results().
135 The timeout is hard-coded to 10 seconds.
137 return list(swarming.yield_results('http://host:9001', keys, 10., None))
141 """Bypassies swarming.main()'s exception handling.
143 It gets in the way when debugging test failures.
145 dispatcher = swarming.subcommand.CommandDispatcher('swarming')
146 return dispatcher.execute(swarming.OptionParserSwarming(), args)
149 class TestCase(auto_stub.TestCase):
150 """Base class that defines the url_open mock."""
152 super(TestCase, self).setUp()
153 self._lock = threading.Lock()
155 self.mock(swarming.net, 'url_open', self._url_open)
156 self.mock(swarming.time, 'sleep', lambda x: None)
157 self.mock(sys, 'stdout', StringIO.StringIO())
158 self.mock(sys, 'stderr', StringIO.StringIO())
162 if not self.has_failed():
163 self._check_output('', '')
164 self.assertEqual([], self.requests)
166 super(TestCase, self).tearDown()
168 def _check_output(self, out, err):
169 self.assertEqual(out, sys.stdout.getvalue())
170 self.assertEqual(err, sys.stderr.getvalue())
172 # Flush their content by mocking them again.
173 self.mock(sys, 'stdout', StringIO.StringIO())
174 self.mock(sys, 'stderr', StringIO.StringIO())
176 def _url_open(self, url, **kwargs):
177 logging.info('url_open(%s)', url)
179 # Ignore 'stream' argument, it's not important for these tests.
181 # Since the client is multi-threaded, requests can be processed out of
183 for index, r in enumerate(self.requests):
184 if r[0] == url and r[1] == kwargs:
185 _, _, returned = self.requests.pop(index)
189 'Failed to find url %s\n%s\nRemaining:\n%s' % (
191 json.dumps(kwargs, indent=2, sort_keys=True),
193 [(i[0], i[1]) for i in self.requests],
194 indent=2, sort_keys=True)))
198 class TestGetTestKeys(TestCase):
199 def test_no_keys(self):
200 self.mock(swarming.time, 'sleep', lambda x: x)
203 'http://host:9001/get_matching_test_cases?name=my_test',
205 StringIO.StringIO('No matching Test Cases'),
206 ) for _ in range(net.URL_OPEN_MAX_ATTEMPTS)
209 swarming.get_task_keys('http://host:9001', 'my_test')
211 except swarming.Failure as e:
213 'Error: Unable to find any task with the name, my_test, on swarming '
215 self.assertEqual(msg, e.args[0])
217 def test_no_keys_on_first_attempt(self):
218 self.mock(swarming.time, 'sleep', lambda x: x)
219 keys = ['key_1', 'key_2']
222 'http://host:9001/get_matching_test_cases?name=my_test',
224 StringIO.StringIO('No matching Test Cases'),
227 'http://host:9001/get_matching_test_cases?name=my_test',
229 StringIO.StringIO(json.dumps(keys)),
232 actual = swarming.get_task_keys('http://host:9001', 'my_test')
233 self.assertEqual(keys, actual)
235 def test_find_keys(self):
236 keys = ['key_1', 'key_2']
239 'http://host:9001/get_matching_test_cases?name=my_test',
241 StringIO.StringIO(json.dumps(keys)),
244 actual = swarming.get_task_keys('http://host:9001', 'my_test')
245 self.assertEqual(keys, actual)
248 class TestGetSwarmResults(TestCase):
249 def test_success(self):
252 'http://host:9001/get_result?r=key1',
253 {'retry_404': False, 'retry_50x': False},
254 generate_url_response(0, SWARM_OUTPUT_SUCCESS, '0, 0'),
257 expected = [gen_yielded_data(0, SWARM_OUTPUT_SUCCESS, '0, 0')]
258 actual = get_swarm_results(['key1'])
259 self.assertEqual(expected, actual)
261 def test_failure(self):
264 'http://host:9001/get_result?r=key1',
265 {'retry_404': False, 'retry_50x': False},
266 generate_url_response(0, SWARM_OUTPUT_FAILURE, '0, 1'),
269 expected = [gen_yielded_data(0, SWARM_OUTPUT_FAILURE, '0, 1')]
270 actual = get_swarm_results(['key1'])
271 self.assertEqual(expected, actual)
273 def test_no_test_output(self):
276 'http://host:9001/get_result?r=key1',
277 {'retry_404': False, 'retry_50x': False},
278 generate_url_response(0, SWARM_OUTPUT_WITH_NO_TEST_OUTPUT, '0, 0'),
281 expected = [gen_yielded_data(0, SWARM_OUTPUT_WITH_NO_TEST_OUTPUT, '0, 0')]
282 actual = get_swarm_results(['key1'])
283 self.assertEqual(expected, actual)
285 def test_no_keys(self):
286 actual = get_swarm_results([])
287 self.assertEqual([], actual)
289 def test_url_errors(self):
290 self.mock(logging, 'error', lambda *_: None)
291 # NOTE: get_swarm_results() hardcodes timeout=10. range(12) is because of an
292 # additional time.time() call deep in net.url_open().
294 lock = threading.Lock()
296 t = threading.current_thread()
298 return now.setdefault(t, range(12)).pop(0)
299 self.mock(swarming.net, 'sleep_before_retry', lambda _x, _y: None)
300 self.mock(swarming, 'now', get_now)
301 # The actual number of requests here depends on 'now' progressing to 10
302 # seconds. It's called twice per loop.
305 'http://host:9001/get_result?r=key1',
306 {'retry_404': False, 'retry_50x': False},
310 'http://host:9001/get_result?r=key1',
311 {'retry_404': False, 'retry_50x': False},
315 'http://host:9001/get_result?r=key1',
316 {'retry_404': False, 'retry_50x': False},
320 'http://host:9001/get_result?r=key1',
321 {'retry_404': False, 'retry_50x': False},
325 'http://host:9001/get_result?r=key1',
326 {'retry_404': False, 'retry_50x': False},
330 actual = get_swarm_results(['key1'])
331 self.assertEqual([], actual)
332 self.assertTrue(all(not v for v in now.itervalues()), now)
334 def test_shard_repeated(self):
337 'http://host:9001/get_result?r=key1',
338 {'retry_404': False, 'retry_50x': False},
339 generate_url_response(0, SWARM_OUTPUT_SUCCESS, '0, 0'),
342 'http://host:9001/get_result?r=key1-repeat',
343 {'retry_404': False, 'retry_50x': False},
344 generate_url_response(0, SWARM_OUTPUT_SUCCESS, '0, 0'),
347 expected = [gen_yielded_data(0, SWARM_OUTPUT_SUCCESS, '0, 0')]
348 actual = get_swarm_results(['key1', 'key1-repeat'])
349 self.assertEqual(expected, actual)
351 def test_one_shard_repeated(self):
352 """Have shard 1 repeated twice, then shard 2 and 3."""
355 'http://host:9001/get_result?r=key1',
356 {'retry_404': False, 'retry_50x': False},
357 generate_url_response(0, TEST_SHARD_OUTPUT_1, '0, 0'),
360 'http://host:9001/get_result?r=key1-repeat',
361 {'retry_404': False, 'retry_50x': False},
362 generate_url_response(0, TEST_SHARD_OUTPUT_1, '0, 0'),
365 'http://host:9001/get_result?r=key2',
366 {'retry_404': False, 'retry_50x': False},
367 generate_url_response(1, TEST_SHARD_OUTPUT_2, '0, 0'),
370 'http://host:9001/get_result?r=key3',
371 {'retry_404': False, 'retry_50x': False},
372 generate_url_response(2, TEST_SHARD_OUTPUT_3, '0, 0'),
376 gen_yielded_data(0, TEST_SHARD_OUTPUT_1, '0, 0'),
377 gen_yielded_data(1, TEST_SHARD_OUTPUT_2, '0, 0'),
378 gen_yielded_data(2, TEST_SHARD_OUTPUT_3, '0, 0'),
380 actual = get_swarm_results(['key1', 'key1-repeat', 'key2', 'key3'])
381 self.assertEqual(expected, sorted(actual))
383 def test_collect_nothing(self):
384 self.mock(swarming, 'get_task_keys', lambda *_: [1, 2])
385 self.mock(swarming, 'yield_results', lambda *_: [])
387 1, swarming.collect('url', 'test_name', 'timeout', 'decorate'))
389 def test_collect_success(self):
390 self.mock(swarming, 'get_task_keys', lambda *_: [1, 2])
392 'config_instance_index': 0,
397 self.mock(swarming, 'yield_results', lambda *_: [(0, data)])
399 0, swarming.collect('url', 'test_name', 'timeout', 'decorate'))
401 '\n================================================================\n'
402 'Begin output from shard index 0 (machine tag: 0, id: unknown)\n'
403 '================================================================\n\n'
404 'Foo================================================================\n'
405 'End output from shard index 0 (machine tag: 0, id: unknown). Return 0'
407 '================================================================\n\n',
410 def test_collect_fail(self):
411 self.mock(swarming, 'get_task_keys', lambda *_: [1, 2])
413 'config_instance_index': 0,
418 self.mock(swarming, 'yield_results', lambda *_: [(0, data)])
420 8, swarming.collect('url', 'test_name', 'timeout', 'decorate'))
422 '\n================================================================\n'
423 'Begin output from shard index 0 (machine tag: 0, id: unknown)\n'
424 '================================================================\n\n'
425 'Foo================================================================\n'
426 'End output from shard index 0 (machine tag: 0, id: unknown). Return 8'
428 '================================================================\n\n',
432 def chromium_tasks(retrieval_url):
436 u'python', u'run_isolated.zip',
437 u'--hash', FILE_HASH,
438 u'--isolate-server', retrieval_url,
440 u'decorate_output': False,
441 u'test_name': u'Run Test',
446 u'python', u'swarm_cleanup.py',
448 u'decorate_output': False,
449 u'test_name': u'Clean Up',
455 def generate_expected_json(
466 u'config_name': u'isolated',
467 u'dimensions': dimensions,
468 u'min_instances': shards,
473 u'encoding': u'UTF-8',
474 u'env_vars': env.copy(),
475 u'restart_on_failure': True,
476 u'test_case_name': TEST_NAME,
477 u'tests': chromium_tasks(isolate_server),
478 u'working_dir': unicode(working_dir),
481 expected[u'env_vars'][u'GTEST_SHARD_INDEX'] = u'%(instance_index)s'
482 expected[u'env_vars'][u'GTEST_TOTAL_SHARDS'] = u'%(num_instances)s'
484 expected[u'tests'][0][u'action'].append(u'--verbose')
488 class MockedStorage(object):
489 def __init__(self, warm_cache):
490 self._warm_cache = warm_cache
495 def __exit__(self, *_args):
498 def upload_items(self, items):
499 return [] if self._warm_cache else items
501 def get_fetch_url(self, _digest): # pylint: disable=R0201
502 return 'http://localhost:8081/fetch_url'
505 class ManifestTest(TestCase):
506 def test_basic_manifest(self):
508 u'GTEST_SHARD_INDEX': u'%(instance_index)s',
509 u'GTEST_TOTAL_SHARDS': u'%(num_instances)s',
511 dimensions = {'os': 'Windows'}
512 manifest = swarming.Manifest(
513 isolate_server='http://localhost:8081',
514 isolated_hash=FILE_HASH,
518 dimensions=dimensions,
519 working_dir='swarm_tests',
525 swarming.chromium_setup(manifest)
526 manifest_json = json.loads(manifest.to_json())
528 expected = generate_expected_json(
530 dimensions={u'os': u'Windows'},
532 working_dir='swarm_tests',
533 isolate_server=u'http://localhost:8081',
535 self.assertEqual(expected, manifest_json)
537 def test_basic_linux(self):
538 """A basic linux manifest test to ensure that windows specific values
541 dimensions = {'os': 'Linux'}
542 manifest = swarming.Manifest(
543 isolate_server='http://localhost:8081',
544 isolated_hash=FILE_HASH,
548 dimensions=dimensions,
549 working_dir='swarm_tests',
555 swarming.chromium_setup(manifest)
556 manifest_json = json.loads(manifest.to_json())
558 expected = generate_expected_json(
560 dimensions={u'os': u'Linux'},
562 working_dir='swarm_tests',
563 isolate_server=u'http://localhost:8081',
565 self.assertEqual(expected, manifest_json)
567 def test_basic_linux_profile(self):
568 dimensions = {'os': 'Linux'}
569 manifest = swarming.Manifest(
570 isolate_server='http://localhost:8081',
571 isolated_hash=FILE_HASH,
575 dimensions=dimensions,
576 working_dir='swarm_tests',
582 swarming.chromium_setup(manifest)
583 manifest_json = json.loads(manifest.to_json())
585 expected = generate_expected_json(
587 dimensions={u'os': u'Linux'},
589 working_dir='swarm_tests',
590 isolate_server=u'http://localhost:8081',
592 self.assertEqual(expected, manifest_json)
594 def test_process_manifest_success(self):
595 self.mock(swarming.net, 'url_read', lambda url, data=None: '{}')
596 self.mock(swarming.isolateserver, 'get_storage',
597 lambda *_: MockedStorage(warm_cache=False))
599 result = swarming.process_manifest(
600 swarming='http://localhost:8082',
601 isolate_server='http://localhost:8081',
602 isolated_hash=FILE_HASH,
607 working_dir='swarm_tests',
612 self.assertEqual(0, result)
614 def test_process_manifest_success_zip_already_uploaded(self):
615 self.mock(swarming.net, 'url_read', lambda url, data=None: '{}')
616 self.mock(swarming.isolateserver, 'get_storage',
617 lambda *_: MockedStorage(warm_cache=True))
619 dimensions = {'os': 'linux2'}
620 result = swarming.process_manifest(
621 swarming='http://localhost:8082',
622 isolate_server='http://localhost:8081',
623 isolated_hash=FILE_HASH,
626 dimensions=dimensions,
628 working_dir='swarm_tests',
633 self.assertEqual(0, result)
636 class MainTest(TestCase):
637 def test_trigger_no_request(self):
638 with self.assertRaises(SystemExit):
640 'trigger', '--swarming', 'https://host',
641 '--isolate-server', 'https://host',
645 'Usage: swarming.py trigger [options] (hash|isolated)\n\n'
646 'swarming.py: error: Must pass one .isolated file or its hash (sha1).'
649 def test_trigger_env(self):
650 self.mock(swarming.isolateserver, 'get_storage',
651 lambda *_: MockedStorage(warm_cache=False))
652 j = generate_expected_json(
654 dimensions={'os': 'Mac'},
656 working_dir='swarm_tests',
657 isolate_server='https://host2',
659 j['data'] = [['http://localhost:8081/fetch_url', 'swarm_data.zip']]
661 'request': json.dumps(j, sort_keys=True, separators=(',',':')),
665 'https://host1/test',
667 # The actual output is ignored as long as it is valid json.
668 StringIO.StringIO('{}'),
673 '--swarming', 'https://host1',
674 '--isolate-server', 'https://host2',
677 '--env', 'foo', 'bar',
678 '--dimension', 'os', 'Mac',
679 '--task-name', TEST_NAME,
682 actual = sys.stdout.getvalue()
683 self.assertEqual(0, ret, (actual, sys.stderr.getvalue()))
685 def test_trigger_dimension_filter(self):
686 self.mock(swarming.isolateserver, 'get_storage',
687 lambda *_: MockedStorage(warm_cache=False))
688 j = generate_expected_json(
690 dimensions={'foo': 'bar', 'os': 'Mac'},
692 working_dir='swarm_tests',
693 isolate_server='https://host2',
695 j['data'] = [['http://localhost:8081/fetch_url', 'swarm_data.zip']]
697 'request': json.dumps(j, sort_keys=True, separators=(',',':')),
701 'https://host1/test',
703 # The actual output is ignored as long as it is valid json.
704 StringIO.StringIO('{}'),
709 '--swarming', 'https://host1',
710 '--isolate-server', 'https://host2',
713 '--dimension', 'foo', 'bar',
714 '--dimension', 'os', 'Mac',
715 '--task-name', TEST_NAME,
718 actual = sys.stdout.getvalue()
719 self.assertEqual(0, ret, (actual, sys.stderr.getvalue()))
722 if __name__ == '__main__':
724 level=logging.DEBUG if '-v' in sys.argv else logging.ERROR)
726 unittest.TestCase.maxDiff = None