1e70c9a5c15708ac76ae5cde48549b93bdd6485b
[platform/framework/web/crosswalk.git] / src / tools / swarming_client / tools / run_swarming_tests_on_swarming.py
1 #!/usr/bin/env python
2 # Copyright 2012 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.
5
6 """Runs the whole set of swarming client unit tests on swarming itself.
7
8 This is done in a few steps:
9   - Archive the whole directory as a single .isolated file.
10   - Create one test-specific .isolated for each test to run. The file is created
11     directly and archived manually with isolateserver.py.
12   - Trigger each of these test-specific .isolated file per OS.
13   - Get all results out of order.
14 """
15
16 __version__ = '0.1'
17
18 import glob
19 import logging
20 import os
21 import shutil
22 import subprocess
23 import sys
24 import tempfile
25 import time
26
27 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
28
29 # Must be first import.
30 import parallel_execution
31
32 from third_party import colorama
33 from third_party.depot_tools import fix_encoding
34 from utils import tools
35
36
37 def check_output(cmd):
38   return subprocess.check_output([sys.executable] + cmd, cwd=ROOT_DIR)
39
40
41 def archive_tree(isolate_server):
42   """Archives a whole tree and return the sha1 of the .isolated file.
43
44   Manually creates a temporary isolated file and archives it.
45   """
46   cmd = [
47       'isolateserver.py', 'archive', '--isolate-server', isolate_server,
48       ROOT_DIR,
49   ]
50   if logging.getLogger().isEnabledFor(logging.INFO):
51     cmd.append('--verbose')
52   out = check_output(cmd)
53   return out.split()[0]
54
55
56 def archive_isolated_triggers(isolate_server, tree_isolated, tests):
57   """Creates and archives all the .isolated files for the tests at once.
58
59   Archiving them in one batch is faster than archiving each file individually.
60   Also the .isolated files can be reused across OSes, reducing the amount of
61   I/O.
62
63   Returns:
64     list of (test, sha1) tuples.
65   """
66   logging.info('archive_isolated_triggers(%s, %s)', tree_isolated, tests)
67   tempdir = tempfile.mkdtemp(prefix='run_swarming_tests_on_swarming_')
68   try:
69     isolateds = []
70     for test in tests:
71       test_name = os.path.basename(test)
72       # Creates a manual .isolated file. See
73       # https://code.google.com/p/swarming/wiki/IsolatedDesign for more details.
74       isolated = {
75         'algo': 'sha-1',
76         'command': ['python', test],
77         'includes': [tree_isolated],
78         'version': '1.0',
79       }
80       v = os.path.join(tempdir, test_name + '.isolated')
81       tools.write_json(v, isolated, True)
82       isolateds.append(v)
83     cmd = [
84         'isolateserver.py', 'archive', '--isolate-server', isolate_server,
85     ] + isolateds
86     if logging.getLogger().isEnabledFor(logging.INFO):
87       cmd.append('--verbose')
88     items = [i.split() for i in check_output(cmd).splitlines()]
89     assert len(items) == len(tests)
90     assert all(
91         items[i][1].endswith(os.path.basename(tests[i]) + '.isolated')
92         for i in xrange(len(tests)))
93     return zip(tests, [i[0] for i in items])
94   finally:
95     shutil.rmtree(tempdir)
96
97
98
99 def run_swarming_tests_on_swarming(
100     swarming_server, isolate_server, priority, oses, tests, logs):
101   """Archives, triggers swarming jobs and gets results."""
102   start = time.time()
103   # First, archive the whole tree.
104   tree_isolated = archive_tree(isolate_server)
105
106   # Create and archive all the .isolated files.
107   isolateds = archive_isolated_triggers(isolate_server, tree_isolated, tests)
108   logging.debug('%s', isolateds)
109   print('Archival took %3.2fs' % (time.time() - start))
110
111   exploded = []
112   for test_path, isolated_hash in isolateds:
113     test_name = os.path.basename(test_path).split('.')[0]
114     for platform in oses:
115       exploded.append((test_name, platform, isolated_hash))
116
117   tasks = [
118     (
119       parallel_execution.task_to_name(name, {'os': platform}, isolated_hash),
120       isolated_hash,
121       {'os': platform},
122     ) for name, platform, isolated_hash in exploded
123   ]
124
125   extra_args = []
126   if priority:
127     extra_args.extend(['--priority', priority])
128     print('Using priority %s' % priority)
129
130   result = 0
131   for failed_task in parallel_execution.run_swarming_tasks_parallel(
132       swarming_server, isolate_server, extra_args, tasks):
133     test_name, dimensions, stdout = failed_task
134     if logs:
135       # Write the logs are they are retrieved.
136       if not os.path.isdir(logs):
137         os.makedirs(logs)
138       name = '%s_%s.log' % (dimensions['os'], test_name.split('/', 1)[0])
139       with open(os.path.join(logs, name), 'wb') as f:
140         f.write(stdout)
141     result = 1
142   return result
143
144
145 def main():
146   parser = parallel_execution.OptionParser(
147               usage='%prog [options]', version=__version__)
148   parser.add_option(
149       '--logs',
150       help='Destination where to store the failure logs (recommended!)')
151   parser.add_option('-o', '--os', help='Run tests only on this OS')
152   parser.add_option(
153       '-t', '--test', action='append',
154       help='Run only these test, can be specified multiple times')
155   options, args = parser.parse_args()
156   if args:
157     parser.error('Unsupported argument %s' % args)
158
159   oses = ['Linux', 'Mac', 'Windows']
160   tests = [
161       os.path.relpath(i, ROOT_DIR)
162       for i in (
163       glob.glob(os.path.join(ROOT_DIR, 'tests', '*_test.py')) +
164       glob.glob(os.path.join(ROOT_DIR, 'googletest', 'tests', '*_test.py')))
165   ]
166   valid_tests = sorted(map(os.path.basename, tests))
167   assert len(valid_tests) == len(set(valid_tests)), (
168       'Can\'t have 2 tests with the same base name')
169
170   if options.test:
171     for t in options.test:
172       if not t in valid_tests:
173         parser.error(
174             '--test %s is unknown. Valid values are:\n%s' % (
175               t, '\n'.join('  ' + i for i in valid_tests)))
176     filters = tuple(os.path.sep + t for t in options.test)
177     tests = [t for t in tests if t.endswith(filters)]
178
179   if options.os:
180     if options.os not in oses:
181       parser.error(
182           '--os %s is unknown. Valid values are %s' % (
183             options.os, ', '.join(sorted(oses))))
184     oses = [options.os]
185
186   if sys.platform in ('win32', 'cygwin'):
187     # If we are on Windows, don't generate the tests for Linux and Mac since
188     # they use symlinks and we can't create symlinks on windows.
189     oses = ['Windows']
190     if options.os != 'win32':
191       print('Linux and Mac tests skipped since running on Windows.')
192
193   return run_swarming_tests_on_swarming(
194       options.swarming,
195       options.isolate_server,
196       options.priority,
197       oses,
198       tests,
199       options.logs)
200
201
202 if __name__ == '__main__':
203   fix_encoding.fix_encoding()
204   tools.disable_buffering()
205   colorama.init()
206   sys.exit(main())