blob: adb95a83618e1b1d2ff968df9211b297fda26d68 [file] [log] [blame]
# 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)