Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / test_runner.py
index 71f3ef2..6d43391 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Copyright 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.
 
@@ -7,16 +7,21 @@
 Handles test configuration, but all the logic for
 actually running the test is in Test and PageRunner."""
 
+import hashlib
 import inspect
 import json
 import os
 import sys
 
-from telemetry import test
+from telemetry import benchmark
+from telemetry import decorators
+from telemetry.core import browser_finder
 from telemetry.core import browser_options
 from telemetry.core import command_line
 from telemetry.core import discover
+from telemetry.core import environment
 from telemetry.core import util
+from telemetry.page import page_set
 from telemetry.page import page_test
 from telemetry.page import profile_creator
 from telemetry.util import find_dependencies
@@ -32,14 +37,27 @@ class Deps(find_dependencies.FindDependenciesCommand):
 
 
 class Help(command_line.OptparseCommand):
-  """Display help information"""
+  """Display help information about a command"""
+
+  usage = '[command]'
 
   def Run(self, args):
-    print >> sys.stderr, ('usage: %s <command> [<options>]' % _ScriptName())
+    if len(args.positional_args) == 1:
+      commands = _MatchingCommands(args.positional_args[0])
+      if len(commands) == 1:
+        command = commands[0]
+        parser = command.CreateParser()
+        command.AddCommandLineArgs(parser)
+        parser.print_help()
+        return 0
+
+    print >> sys.stderr, ('usage: %s [command] [<options>]' % _ScriptName())
     print >> sys.stderr, 'Available commands are:'
     for command in _Commands():
       print >> sys.stderr, '  %-10s %s' % (
           command.Name(), command.Description())
+    print >> sys.stderr, ('"%s help <command>" to see usage information '
+                          'for a specific command.' % _ScriptName())
     return 0
 
 
@@ -49,8 +67,15 @@ class List(command_line.OptparseCommand):
   usage = '[test_name] [<options>]'
 
   @classmethod
+  def CreateParser(cls):
+    options = browser_options.BrowserFinderOptions()
+    parser = options.CreateParser('%%prog %s %s' % (cls.Name(), cls.usage))
+    return parser
+
+  @classmethod
   def AddCommandLineArgs(cls, parser):
-    parser.add_option('-j', '--json', action='store_true')
+    parser.add_option('-j', '--json-output-file', type='string')
+    parser.add_option('-n', '--num-shards', type='int', default=1)
 
   @classmethod
   def ProcessCommandLineArgs(cls, parser, args):
@@ -62,22 +87,17 @@ class List(command_line.OptparseCommand):
       parser.error('Must provide at most one test name.')
 
   def Run(self, args):
-    if args.json:
-      test_list = []
-      for test_class in sorted(args.tests, key=lambda t: t.Name()):
-        test_list.append({
-            'name': test_class.Name(),
-            'description': test_class.Description(),
-            'options': test_class.options,
-        })
-      print json.dumps(test_list)
+    if args.json_output_file:
+      possible_browser = browser_finder.FindBrowser(args)
+      with open(args.json_output_file, 'w') as f:
+        f.write(_GetJsonTestList(possible_browser, args.tests, args.num_shards))
     else:
       _PrintTestList(args.tests)
     return 0
 
 
 class Run(command_line.OptparseCommand):
-  """Run one or more tests"""
+  """Run one or more tests (default)"""
 
   usage = 'test_name [page_set] [<options>]'
 
@@ -89,7 +109,7 @@ class Run(command_line.OptparseCommand):
 
   @classmethod
   def AddCommandLineArgs(cls, parser):
-    test.AddCommandLineArgs(parser)
+    benchmark.AddCommandLineArgs(parser)
 
     # Allow tests to add their own command line options.
     matching_tests = []
@@ -135,21 +155,26 @@ class Run(command_line.OptparseCommand):
       if not os.path.exists(page_set_path):
         parser.error('Page set not found.')
       if not (os.path.isfile(page_set_path) and
-              os.path.splitext(page_set_path)[1] == '.json'):
-        parser.error('Page set is not a JSON file.')
+              discover.IsPageSetFile(page_set_path)):
+        parser.error('Unsupported page set file format.')
 
-      class TestWrapper(test.Test):
+      class TestWrapper(benchmark.Benchmark):
         test = test_class
-        page_set = page_set_path
+
+        @classmethod
+        def CreatePageSet(cls, options):
+          return page_set.PageSet.FromFile(page_set_path)
+
       test_class = TestWrapper
     else:
       if len(args.positional_args) > 1:
         parser.error('Too many arguments.')
 
-    assert issubclass(test_class, test.Test), 'Trying to run a non-Test?!'
+    assert issubclass(test_class, benchmark.Benchmark), (
+        'Trying to run a non-Benchmark?!')
 
+    benchmark.ProcessCommandLineArgs(parser, args)
     test_class.ProcessCommandLineArgs(parser, args)
-    test.ProcessCommandLineArgs(parser, args)
 
     cls._test = test_class
 
@@ -170,16 +195,22 @@ def _Commands():
       continue
     yield cls
 
+def _MatchingCommands(string):
+  return [command for command in _Commands()
+         if command.Name().startswith(string)]
 
+@decorators.Cache
 def _Tests():
-  base_dir = util.GetBaseDir()
-  tests = discover.DiscoverClasses(base_dir, base_dir, test.Test,
-                                   index_by_class_name=True).values()
-  page_tests = discover.DiscoverClasses(base_dir, base_dir, page_test.PageTest,
-                                        index_by_class_name=True).values()
-  page_tests = [test_class for test_class in page_tests
-                if not issubclass(test_class, profile_creator.ProfileCreator)]
-  return tests + page_tests
+  tests = []
+  for base_dir in config.base_paths:
+    tests += discover.DiscoverClasses(base_dir, base_dir, benchmark.Benchmark,
+                                      index_by_class_name=True).values()
+    page_tests = discover.DiscoverClasses(base_dir, base_dir,
+                                          page_test.PageTest,
+                                          index_by_class_name=True).values()
+    tests += [test_class for test_class in page_tests
+              if not issubclass(test_class, profile_creator.ProfileCreator)]
+  return tests
 
 
 def _MatchTestName(input_test_name, exact_matches=True):
@@ -194,8 +225,8 @@ def _MatchTestName(input_test_name, exact_matches=True):
   # Exact matching.
   if exact_matches:
     # Don't add aliases to search dict, only allow exact matching for them.
-    if input_test_name in test_aliases:
-      exact_match = test_aliases[input_test_name]
+    if input_test_name in config.test_aliases:
+      exact_match = config.test_aliases[input_test_name]
     else:
       exact_match = input_test_name
 
@@ -208,6 +239,48 @@ def _MatchTestName(input_test_name, exact_matches=True):
           if _Matches(input_test_name, test_class.Name())]
 
 
+def _GetJsonTestList(possible_browser, test_classes, num_shards):
+  """Returns a list of all enabled tests in a JSON format expected by buildbots.
+
+  JSON format (see build/android/pylib/perf/test_runner.py):
+  { "version": int,
+    "steps": {
+      "foo": {
+        "device_affinity": int,
+        "cmd": "script_to_execute foo"
+      },
+      "bar": {
+        "device_affinity": int,
+        "cmd": "script_to_execute bar"
+      }
+    }
+  }
+  """
+  output = {
+    'version': 1,
+    'steps': {
+    }
+  }
+  for test_class in test_classes:
+    if not issubclass(test_class, benchmark.Benchmark):
+      continue
+    if not decorators.IsEnabled(test_class, possible_browser):
+      continue
+    name = test_class.Name()
+    output['steps'][name] = {
+      'cmd': ' '.join([sys.executable, os.path.realpath(sys.argv[0]),
+                       '--browser=%s' % possible_browser.browser_type,
+                       '-v', '--output-format=buildbot', name]),
+      # TODO(tonyg): Currently we set the device affinity to a stable hash of
+      # the test name. This somewhat evenly distributes benchmarks among the
+      # requested number of shards. However, it is far from optimal in terms of
+      # cycle time. We should add a test size decorator (e.g. small, medium,
+      # large) and let that inform sharding.
+      'device_affinity': int(hashlib.sha1(name).hexdigest(), 16) % num_shards
+    }
+  return json.dumps(output, indent=2, sort_keys=True)
+
+
 def _PrintTestList(tests):
   if not tests:
     print >> sys.stderr, 'No tests found!'
@@ -217,7 +290,7 @@ def _PrintTestList(tests):
   format_string = '  %%-%ds %%s' % max(len(t.Name()) for t in tests)
 
   filtered_tests = [test_class for test_class in tests
-                    if issubclass(test_class, test.Test)]
+                    if issubclass(test_class, benchmark.Benchmark)]
   if filtered_tests:
     print >> sys.stderr, 'Available tests are:'
     for test_class in sorted(filtered_tests, key=lambda t: t.Name()):
@@ -235,10 +308,10 @@ def _PrintTestList(tests):
     print >> sys.stderr
 
 
-test_aliases = {}
+config = environment.Environment([util.GetBaseDir()])
 
 
-def Main():
+def main():
   # Get the command name from the command line.
   if len(sys.argv) > 1 and sys.argv[1] == '--help':
     sys.argv[1] = 'help'
@@ -250,8 +323,7 @@ def Main():
       break
 
   # Validate and interpret the command name.
-  commands = [command for command in _Commands()
-              if command.Name().startswith(command_name)]
+  commands = _MatchingCommands(command_name)
   if len(commands) > 1:
     print >> sys.stderr, ('"%s" is not a %s command. Did you mean one of these?'
                           % (command_name, _ScriptName()))