Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / test_runner.py
1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Parses the command line, discovers the appropriate tests, and runs them.
6
7 Handles test configuration, but all the logic for
8 actually running the test is in Test and PageRunner."""
9
10 import hashlib
11 import inspect
12 import json
13 import os
14 import sys
15
16 from telemetry import benchmark
17 from telemetry import decorators
18 from telemetry.core import browser_finder
19 from telemetry.core import browser_options
20 from telemetry.core import command_line
21 from telemetry.core import discover
22 from telemetry.core import environment
23 from telemetry.core import util
24 from telemetry.page import page_set
25 from telemetry.page import page_test
26 from telemetry.page import profile_creator
27 from telemetry.util import find_dependencies
28
29
30 class Deps(find_dependencies.FindDependenciesCommand):
31   """Prints all dependencies"""
32
33   def Run(self, args):
34     main_module = sys.modules['__main__']
35     args.positional_args.append(os.path.realpath(main_module.__file__))
36     return super(Deps, self).Run(args)
37
38
39 class Help(command_line.OptparseCommand):
40   """Display help information about a command"""
41
42   usage = '[command]'
43
44   def Run(self, args):
45     if len(args.positional_args) == 1:
46       commands = _MatchingCommands(args.positional_args[0])
47       if len(commands) == 1:
48         command = commands[0]
49         parser = command.CreateParser()
50         command.AddCommandLineArgs(parser)
51         parser.print_help()
52         return 0
53
54     print >> sys.stderr, ('usage: %s [command] [<options>]' % _ScriptName())
55     print >> sys.stderr, 'Available commands are:'
56     for command in _Commands():
57       print >> sys.stderr, '  %-10s %s' % (
58           command.Name(), command.Description())
59     print >> sys.stderr, ('"%s help <command>" to see usage information '
60                           'for a specific command.' % _ScriptName())
61     return 0
62
63
64 class List(command_line.OptparseCommand):
65   """Lists the available tests"""
66
67   usage = '[test_name] [<options>]'
68
69   @classmethod
70   def CreateParser(cls):
71     options = browser_options.BrowserFinderOptions()
72     parser = options.CreateParser('%%prog %s %s' % (cls.Name(), cls.usage))
73     return parser
74
75   @classmethod
76   def AddCommandLineArgs(cls, parser):
77     parser.add_option('-j', '--json-output-file', type='string')
78     parser.add_option('-n', '--num-shards', type='int', default=1)
79
80   @classmethod
81   def ProcessCommandLineArgs(cls, parser, args):
82     if not args.positional_args:
83       args.tests = _Tests()
84     elif len(args.positional_args) == 1:
85       args.tests = _MatchTestName(args.positional_args[0], exact_matches=False)
86     else:
87       parser.error('Must provide at most one test name.')
88
89   def Run(self, args):
90     if args.json_output_file:
91       possible_browser = browser_finder.FindBrowser(args)
92       with open(args.json_output_file, 'w') as f:
93         f.write(_GetJsonTestList(possible_browser, args.tests, args.num_shards))
94     else:
95       _PrintTestList(args.tests)
96     return 0
97
98
99 class Run(command_line.OptparseCommand):
100   """Run one or more tests (default)"""
101
102   usage = 'test_name [page_set] [<options>]'
103
104   @classmethod
105   def CreateParser(cls):
106     options = browser_options.BrowserFinderOptions()
107     parser = options.CreateParser('%%prog %s %s' % (cls.Name(), cls.usage))
108     return parser
109
110   @classmethod
111   def AddCommandLineArgs(cls, parser):
112     benchmark.AddCommandLineArgs(parser)
113
114     # Allow tests to add their own command line options.
115     matching_tests = []
116     for arg in sys.argv[1:]:
117       matching_tests += _MatchTestName(arg)
118
119     if matching_tests:
120       # TODO(dtu): After move to argparse, add command-line args for all tests
121       # to subparser. Using subparsers will avoid duplicate arguments.
122       matching_test = matching_tests.pop()
123       matching_test.AddCommandLineArgs(parser)
124       # The test's options override the defaults!
125       matching_test.SetArgumentDefaults(parser)
126
127   @classmethod
128   def ProcessCommandLineArgs(cls, parser, args):
129     if not args.positional_args:
130       _PrintTestList(_Tests())
131       sys.exit(-1)
132
133     input_test_name = args.positional_args[0]
134     matching_tests = _MatchTestName(input_test_name)
135     if not matching_tests:
136       print >> sys.stderr, 'No test named "%s".' % input_test_name
137       print >> sys.stderr
138       _PrintTestList(_Tests())
139       sys.exit(-1)
140
141     if len(matching_tests) > 1:
142       print >> sys.stderr, 'Multiple tests named "%s".' % input_test_name
143       print >> sys.stderr, 'Did you mean one of these?'
144       print >> sys.stderr
145       _PrintTestList(matching_tests)
146       sys.exit(-1)
147
148     test_class = matching_tests.pop()
149     if issubclass(test_class, page_test.PageTest):
150       if len(args.positional_args) < 2:
151         parser.error('Need to specify a page set for "%s".' % test_class.Name())
152       if len(args.positional_args) > 2:
153         parser.error('Too many arguments.')
154       page_set_path = args.positional_args[1]
155       if not os.path.exists(page_set_path):
156         parser.error('Page set not found.')
157       if not (os.path.isfile(page_set_path) and
158               discover.IsPageSetFile(page_set_path)):
159         parser.error('Unsupported page set file format.')
160
161       class TestWrapper(benchmark.Benchmark):
162         test = test_class
163
164         @classmethod
165         def CreatePageSet(cls, options):
166           return page_set.PageSet.FromFile(page_set_path)
167
168       test_class = TestWrapper
169     else:
170       if len(args.positional_args) > 1:
171         parser.error('Too many arguments.')
172
173     assert issubclass(test_class, benchmark.Benchmark), (
174         'Trying to run a non-Benchmark?!')
175
176     benchmark.ProcessCommandLineArgs(parser, args)
177     test_class.ProcessCommandLineArgs(parser, args)
178
179     cls._test = test_class
180
181   def Run(self, args):
182     return min(255, self._test().Run(args))
183
184
185 def _ScriptName():
186   return os.path.basename(sys.argv[0])
187
188
189 def _Commands():
190   """Generates a list of all classes in this file that subclass Command."""
191   for _, cls in inspect.getmembers(sys.modules[__name__]):
192     if not inspect.isclass(cls):
193       continue
194     if not issubclass(cls, command_line.Command):
195       continue
196     yield cls
197
198 def _MatchingCommands(string):
199   return [command for command in _Commands()
200          if command.Name().startswith(string)]
201
202 @decorators.Cache
203 def _Tests():
204   tests = []
205   for base_dir in config.base_paths:
206     tests += discover.DiscoverClasses(base_dir, base_dir, benchmark.Benchmark,
207                                       index_by_class_name=True).values()
208     page_tests = discover.DiscoverClasses(base_dir, base_dir,
209                                           page_test.PageTest,
210                                           index_by_class_name=True).values()
211     tests += [test_class for test_class in page_tests
212               if not issubclass(test_class, profile_creator.ProfileCreator)]
213   return tests
214
215
216 def _MatchTestName(input_test_name, exact_matches=True):
217   def _Matches(input_string, search_string):
218     if search_string.startswith(input_string):
219       return True
220     for part in search_string.split('.'):
221       if part.startswith(input_string):
222         return True
223     return False
224
225   # Exact matching.
226   if exact_matches:
227     # Don't add aliases to search dict, only allow exact matching for them.
228     if input_test_name in config.test_aliases:
229       exact_match = config.test_aliases[input_test_name]
230     else:
231       exact_match = input_test_name
232
233     for test_class in _Tests():
234       if exact_match == test_class.Name():
235         return [test_class]
236
237   # Fuzzy matching.
238   return [test_class for test_class in _Tests()
239           if _Matches(input_test_name, test_class.Name())]
240
241
242 def _GetJsonTestList(possible_browser, test_classes, num_shards):
243   """Returns a list of all enabled tests in a JSON format expected by buildbots.
244
245   JSON format (see build/android/pylib/perf/test_runner.py):
246   { "version": int,
247     "steps": {
248       "foo": {
249         "device_affinity": int,
250         "cmd": "script_to_execute foo"
251       },
252       "bar": {
253         "device_affinity": int,
254         "cmd": "script_to_execute bar"
255       }
256     }
257   }
258   """
259   output = {
260     'version': 1,
261     'steps': {
262     }
263   }
264   for test_class in test_classes:
265     if not issubclass(test_class, benchmark.Benchmark):
266       continue
267     if not decorators.IsEnabled(test_class, possible_browser):
268       continue
269     name = test_class.Name()
270     output['steps'][name] = {
271       'cmd': ' '.join([sys.executable, os.path.realpath(sys.argv[0]),
272                        '--browser=%s' % possible_browser.browser_type,
273                        '-v', '--output-format=buildbot', name]),
274       # TODO(tonyg): Currently we set the device affinity to a stable hash of
275       # the test name. This somewhat evenly distributes benchmarks among the
276       # requested number of shards. However, it is far from optimal in terms of
277       # cycle time. We should add a test size decorator (e.g. small, medium,
278       # large) and let that inform sharding.
279       'device_affinity': int(hashlib.sha1(name).hexdigest(), 16) % num_shards
280     }
281   return json.dumps(output, indent=2, sort_keys=True)
282
283
284 def _PrintTestList(tests):
285   if not tests:
286     print >> sys.stderr, 'No tests found!'
287     return
288
289   # Align the test names to the longest one.
290   format_string = '  %%-%ds %%s' % max(len(t.Name()) for t in tests)
291
292   filtered_tests = [test_class for test_class in tests
293                     if issubclass(test_class, benchmark.Benchmark)]
294   if filtered_tests:
295     print >> sys.stderr, 'Available tests are:'
296     for test_class in sorted(filtered_tests, key=lambda t: t.Name()):
297       print >> sys.stderr, format_string % (
298           test_class.Name(), test_class.Description())
299     print >> sys.stderr
300
301   filtered_tests = [test_class for test_class in tests
302                     if issubclass(test_class, page_test.PageTest)]
303   if filtered_tests:
304     print >> sys.stderr, 'Available page tests are:'
305     for test_class in sorted(filtered_tests, key=lambda t: t.Name()):
306       print >> sys.stderr, format_string % (
307           test_class.Name(), test_class.Description())
308     print >> sys.stderr
309
310
311 config = environment.Environment([util.GetBaseDir()])
312
313
314 def main():
315   # Get the command name from the command line.
316   if len(sys.argv) > 1 and sys.argv[1] == '--help':
317     sys.argv[1] = 'help'
318
319   command_name = 'run'
320   for arg in sys.argv[1:]:
321     if not arg.startswith('-'):
322       command_name = arg
323       break
324
325   # Validate and interpret the command name.
326   commands = _MatchingCommands(command_name)
327   if len(commands) > 1:
328     print >> sys.stderr, ('"%s" is not a %s command. Did you mean one of these?'
329                           % (command_name, _ScriptName()))
330     for command in commands:
331       print >> sys.stderr, '  %-10s %s' % (
332           command.Name(), command.Description())
333     return 1
334   if commands:
335     command = commands[0]
336   else:
337     command = Run
338
339   # Parse and run the command.
340   parser = command.CreateParser()
341   command.AddCommandLineArgs(parser)
342   options, args = parser.parse_args()
343   if commands:
344     args = args[1:]
345   options.positional_args = args
346   command.ProcessCommandLineArgs(parser, options)
347   return command().Run(options)