Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / test_runner.py
1 # Copyright (c) 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 inspect
11 import json
12 import os
13 import sys
14
15 from telemetry import decorators
16 from telemetry import test
17 from telemetry.core import browser_options
18 from telemetry.core import command_line
19 from telemetry.core import discover
20 from telemetry.core import environment
21 from telemetry.core import util
22 from telemetry.page import page_set
23 from telemetry.page import page_test
24 from telemetry.page import profile_creator
25 from telemetry.util import find_dependencies
26
27
28 class Deps(find_dependencies.FindDependenciesCommand):
29   """Prints all dependencies"""
30
31   def Run(self, args):
32     main_module = sys.modules['__main__']
33     args.positional_args.append(os.path.realpath(main_module.__file__))
34     return super(Deps, self).Run(args)
35
36
37 class Help(command_line.OptparseCommand):
38   """Display help information"""
39
40   def Run(self, args):
41     print >> sys.stderr, ('usage: %s <command> [<options>]' % _ScriptName())
42     print >> sys.stderr, 'Available commands are:'
43     for command in _Commands():
44       print >> sys.stderr, '  %-10s %s' % (
45           command.Name(), command.Description())
46     return 0
47
48
49 class List(command_line.OptparseCommand):
50   """Lists the available tests"""
51
52   usage = '[test_name] [<options>]'
53
54   @classmethod
55   def AddCommandLineArgs(cls, parser):
56     parser.add_option('-j', '--json', action='store_true')
57
58   @classmethod
59   def ProcessCommandLineArgs(cls, parser, args):
60     if not args.positional_args:
61       args.tests = _Tests()
62     elif len(args.positional_args) == 1:
63       args.tests = _MatchTestName(args.positional_args[0], exact_matches=False)
64     else:
65       parser.error('Must provide at most one test name.')
66
67   def Run(self, args):
68     if args.json:
69       test_list = []
70       for test_class in sorted(args.tests, key=lambda t: t.Name()):
71         test_list.append({
72             'name': test_class.Name(),
73             'description': test_class.Description(),
74             'options': test_class.options,
75         })
76       print json.dumps(test_list)
77     else:
78       _PrintTestList(args.tests)
79     return 0
80
81
82 class Run(command_line.OptparseCommand):
83   """Run one or more tests"""
84
85   usage = 'test_name [page_set] [<options>]'
86
87   @classmethod
88   def CreateParser(cls):
89     options = browser_options.BrowserFinderOptions()
90     parser = options.CreateParser('%%prog %s %s' % (cls.Name(), cls.usage))
91     return parser
92
93   @classmethod
94   def AddCommandLineArgs(cls, parser):
95     test.AddCommandLineArgs(parser)
96
97     # Allow tests to add their own command line options.
98     matching_tests = []
99     for arg in sys.argv[1:]:
100       matching_tests += _MatchTestName(arg)
101
102     if matching_tests:
103       # TODO(dtu): After move to argparse, add command-line args for all tests
104       # to subparser. Using subparsers will avoid duplicate arguments.
105       matching_test = matching_tests.pop()
106       matching_test.AddCommandLineArgs(parser)
107       # The test's options override the defaults!
108       matching_test.SetArgumentDefaults(parser)
109
110   @classmethod
111   def ProcessCommandLineArgs(cls, parser, args):
112     if not args.positional_args:
113       _PrintTestList(_Tests())
114       sys.exit(-1)
115
116     input_test_name = args.positional_args[0]
117     matching_tests = _MatchTestName(input_test_name)
118     if not matching_tests:
119       print >> sys.stderr, 'No test named "%s".' % input_test_name
120       print >> sys.stderr
121       _PrintTestList(_Tests())
122       sys.exit(-1)
123
124     if len(matching_tests) > 1:
125       print >> sys.stderr, 'Multiple tests named "%s".' % input_test_name
126       print >> sys.stderr, 'Did you mean one of these?'
127       print >> sys.stderr
128       _PrintTestList(matching_tests)
129       sys.exit(-1)
130
131     test_class = matching_tests.pop()
132     if issubclass(test_class, page_test.PageTest):
133       if len(args.positional_args) < 2:
134         parser.error('Need to specify a page set for "%s".' % test_class.Name())
135       if len(args.positional_args) > 2:
136         parser.error('Too many arguments.')
137       page_set_path = args.positional_args[1]
138       if not os.path.exists(page_set_path):
139         parser.error('Page set not found.')
140       if not (os.path.isfile(page_set_path) and
141               discover.IsPageSetFile(page_set_path)):
142         parser.error('Unsupported page set file format.')
143
144       class TestWrapper(test.Test):
145         test = test_class
146
147         @classmethod
148         def CreatePageSet(cls, options):
149           return page_set.PageSet.FromFile(page_set_path)
150
151       test_class = TestWrapper
152     else:
153       if len(args.positional_args) > 1:
154         parser.error('Too many arguments.')
155
156     assert issubclass(test_class, test.Test), 'Trying to run a non-Test?!'
157
158     test_class.ProcessCommandLineArgs(parser, args)
159     test.ProcessCommandLineArgs(parser, args)
160
161     cls._test = test_class
162
163   def Run(self, args):
164     return min(255, self._test().Run(args))
165
166
167 def _ScriptName():
168   return os.path.basename(sys.argv[0])
169
170
171 def _Commands():
172   """Generates a list of all classes in this file that subclass Command."""
173   for _, cls in inspect.getmembers(sys.modules[__name__]):
174     if not inspect.isclass(cls):
175       continue
176     if not issubclass(cls, command_line.Command):
177       continue
178     yield cls
179
180
181 @decorators.Cache
182 def _Tests():
183   tests = []
184   for base_dir in config.base_paths:
185     tests += discover.DiscoverClasses(base_dir, base_dir, test.Test,
186                                       index_by_class_name=True).values()
187     page_tests = discover.DiscoverClasses(base_dir, base_dir,
188                                           page_test.PageTest,
189                                           index_by_class_name=True).values()
190     tests += [test_class for test_class in page_tests
191               if not issubclass(test_class, profile_creator.ProfileCreator)]
192   return tests
193
194
195 def _MatchTestName(input_test_name, exact_matches=True):
196   def _Matches(input_string, search_string):
197     if search_string.startswith(input_string):
198       return True
199     for part in search_string.split('.'):
200       if part.startswith(input_string):
201         return True
202     return False
203
204   # Exact matching.
205   if exact_matches:
206     # Don't add aliases to search dict, only allow exact matching for them.
207     if input_test_name in config.test_aliases:
208       exact_match = config.test_aliases[input_test_name]
209     else:
210       exact_match = input_test_name
211
212     for test_class in _Tests():
213       if exact_match == test_class.Name():
214         return [test_class]
215
216   # Fuzzy matching.
217   return [test_class for test_class in _Tests()
218           if _Matches(input_test_name, test_class.Name())]
219
220
221 def _PrintTestList(tests):
222   if not tests:
223     print >> sys.stderr, 'No tests found!'
224     return
225
226   # Align the test names to the longest one.
227   format_string = '  %%-%ds %%s' % max(len(t.Name()) for t in tests)
228
229   filtered_tests = [test_class for test_class in tests
230                     if issubclass(test_class, test.Test)]
231   if filtered_tests:
232     print >> sys.stderr, 'Available tests are:'
233     for test_class in sorted(filtered_tests, key=lambda t: t.Name()):
234       print >> sys.stderr, format_string % (
235           test_class.Name(), test_class.Description())
236     print >> sys.stderr
237
238   filtered_tests = [test_class for test_class in tests
239                     if issubclass(test_class, page_test.PageTest)]
240   if filtered_tests:
241     print >> sys.stderr, 'Available page tests are:'
242     for test_class in sorted(filtered_tests, key=lambda t: t.Name()):
243       print >> sys.stderr, format_string % (
244           test_class.Name(), test_class.Description())
245     print >> sys.stderr
246
247
248 config = environment.Environment([util.GetBaseDir()])
249
250
251 def main():
252   # Get the command name from the command line.
253   if len(sys.argv) > 1 and sys.argv[1] == '--help':
254     sys.argv[1] = 'help'
255
256   command_name = 'run'
257   for arg in sys.argv[1:]:
258     if not arg.startswith('-'):
259       command_name = arg
260       break
261
262   # Validate and interpret the command name.
263   commands = [command for command in _Commands()
264               if command.Name().startswith(command_name)]
265   if len(commands) > 1:
266     print >> sys.stderr, ('"%s" is not a %s command. Did you mean one of these?'
267                           % (command_name, _ScriptName()))
268     for command in commands:
269       print >> sys.stderr, '  %-10s %s' % (
270           command.Name(), command.Description())
271     return 1
272   if commands:
273     command = commands[0]
274   else:
275     command = Run
276
277   # Parse and run the command.
278   parser = command.CreateParser()
279   command.AddCommandLineArgs(parser)
280   options, args = parser.parse_args()
281   if commands:
282     args = args[1:]
283   options.positional_args = args
284   command.ProcessCommandLineArgs(parser, options)
285   return command().Run(options)