page_test_runner.py revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
1# Copyright (c) 2012 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.
4import os
5import sys
6
7from telemetry import test as test_module
8from telemetry.core import browser_options
9from telemetry.core import discover
10from telemetry.core import profile_types
11from telemetry.page import page_test as page_test_module
12from telemetry.page import page_runner
13from telemetry.page import page_set
14
15def Main(base_dir, page_set_filenames):
16  """Turns a PageTest into a command-line program.
17
18  Args:
19    base_dir: Path to directory containing tests and ProfileCreators.
20  """
21  runner = PageTestRunner()
22  sys.exit(runner.Run(base_dir, page_set_filenames))
23
24class PageTestRunner(object):
25  def __init__(self):
26    self._parser = None
27    self._options = None
28    self._args = None
29
30  @property
31  def test_class(self):
32    return page_test_module.PageTest
33
34  @property
35  def test_class_name(self):
36    return 'test'
37
38  def Run(self, base_dir, page_set_filenames):
39    test, ps = self.ParseCommandLine(sys.argv, base_dir, page_set_filenames)
40    results = page_runner.Run(test, ps, self._options)
41    results.PrintSummary()
42    return min(255, len(results.failures + results.errors))
43
44  def FindTestConstructors(self, base_dir):
45    # Look for both Tests and PageTests, but Tests get priority, because
46    test_constructors = discover.DiscoverClasses(
47        base_dir, base_dir, self.test_class)
48    test_constructors.update(discover.DiscoverClasses(
49        base_dir, base_dir, test_module.Test, index_by_class_name=True))
50    return test_constructors
51
52  def FindTestName(self, test_constructors, args):
53    """Find the test name in an arbitrary argument list.
54
55    We can't use the optparse parser, because the test may add its own
56    command-line options. If the user passed in any of those, the
57    optparse parsing will fail.
58
59    Returns:
60      test_name or None
61    """
62    test_name = None
63    for arg in [self.GetModernizedTestName(a) for a in args]:
64      if arg in test_constructors:
65        test_name = arg
66
67    return test_name
68
69  def GetModernizedTestName(self, arg):
70    """Sometimes tests change names but buildbots keep calling the old name.
71
72    If arg matches an old test name, return the new test name instead.
73    Otherwise, return the arg.
74    """
75    return arg
76
77  def GetPageSet(self, test, page_set_filenames):
78    ps = test.CreatePageSet(self._args, self._options)
79    if ps:
80      return ps
81
82    if len(self._args) < 2:
83      page_set_list = ',\n'.join(
84          sorted([os.path.relpath(f) for f in page_set_filenames]))
85      self.PrintParseError(
86          'No page set, file, or URL specified.\n'
87          'Available page sets:\n'
88          '%s' % page_set_list)
89
90    page_set_arg = self._args[1]
91
92    # We've been given a URL. Create a page set with just that URL.
93    if (page_set_arg.startswith('http://') or
94        page_set_arg.startswith('https://')):
95      self._options.allow_live_sites = True
96      return page_set.PageSet.FromDict({
97          'pages': [{'url': page_set_arg}]
98          }, os.path.dirname(__file__))
99
100    # We've been given a page set JSON. Load it.
101    if page_set_arg.endswith('.json'):
102      return page_set.PageSet.FromFile(page_set_arg)
103
104    # We've been given a file or directory. Create a page set containing it.
105    if os.path.exists(page_set_arg):
106      page_set_dict = {'pages': []}
107
108      def _AddFile(file_path):
109        page_set_dict['pages'].append({'url': 'file://' + file_path})
110
111      def _AddDir(dir_path):
112        for path in os.listdir(dir_path):
113          path = os.path.join(dir_path, path)
114          _AddPath(path)
115
116      def _AddPath(path):
117        if os.path.isdir(path):
118          _AddDir(path)
119        else:
120          _AddFile(path)
121
122      _AddPath(page_set_arg)
123      return page_set.PageSet.FromDict(page_set_dict, os.getcwd() + os.sep)
124
125    raise Exception('Did not understand "%s". Pass a page set, file or URL.' %
126                    page_set_arg)
127
128  def ParseCommandLine(self, args, base_dir, page_set_filenames):
129    # Need to collect profile creators before creating command line parser.
130    profile_types.FindProfileCreators(base_dir, base_dir)
131
132    self._options = browser_options.BrowserOptions()
133    self._parser = self._options.CreateParser(
134        '%%prog [options] %s page_set' % self.test_class_name)
135
136    test_constructors = self.FindTestConstructors(base_dir)
137    test_name = self.FindTestName(test_constructors, args)
138    test = None
139    if test_name:
140      test = test_constructors[test_name]()
141      if isinstance(test, test_module.Test):
142        page_test = test.test()
143      else:
144        page_test = test
145      page_test.AddOutputOptions(self._parser)
146      page_test.AddCommandLineOptions(self._parser)
147    page_runner.AddCommandLineOptions(self._parser)
148
149    _, self._args = self._parser.parse_args()
150
151    if len(self._args) < 1:
152      error_message = 'No %s specified.\nAvailable %ss:\n' % (
153          self.test_class_name, self.test_class_name)
154      test_list_string = ',\n'.join(sorted(test_constructors.keys()))
155      self.PrintParseError(error_message + test_list_string)
156
157    if not test:
158      error_message = 'No %s named %s.\nAvailable %ss:\n' % (
159          self.test_class_name, self._args[0], self.test_class_name)
160      test_list_string = ',\n'.join(sorted(test_constructors.keys()))
161      self.PrintParseError(error_message + test_list_string)
162
163    if isinstance(test, test_module.Test):
164      ps = test.CreatePageSet(self._options)
165    else:
166      ps = self.GetPageSet(test, page_set_filenames)
167
168    if len(self._args) > 2:
169      self.PrintParseError('Too many arguments.')
170
171    return page_test, ps
172
173  def PrintParseError(self, message):
174    self._parser.error(message)
175