- add sources.
[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 copy
11 import inspect
12 import json
13 import optparse
14 import os
15 import sys
16
17 from telemetry import test
18 from telemetry.core import browser_options
19 from telemetry.core import discover
20 from telemetry.core import util
21
22
23 class Command(object):
24   usage = ''
25
26   @property
27   def name(self):
28     return self.__class__.__name__.lower()
29
30   @property
31   def description(self):
32     return self.__doc__
33
34   def CreateParser(self):
35     return optparse.OptionParser('%%prog %s %s' % (self.name, self.usage))
36
37   def AddCommandLineOptions(self, parser):
38     pass
39
40   def ProcessCommandLine(self, parser, options, args):
41     pass
42
43   def Run(self, options, args):
44     raise NotImplementedError()
45
46
47 class Help(Command):
48   """Display help information"""
49
50   def Run(self, options, args):
51     print >> sys.stderr, ('usage: %s <command> [<options>]' % _GetScriptName())
52     print >> sys.stderr, 'Available commands are:'
53     for command in COMMANDS:
54       print >> sys.stderr, '  %-10s %s' % (command.name, command.description)
55     return 0
56
57
58 class List(Command):
59   """Lists the available tests"""
60
61   usage = '[test_name] [<options>]'
62
63   def __init__(self):
64     super(List, self).__init__()
65     self._tests = None
66
67   def AddCommandLineOptions(self, parser):
68     parser.add_option('-j', '--json', action='store_true')
69
70   def ProcessCommandLine(self, parser, options, args):
71     if not args:
72       self._tests = _GetTests()
73     elif len(args) == 1:
74       self._tests = _MatchTestName(args[0])
75     else:
76       parser.error('Must provide at most one test name.')
77
78   def Run(self, options, args):
79     if options.json:
80       test_list = []
81       for test_name, test_class in sorted(self._tests.items()):
82         test_list.append({
83               'name': test_name,
84               'description': test_class.__doc__,
85               'enabled': test_class.enabled,
86               'options': test_class.options,
87             })
88       print json.dumps(test_list)
89     else:
90       print >> sys.stderr, 'Available tests are:'
91       _PrintTestList(self._tests)
92     return 0
93
94
95 class Run(Command):
96   """Run one or more tests"""
97
98   usage = 'test_name [<options>]'
99
100   def __init__(self):
101     super(Run, self).__init__()
102     self._test = None
103
104   def CreateParser(self):
105     options = browser_options.BrowserFinderOptions()
106     parser = options.CreateParser('%%prog %s %s' % (self.name, self.usage))
107     return parser
108
109   def AddCommandLineOptions(self, parser):
110     test.Test.AddCommandLineOptions(parser)
111
112     # Allow tests to add their own command line options.
113     matching_tests = {}
114     for arg in sys.argv[1:]:
115       matching_tests.update(_MatchTestName(arg))
116     for test_class in matching_tests.itervalues():
117       test_class.AddTestCommandLineOptions(parser)
118
119   def ProcessCommandLine(self, parser, options, args):
120     if len(args) != 1:
121       parser.error('Must provide one test name.')
122
123     input_test_name = args[0]
124     matching_tests = _MatchTestName(input_test_name)
125     if not matching_tests:
126       print >> sys.stderr, 'No test named "%s".' % input_test_name
127       print >> sys.stderr
128       print >> sys.stderr, 'Available tests:'
129       _PrintTestList(_GetTests())
130       sys.exit(1)
131     if len(matching_tests) > 1:
132       print >> sys.stderr, 'Multiple tests named "%s".' % input_test_name
133       print >> sys.stderr
134       print >> sys.stderr, 'Did you mean one of these?'
135       _PrintTestList(matching_tests)
136       sys.exit(1)
137
138     self._test = matching_tests.popitem()[1]
139
140   def Run(self, options, args):
141     if not self._test.enabled:
142       print >> sys.stderr, 'TEST IS DISABLED. SKIPPING.'
143       return 0
144     return min(255, self._test().Run(copy.copy(options)))
145
146
147 COMMANDS = [cls() for _, cls in inspect.getmembers(sys.modules[__name__])
148             if inspect.isclass(cls)
149             and cls is not Command and issubclass(cls, Command)]
150
151
152 def _GetScriptName():
153   return os.path.basename(sys.argv[0])
154
155
156 def _GetTests():
157   # Lazy load and cache results.
158   if not hasattr(_GetTests, 'tests'):
159     base_dir = util.GetBaseDir()
160     tests = discover.DiscoverClasses(base_dir, base_dir, test.Test,
161                                      index_by_class_name=True)
162     tests = dict((test.GetName(), test) for test in tests.itervalues())
163     _GetTests.tests = tests
164   return _GetTests.tests
165
166
167 def _MatchTestName(input_test_name):
168   def _Matches(input_string, search_string):
169     if search_string.startswith(input_string):
170       return True
171     for part in search_string.split('.'):
172       if part.startswith(input_string):
173         return True
174     return False
175
176   # Exact matching.
177   if input_test_name in test_aliases:
178     exact_match = test_aliases[input_test_name]
179   else:
180     exact_match = input_test_name
181   if exact_match in _GetTests():
182     return {exact_match: _GetTests()[exact_match]}
183
184   # Fuzzy matching.
185   return dict((test_name, test_class)
186       for test_name, test_class in _GetTests().iteritems()
187       if _Matches(input_test_name, test_name))
188
189
190 def _PrintTestList(tests):
191   for test_name, test_class in sorted(tests.items()):
192     if test_class.__doc__:
193       description = test_class.__doc__.splitlines()[0]
194       # Align the test names to the longest one.
195       format_string = '  %%-%ds %%s' % max(map(len, tests.iterkeys()))
196       print >> sys.stderr, format_string % (test_name, description)
197     else:
198       print >> sys.stderr, '  %s' % test_name
199
200
201 test_aliases = {}
202
203
204 def Main():
205   # Get the command name from the command line.
206   if len(sys.argv) > 1 and sys.argv[1] == '--help':
207     sys.argv[1] = 'help'
208
209   command_name = 'run'
210   for arg in sys.argv[1:]:
211     if not arg.startswith('-'):
212       command_name = arg
213       break
214
215   # Validate and interpret the command name.
216   commands = [command for command in COMMANDS
217               if command.name.startswith(command_name)]
218   if len(commands) > 1:
219     print >> sys.stderr, ('"%s" is not a %s command. Did you mean one of these?'
220                           % (command_name, _GetScriptName()))
221     for command in commands:
222       print >> sys.stderr, '  %-10s %s' % (command.name, command.description)
223     return 1
224   if commands:
225     command = commands[0]
226   else:
227     command = Run()
228
229   # Parse and run the command.
230   parser = command.CreateParser()
231   command.AddCommandLineOptions(parser)
232   options, args = parser.parse_args()
233   if commands:
234     args = args[1:]
235   command.ProcessCommandLine(parser, options, args)
236   return command.Run(options, args)