• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 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.
4
5import fnmatch
6import inspect
7import os
8import re
9
10from telemetry import decorators
11from telemetry.core import camel_case
12from telemetry.core import util
13from telemetry.page import page_set
14
15
16@decorators.Cache
17def DiscoverModules(start_dir, top_level_dir, pattern='*'):
18  """Discover all modules in |start_dir| which match |pattern|.
19
20  Args:
21    start_dir: The directory to recursively search.
22    top_level_dir: The top level of the package, for importing.
23    pattern: Unix shell-style pattern for filtering the filenames to import.
24
25  Returns:
26    list of modules.
27  """
28  modules = []
29  for dir_path, _, filenames in os.walk(start_dir):
30    for filename in filenames:
31      # Filter out unwanted filenames.
32      if filename.startswith('.') or filename.startswith('_'):
33        continue
34      if os.path.splitext(filename)[1] != '.py':
35        continue
36      if not fnmatch.fnmatch(filename, pattern):
37        continue
38
39      # Find the module.
40      module_rel_path = os.path.relpath(os.path.join(dir_path, filename),
41                                        top_level_dir)
42      module_name = re.sub(r'[/\\]', '.', os.path.splitext(module_rel_path)[0])
43
44      # Import the module.
45      module = __import__(module_name, fromlist=[True])
46
47      modules.append(module)
48  return modules
49
50
51# TODO(dtu): Normalize all discoverable classes to have corresponding module
52# and class names, then always index by class name.
53@decorators.Cache
54def DiscoverClasses(start_dir, top_level_dir, base_class, pattern='*',
55                    index_by_class_name=False):
56  """Discover all classes in |start_dir| which subclass |base_class|.
57
58  Base classes that contain subclasses are ignored by default.
59
60  Args:
61    start_dir: The directory to recursively search.
62    top_level_dir: The top level of the package, for importing.
63    base_class: The base class to search for.
64    pattern: Unix shell-style pattern for filtering the filenames to import.
65    index_by_class_name: If True, use class name converted to
66        lowercase_with_underscores instead of module name in return dict keys.
67
68  Returns:
69    dict of {module_name: class} or {underscored_class_name: class}
70  """
71  modules = DiscoverModules(start_dir, top_level_dir, pattern)
72  classes = {}
73  for module in modules:
74    new_classes = DiscoverClassesInModule(
75        module, base_class, index_by_class_name)
76    classes = dict(classes.items() + new_classes.items())
77  return classes
78
79@decorators.Cache
80def DiscoverClassesInModule(module, base_class, index_by_class_name=False):
81  """Discover all classes in |module| which subclass |base_class|.
82
83  Base classes that contain subclasses are ignored by default.
84
85  Args:
86    module: The module to search.
87    base_class: The base class to search for.
88    index_by_class_name: If True, use class name converted to
89        lowercase_with_underscores instead of module name in return dict keys.
90
91  Returns:
92    dict of {module_name: class} or {underscored_class_name: class}
93  """
94  classes = {}
95  for _, obj in inspect.getmembers(module):
96    # Ensure object is a class.
97    if not inspect.isclass(obj):
98      continue
99    # Include only subclasses of base_class.
100    if not issubclass(obj, base_class):
101      continue
102    # Exclude the base_class itself.
103    if obj is base_class:
104      continue
105    # Exclude protected or private classes.
106    if obj.__name__.startswith('_'):
107      continue
108    # Include only the module in which the class is defined.
109    # If a class is imported by another module, exclude those duplicates.
110    if obj.__module__ != module.__name__:
111      continue
112
113    if index_by_class_name:
114      key_name = camel_case.ToUnderscore(obj.__name__)
115    else:
116      key_name = module.__name__.split('.')[-1]
117    classes[key_name] = obj
118
119  return classes
120
121
122_counter = [0]
123def _GetUniqueModuleName():
124  _counter[0] += 1
125  return "module_" + str(_counter[0])
126
127
128def IsPageSetFile(file_path):
129  root_name, ext_name = os.path.splitext(file_path)
130  if 'unittest' in root_name or 'page_sets/data' in root_name:
131    return False
132  if ext_name != '.py':
133    return False
134  module = util.GetPythonPageSetModule(file_path)
135  return bool(DiscoverClassesInModule(module, page_set.PageSet))
136