| # Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Parses the command line, discovers the appropriate tests, and runs them. |
| |
| Handles test configuration, but all the logic for |
| actually running the test is in Test and PageRunner.""" |
| |
| import copy |
| import inspect |
| import json |
| import optparse |
| import os |
| import sys |
| |
| from telemetry import test |
| from telemetry.core import browser_options |
| from telemetry.core import discover |
| from telemetry.core import util |
| |
| |
| class Command(object): |
| usage = '' |
| |
| @property |
| def name(self): |
| return self.__class__.__name__.lower() |
| |
| @property |
| def description(self): |
| return self.__doc__ |
| |
| def CreateParser(self): |
| return optparse.OptionParser('%%prog %s %s' % (self.name, self.usage)) |
| |
| def AddCommandLineOptions(self, parser): |
| pass |
| |
| def ProcessCommandLine(self, parser, options, args): |
| pass |
| |
| def Run(self, options, args): |
| raise NotImplementedError() |
| |
| |
| class Help(Command): |
| """Display help information""" |
| |
| def Run(self, options, args): |
| print >> sys.stderr, ('usage: %s <command> [<options>]' % _GetScriptName()) |
| print >> sys.stderr, 'Available commands are:' |
| for command in COMMANDS: |
| print >> sys.stderr, ' %-10s %s' % (command.name, command.description) |
| return 0 |
| |
| |
| class List(Command): |
| """Lists the available tests""" |
| |
| usage = '[test_name] [<options>]' |
| |
| def __init__(self): |
| super(List, self).__init__() |
| self._tests = None |
| |
| def AddCommandLineOptions(self, parser): |
| parser.add_option('-j', '--json', action='store_true') |
| |
| def ProcessCommandLine(self, parser, options, args): |
| if not args: |
| self._tests = _GetTests() |
| elif len(args) == 1: |
| self._tests = _MatchTestName(args[0]) |
| else: |
| parser.error('Must provide at most one test name.') |
| |
| def Run(self, options, args): |
| if options.json: |
| test_list = [] |
| for test_name, test_class in sorted(self._tests.items()): |
| test_list.append({ |
| 'name': test_name, |
| 'description': test_class.__doc__, |
| 'enabled': test_class.enabled, |
| 'options': test_class.options, |
| }) |
| print json.dumps(test_list) |
| else: |
| print >> sys.stderr, 'Available tests are:' |
| _PrintTestList(self._tests) |
| return 0 |
| |
| |
| class Run(Command): |
| """Run one or more tests""" |
| |
| usage = 'test_name [<options>]' |
| |
| def __init__(self): |
| super(Run, self).__init__() |
| self._test = None |
| |
| def CreateParser(self): |
| options = browser_options.BrowserFinderOptions() |
| parser = options.CreateParser('%%prog %s %s' % (self.name, self.usage)) |
| return parser |
| |
| def AddCommandLineOptions(self, parser): |
| test.Test.AddCommandLineOptions(parser) |
| |
| # Allow tests to add their own command line options. |
| matching_tests = {} |
| for arg in sys.argv[1:]: |
| matching_tests.update(_MatchTestName(arg)) |
| for test_class in matching_tests.itervalues(): |
| test_class.AddTestCommandLineOptions(parser) |
| |
| def ProcessCommandLine(self, parser, options, args): |
| if len(args) != 1: |
| parser.error('Must provide one test name.') |
| |
| input_test_name = args[0] |
| matching_tests = _MatchTestName(input_test_name) |
| if not matching_tests: |
| print >> sys.stderr, 'No test named "%s".' % input_test_name |
| print >> sys.stderr |
| print >> sys.stderr, 'Available tests:' |
| _PrintTestList(_GetTests()) |
| sys.exit(1) |
| if len(matching_tests) > 1: |
| print >> sys.stderr, 'Multiple tests named "%s".' % input_test_name |
| print >> sys.stderr |
| print >> sys.stderr, 'Did you mean one of these?' |
| _PrintTestList(matching_tests) |
| sys.exit(1) |
| |
| self._test = matching_tests.popitem()[1] |
| |
| def Run(self, options, args): |
| if not self._test.enabled: |
| print >> sys.stderr, 'TEST IS DISABLED. SKIPPING.' |
| return 0 |
| return min(255, self._test().Run(copy.copy(options))) |
| |
| |
| COMMANDS = [cls() for _, cls in inspect.getmembers(sys.modules[__name__]) |
| if inspect.isclass(cls) |
| and cls is not Command and issubclass(cls, Command)] |
| |
| |
| def _GetScriptName(): |
| return os.path.basename(sys.argv[0]) |
| |
| |
| def _GetTests(): |
| # Lazy load and cache results. |
| if not hasattr(_GetTests, 'tests'): |
| base_dir = util.GetBaseDir() |
| tests = discover.DiscoverClasses(base_dir, base_dir, test.Test, |
| index_by_class_name=True) |
| tests = dict((test.GetName(), test) for test in tests.itervalues()) |
| _GetTests.tests = tests |
| return _GetTests.tests |
| |
| |
| def _MatchTestName(input_test_name): |
| def _Matches(input_string, search_string): |
| if search_string.startswith(input_string): |
| return True |
| for part in search_string.split('.'): |
| if part.startswith(input_string): |
| return True |
| return False |
| |
| # Exact matching. |
| if input_test_name in test_aliases: |
| exact_match = test_aliases[input_test_name] |
| else: |
| exact_match = input_test_name |
| if exact_match in _GetTests(): |
| return {exact_match: _GetTests()[exact_match]} |
| |
| # Fuzzy matching. |
| return dict((test_name, test_class) |
| for test_name, test_class in _GetTests().iteritems() |
| if _Matches(input_test_name, test_name)) |
| |
| |
| def _PrintTestList(tests): |
| for test_name, test_class in sorted(tests.items()): |
| if test_class.__doc__: |
| description = test_class.__doc__.splitlines()[0] |
| # Align the test names to the longest one. |
| format_string = ' %%-%ds %%s' % max(map(len, tests.iterkeys())) |
| print >> sys.stderr, format_string % (test_name, description) |
| else: |
| print >> sys.stderr, ' %s' % test_name |
| |
| |
| test_aliases = {} |
| |
| |
| def Main(): |
| # Get the command name from the command line. |
| if len(sys.argv) > 1 and sys.argv[1] == '--help': |
| sys.argv[1] = 'help' |
| |
| command_name = 'run' |
| for arg in sys.argv[1:]: |
| if not arg.startswith('-'): |
| command_name = arg |
| break |
| |
| # Validate and interpret the command name. |
| commands = [command for command in COMMANDS |
| if command.name.startswith(command_name)] |
| if len(commands) > 1: |
| print >> sys.stderr, ('"%s" is not a %s command. Did you mean one of these?' |
| % (command_name, _GetScriptName())) |
| for command in commands: |
| print >> sys.stderr, ' %-10s %s' % (command.name, command.description) |
| return 1 |
| if commands: |
| command = commands[0] |
| else: |
| command = Run() |
| |
| # Parse and run the command. |
| parser = command.CreateParser() |
| command.AddCommandLineOptions(parser) |
| options, args = parser.parse_args() |
| if commands: |
| args = args[1:] |
| command.ProcessCommandLine(parser, options, args) |
| return command.Run(options, args) |