- add third_party src.
[platform/framework/web/crosswalk.git] / src / tools / swarming_client / tests / swarming_test.py
1 #!/usr/bin/env python
2 # Copyright 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 import hashlib
7 import json
8 import logging
9 import os
10 import StringIO
11 import sys
12 import threading
13 import unittest
14
15 import auto_stub
16
17 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18 sys.path.insert(0, ROOT_DIR)
19
20 import swarming
21 from utils import net
22
23
24 ALGO = hashlib.sha1
25 FILE_NAME = u'test.isolated'
26 FILE_HASH = u'1' * 40
27 TEST_NAME = u'unit_tests'
28 STDOUT_FOR_TRIGGER_LEN = 180
29
30
31 TEST_CASE_SUCCESS = (
32   '[----------] 2 tests from StaticCookiePolicyTest\n'
33   '[ RUN      ] StaticCookiePolicyTest.AllowAllCookiesTest\n'
34   '[       OK ] StaticCookiePolicyTest.AllowAllCookiesTest (0 ms)\n'
35   '[ RUN      ] StaticCookiePolicyTest.BlockAllCookiesTest\n'
36   '[       OK ] StaticCookiePolicyTest.BlockAllCookiesTest (0 ms)\n'
37   '[----------] 2 tests from StaticCookiePolicyTest (0 ms total)\n'
38   '\n'
39   '[----------] 1 test from TCPListenSocketTest\n'
40   '[ RUN      ] TCPListenSocketTest.ServerSend\n'
41   '[       OK ] TCPListenSocketTest.ServerSend (1 ms)\n'
42   '[----------] 1 test from TCPListenSocketTest (1 ms total)\n')
43
44
45 TEST_CASE_FAILURE = (
46   '[----------] 2 tests from StaticCookiePolicyTest\n'
47   '[ RUN      ] StaticCookiePolicyTest.AllowAllCookiesTest\n'
48   '[       OK ] StaticCookiePolicyTest.AllowAllCookiesTest (0 ms)\n'
49   '[ RUN      ] StaticCookiePolicyTest.BlockAllCookiesTest\n'
50   'C:\\win\\build\\src\\chrome\\test.cc: error: Value of: result()\n'
51   '  Actual: false\n'
52   'Expected: true\n'
53   '[  FAILED  ] StaticCookiePolicyTest.BlockAllCookiesTest (0 ms)\n'
54   '[----------] 2 tests from StaticCookiePolicyTest (0 ms total)\n'
55   '\n'
56   '[----------] 1 test from TCPListenSocketTest\n'
57   '[ RUN      ] TCPListenSocketTest.ServerSend\n'
58   '[       OK ] TCPListenSocketTest.ServerSend (1 ms)\n'
59   '[----------] 1 test from TCPListenSocketTest (1 ms total)\n')
60
61
62 SWARM_OUTPUT_SUCCESS = (
63   '[ RUN      ] unittests.Run Test\n' +
64   TEST_CASE_SUCCESS +
65   '[       OK ] unittests.Run Test (2549 ms)\n'
66   '[ RUN      ] unittests.Clean Up\n'
67   'No output!\n'
68   '[       OK ] unittests.Clean Up (6 ms)\n'
69   '\n'
70   '[----------] unittests summary\n'
71   '[==========] 2 tests ran. (2556 ms total)\n')
72
73
74 SWARM_OUTPUT_FAILURE = (
75   '[ RUN      ] unittests.Run Test\n' +
76   TEST_CASE_FAILURE +
77   '[       OK ] unittests.Run Test (2549 ms)\n'
78   '[ RUN      ] unittests.Clean Up\n'
79   'No output!\n'
80   '[       OK ] unittests.Clean Up (6 ms)\n'
81   '\n'
82   '[----------] unittests summary\n'
83   '[==========] 2 tests ran. (2556 ms total)\n')
84
85
86 SWARM_OUTPUT_WITH_NO_TEST_OUTPUT = (
87   '\n'
88   'Unable to connection to swarm machine.\n')
89
90
91 TEST_SHARD_1 = 'Note: This is test shard 1 of 3.'
92 TEST_SHARD_2 = 'Note: This is test shard 2 of 3.'
93 TEST_SHARD_3 = 'Note: This is test shard 3 of 3.'
94
95
96 SWARM_SHARD_OUTPUT = (
97   '[ RUN      ] unittests.Run Test\n'
98   '%s\n'
99   '[       OK ] unittests.Run Test (2549 ms)\n'
100   '[ RUN      ] unittests.Clean Up\n'
101   'No output!\n'
102   '[       OK ] unittests.Clean Up (6 ms)\n'
103   '\n'
104   '[----------] unittests summary\n'
105   '[==========] 2 tests ran. (2556 ms total)\n')
106
107
108 TEST_SHARD_OUTPUT_1 = SWARM_SHARD_OUTPUT % TEST_SHARD_1
109 TEST_SHARD_OUTPUT_2 = SWARM_SHARD_OUTPUT % TEST_SHARD_2
110 TEST_SHARD_OUTPUT_3 = SWARM_SHARD_OUTPUT % TEST_SHARD_3
111
112
113 def gen_data(index, shard_output, exit_codes):
114   return {
115     u'config_instance_index': index,
116     u'exit_codes': unicode(exit_codes),
117     u'machine_id': u'host',
118     u'machine_tag': u'localhost',
119     u'output': unicode(shard_output),
120   }
121
122
123 def gen_yielded_data(index, shard_output, exit_codes):
124   """Returns an entry as it would be yielded by yield_results()."""
125   return index, gen_data(index, shard_output, exit_codes)
126
127
128 def generate_url_response(index, shard_output, exit_codes):
129   return net.HttpResponse.get_fake_response(
130       json.dumps(gen_data(index, shard_output, exit_codes)), 'mocked_url')
131
132
133 def get_swarm_results(keys):
134   """Simplifies the call to yield_results().
135
136   The timeout is hard-coded to 10 seconds.
137   """
138   return list(swarming.yield_results('http://host:9001', keys, 10., None))
139
140
141 class TestCase(auto_stub.TestCase):
142   """Base class that defines the url_open mock."""
143   def setUp(self):
144     super(TestCase, self).setUp()
145     self._lock = threading.Lock()
146     self.requests = []
147     self.mock(swarming.net, 'url_open', self._url_open)
148
149   def tearDown(self):
150     try:
151       if not self.has_failed():
152         self.assertEqual([], self.requests)
153     finally:
154       super(TestCase, self).tearDown()
155
156   def _url_open(self, url, **kwargs):
157     logging.info('url_open(%s)', url)
158     with self._lock:
159       # Ignore 'stream' argument, it's not important for these tests.
160       kwargs.pop('stream')
161       # Since the client is multi-threaded, requests can be processed out of
162       # order.
163       for index, r in enumerate(self.requests):
164         if r[0] == url and r[1] == kwargs:
165           _, _, returned = self.requests.pop(index)
166           break
167       else:
168         self.fail('Failed to find url %s' % url)
169     return returned
170
171
172 class TestGetTestKeys(TestCase):
173   def test_no_keys(self):
174     self.mock(swarming.time, 'sleep', lambda x: x)
175     self.requests = [
176       (
177         'http://host:9001/get_matching_test_cases?name=my_test',
178         {'retry_404': True},
179         StringIO.StringIO('No matching Test Cases'),
180       ) for _ in range(net.URL_OPEN_MAX_ATTEMPTS)
181     ]
182     try:
183       swarming.get_test_keys('http://host:9001', 'my_test')
184       self.fail()
185     except swarming.Failure as e:
186       msg = (
187           'Error: Unable to find any tests with the name, my_test, on swarm '
188           'server')
189       self.assertEqual(msg, e.args[0])
190
191   def test_no_keys_on_first_attempt(self):
192     self.mock(swarming.time, 'sleep', lambda x: x)
193     keys = ['key_1', 'key_2']
194     self.requests = [
195       (
196         'http://host:9001/get_matching_test_cases?name=my_test',
197         {'retry_404': True},
198         StringIO.StringIO('No matching Test Cases'),
199       ),
200       (
201         'http://host:9001/get_matching_test_cases?name=my_test',
202         {'retry_404': True},
203         StringIO.StringIO(json.dumps(keys)),
204       ),
205     ]
206     actual = swarming.get_test_keys('http://host:9001', 'my_test')
207     self.assertEqual(keys, actual)
208
209   def test_find_keys(self):
210     keys = ['key_1', 'key_2']
211     self.requests = [
212       (
213         'http://host:9001/get_matching_test_cases?name=my_test',
214         {'retry_404': True},
215         StringIO.StringIO(json.dumps(keys)),
216       ),
217     ]
218     actual = swarming.get_test_keys('http://host:9001', 'my_test')
219     self.assertEqual(keys, actual)
220
221
222 class TestGetSwarmResults(TestCase):
223   def test_success(self):
224     self.requests = [
225       (
226         'http://host:9001/get_result?r=key1',
227         {'retry_404': False, 'retry_50x': False},
228         generate_url_response(0, SWARM_OUTPUT_SUCCESS, '0, 0'),
229       ),
230     ]
231     expected = [gen_yielded_data(0, SWARM_OUTPUT_SUCCESS, '0, 0')]
232     actual = get_swarm_results(['key1'])
233     self.assertEqual(expected, actual)
234
235   def test_failure(self):
236     self.requests = [
237       (
238         'http://host:9001/get_result?r=key1',
239         {'retry_404': False, 'retry_50x': False},
240         generate_url_response(0, SWARM_OUTPUT_FAILURE, '0, 1'),
241       ),
242     ]
243     expected = [gen_yielded_data(0, SWARM_OUTPUT_FAILURE, '0, 1')]
244     actual = get_swarm_results(['key1'])
245     self.assertEqual(expected, actual)
246
247   def test_no_test_output(self):
248     self.requests = [
249       (
250         'http://host:9001/get_result?r=key1',
251         {'retry_404': False, 'retry_50x': False},
252         generate_url_response(0, SWARM_OUTPUT_WITH_NO_TEST_OUTPUT, '0, 0'),
253       ),
254     ]
255     expected = [gen_yielded_data(0, SWARM_OUTPUT_WITH_NO_TEST_OUTPUT, '0, 0')]
256     actual = get_swarm_results(['key1'])
257     self.assertEqual(expected, actual)
258
259   def test_no_keys(self):
260     actual = get_swarm_results([])
261     self.assertEqual([], actual)
262
263   def test_url_errors(self):
264     self.mock(logging, 'error', lambda *_: None)
265     # NOTE: get_swarm_results() hardcodes timeout=10. range(12) is because of an
266     # additional time.time() call deep in net.url_open().
267     now = {}
268     lock = threading.Lock()
269     def get_now():
270       t = threading.current_thread()
271       with lock:
272         return now.setdefault(t, range(12)).pop(0)
273     self.mock(swarming.net, 'sleep_before_retry', lambda _x, _y: None)
274     self.mock(swarming, 'now', get_now)
275     # The actual number of requests here depends on 'now' progressing to 10
276     # seconds. It's called twice per loop.
277     self.requests = [
278       (
279         'http://host:9001/get_result?r=key1',
280         {'retry_404': False, 'retry_50x': False},
281         None,
282       ),
283       (
284         'http://host:9001/get_result?r=key1',
285         {'retry_404': False, 'retry_50x': False},
286         None,
287       ),
288       (
289         'http://host:9001/get_result?r=key1',
290         {'retry_404': False, 'retry_50x': False},
291         None,
292       ),
293       (
294         'http://host:9001/get_result?r=key1',
295         {'retry_404': False, 'retry_50x': False},
296         None,
297       ),
298       (
299         'http://host:9001/get_result?r=key1',
300         {'retry_404': False, 'retry_50x': False},
301         None,
302       ),
303     ]
304     actual = get_swarm_results(['key1'])
305     self.assertEqual([], actual)
306     self.assertTrue(all(not v for v in now.itervalues()), now)
307
308   def test_shard_repeated(self):
309     self.requests = [
310       (
311         'http://host:9001/get_result?r=key1',
312         {'retry_404': False, 'retry_50x': False},
313         generate_url_response(0, SWARM_OUTPUT_SUCCESS, '0, 0'),
314       ),
315       (
316         'http://host:9001/get_result?r=key1-repeat',
317         {'retry_404': False, 'retry_50x': False},
318         generate_url_response(0, SWARM_OUTPUT_SUCCESS, '0, 0'),
319       ),
320     ]
321     expected = [gen_yielded_data(0, SWARM_OUTPUT_SUCCESS, '0, 0')]
322     actual = get_swarm_results(['key1', 'key1-repeat'])
323     self.assertEqual(expected, actual)
324
325   def test_one_shard_repeated(self):
326     """Have shard 1 repeated twice, then shard 2 and 3."""
327     self.requests = [
328       (
329         'http://host:9001/get_result?r=key1',
330         {'retry_404': False, 'retry_50x': False},
331         generate_url_response(0, TEST_SHARD_OUTPUT_1, '0, 0'),
332       ),
333       (
334         'http://host:9001/get_result?r=key1-repeat',
335         {'retry_404': False, 'retry_50x': False},
336         generate_url_response(0, TEST_SHARD_OUTPUT_1, '0, 0'),
337       ),
338       (
339         'http://host:9001/get_result?r=key2',
340         {'retry_404': False, 'retry_50x': False},
341         generate_url_response(1, TEST_SHARD_OUTPUT_2, '0, 0'),
342       ),
343       (
344         'http://host:9001/get_result?r=key3',
345         {'retry_404': False, 'retry_50x': False},
346         generate_url_response(2, TEST_SHARD_OUTPUT_3, '0, 0'),
347       ),
348     ]
349     expected = [
350       gen_yielded_data(0, TEST_SHARD_OUTPUT_1, '0, 0'),
351       gen_yielded_data(1, TEST_SHARD_OUTPUT_2, '0, 0'),
352       gen_yielded_data(2, TEST_SHARD_OUTPUT_3, '0, 0'),
353     ]
354     actual = get_swarm_results(['key1', 'key1-repeat', 'key2', 'key3'])
355     self.assertEqual(expected, sorted(actual))
356
357   def test_collect_nothing(self):
358     self.mock(swarming, 'get_test_keys', lambda *_: [1, 2])
359     self.mock(swarming, 'yield_results', lambda *_: [])
360     self.assertEquals(
361         1, swarming.collect('url', 'test_name', 'timeout', 'decorate'))
362
363   def test_collect_success(self):
364     self.mock(swarming, 'get_test_keys', lambda *_: [1, 2])
365     self.mock(sys, 'stdout', StringIO.StringIO())
366     data = {
367       'config_instance_index': 0,
368       'exit_codes': '0',
369       'machine_id': 0,
370       'output': 'Foo',
371     }
372     self.mock(swarming, 'yield_results', lambda *_: [(0, data)])
373     self.assertEquals(
374         0, swarming.collect('url', 'test_name', 'timeout', 'decorate'))
375
376   def test_collect_fail(self):
377     self.mock(swarming, 'get_test_keys', lambda *_: [1, 2])
378     self.mock(sys, 'stdout', StringIO.StringIO())
379     data = {
380       'config_instance_index': 0,
381       'exit_codes': '0,8',
382       'machine_id': 0,
383       'output': 'Foo',
384     }
385     self.mock(swarming, 'yield_results', lambda *_: [(0, data)])
386     self.assertEquals(
387         8, swarming.collect('url', 'test_name', 'timeout', 'decorate'))
388
389
390 def chromium_tasks(retrieval_url):
391   return [
392     {
393       u'action': [
394         u'python', u'run_isolated.zip',
395         u'--hash', FILE_HASH,
396         u'--isolate-server', retrieval_url,
397       ],
398       u'decorate_output': False,
399       u'test_name': u'Run Test',
400       u'time_out': 600,
401     },
402     {
403       u'action' : [
404           u'python', u'swarm_cleanup.py',
405       ],
406       u'decorate_output': False,
407       u'test_name': u'Clean Up',
408       u'time_out': 600,
409     }
410   ]
411
412
413 def generate_expected_json(
414     shards,
415     slave_os,
416     working_dir,
417     isolate_server,
418     profile):
419   os_value = unicode(swarming.PLATFORM_MAPPING_SWARMING[slave_os])
420   expected = {
421     u'cleanup': u'root',
422     u'configurations': [
423       {
424         u'config_name': os_value,
425         u'dimensions': {
426           u'os': os_value,
427         },
428         u'min_instances': shards,
429         u'priority': 101,
430       },
431     ],
432     u'data': [],
433     u'env_vars': {},
434     u'restart_on_failure': True,
435     u'test_case_name': TEST_NAME,
436     u'tests': chromium_tasks(isolate_server),
437     u'working_dir': unicode(working_dir),
438   }
439   if shards > 1:
440     expected[u'env_vars'][u'GTEST_SHARD_INDEX'] = u'%(instance_index)s'
441     expected[u'env_vars'][u'GTEST_TOTAL_SHARDS'] = u'%(num_instances)s'
442   if profile:
443     expected[u'tests'][0][u'action'].append(u'--verbose')
444   return expected
445
446
447 class MockedStorage(object):
448   def __init__(self, warm_cache):
449     self._warm_cache = warm_cache
450
451   def upload_items(self, items):
452     return [] if self._warm_cache else items
453
454   def get_fetch_url(self, _digest):  # pylint: disable=R0201
455     return 'http://localhost:8081/fetch_url'
456
457
458 class ManifestTest(auto_stub.TestCase):
459   def setUp(self):
460     self.mock(swarming.time, 'sleep', lambda x: None)
461     self.mock(sys, 'stdout', StringIO.StringIO())
462     self.mock(sys, 'stderr', StringIO.StringIO())
463
464   def tearDown(self):
465     if not self.has_failed():
466       self._check_output('', '')
467     super(ManifestTest, self).tearDown()
468
469   def _check_output(self, out, err):
470     self.assertEqual(out, sys.stdout.getvalue())
471     self.assertEqual(err, sys.stderr.getvalue())
472
473     # Flush their content by mocking them again.
474     self.mock(sys, 'stdout', StringIO.StringIO())
475     self.mock(sys, 'stderr', StringIO.StringIO())
476
477   def test_basic_manifest(self):
478     manifest = swarming.Manifest(
479         isolated_hash=FILE_HASH,
480         test_name=TEST_NAME,
481         shards=2,
482         test_filter='*',
483         slave_os='Windows',
484         working_dir='swarm_tests',
485         isolate_server='http://localhost:8081',
486         verbose=False,
487         profile=False,
488         priority=101,
489         algo=ALGO)
490
491     swarming.chromium_setup(manifest)
492     manifest_json = json.loads(manifest.to_json())
493
494     expected = generate_expected_json(
495         shards=2,
496         slave_os='win32',
497         working_dir='swarm_tests',
498         isolate_server=u'http://localhost:8081',
499         profile=False)
500     self.assertEqual(expected, manifest_json)
501
502   def test_basic_linux(self):
503     """A basic linux manifest test to ensure that windows specific values
504        aren't used.
505     """
506     manifest = swarming.Manifest(
507         isolated_hash=FILE_HASH,
508         test_name=TEST_NAME,
509         shards=1,
510         test_filter='*',
511         slave_os='Linux',
512         working_dir='swarm_tests',
513         isolate_server='http://localhost:8081',
514         verbose=False,
515         profile=False,
516         priority=101,
517         algo=ALGO)
518
519     swarming.chromium_setup(manifest)
520     manifest_json = json.loads(manifest.to_json())
521
522     expected = generate_expected_json(
523         shards=1,
524         slave_os='linux2',
525         working_dir='swarm_tests',
526         isolate_server=u'http://localhost:8081',
527         profile=False)
528     self.assertEqual(expected, manifest_json)
529
530   def test_basic_linux_profile(self):
531     manifest = swarming.Manifest(
532         isolated_hash=FILE_HASH,
533         test_name=TEST_NAME,
534         shards=1,
535         test_filter='*',
536         slave_os='Linux',
537         working_dir='swarm_tests',
538         isolate_server='http://localhost:8081',
539         verbose=False,
540         profile=True,
541         priority=101,
542         algo=ALGO)
543
544     swarming.chromium_setup(manifest)
545     manifest_json = json.loads(manifest.to_json())
546
547     expected = generate_expected_json(
548         shards=1,
549         slave_os='linux2',
550         working_dir='swarm_tests',
551         isolate_server=u'http://localhost:8081',
552         profile=True)
553     self.assertEqual(expected, manifest_json)
554
555   def test_process_manifest_success(self):
556     self.mock(swarming.net, 'url_read', lambda url, data=None: '{}')
557     self.mock(swarming.isolateserver, 'get_storage',
558         lambda *_: MockedStorage(warm_cache=False))
559
560     result = swarming.process_manifest(
561         file_hash_or_isolated=FILE_HASH,
562         test_name=TEST_NAME,
563         shards=1,
564         test_filter='*',
565         slave_os='linux2',
566         working_dir='swarm_tests',
567         isolate_server='http://localhost:8081',
568         swarming='http://localhost:8082',
569         verbose=False,
570         profile=False,
571         priority=101,
572         algo=ALGO)
573     self.assertEqual(0, result)
574
575     # Just assert it printed enough, since it contains variable output.
576     out = sys.stdout.getvalue()
577     self.assertTrue(
578         len(out) > STDOUT_FOR_TRIGGER_LEN,
579         (out, sys.stderr.getvalue()))
580     self.assertTrue('Upload complete' in out)
581     self.mock(sys, 'stdout', StringIO.StringIO())
582
583   def test_process_manifest_success_zip_already_uploaded(self):
584     self.mock(swarming.net, 'url_read', lambda url, data=None: '{}')
585     self.mock(swarming.isolateserver, 'get_storage',
586         lambda *_: MockedStorage(warm_cache=True))
587
588     result = swarming.process_manifest(
589         file_hash_or_isolated=FILE_HASH,
590         test_name=TEST_NAME,
591         shards=1,
592         test_filter='*',
593         slave_os='linux2',
594         working_dir='swarm_tests',
595         isolate_server='http://localhost:8081',
596         swarming='http://localhost:8082',
597         verbose=False,
598         profile=False,
599         priority=101,
600         algo=ALGO)
601     self.assertEqual(0, result)
602
603     # Just assert it printed enough, since it contains variable output.
604     out = sys.stdout.getvalue()
605     self.assertTrue(len(out) > STDOUT_FOR_TRIGGER_LEN)
606     self.assertTrue('Zip file already on server' in out)
607     self.mock(sys, 'stdout', StringIO.StringIO())
608
609   def test_no_request(self):
610     try:
611       swarming.main([
612           'trigger', '--swarming', 'https://example.com',
613           '--isolate-server', 'https://example.com'])
614       self.fail()
615     except SystemExit as e:
616       self.assertEqual(2, e.code)
617       self._check_output(
618           '',
619           'Usage: swarming.py trigger [options]\n\n'
620           'swarming.py: error: At least one --task is required.\n')
621
622
623 if __name__ == '__main__':
624   logging.basicConfig(
625       level=logging.DEBUG if '-v' in sys.argv else logging.ERROR)
626   if '-v' in sys.argv:
627     unittest.TestCase.maxDiff = None
628   unittest.main()