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.
10 from telemetry import decorators
11 from telemetry.core import camel_case
12 from telemetry.core import util
13 from telemetry.page import page_set
17 def DiscoverModules(start_dir, top_level_dir, pattern='*'):
18 """Discover all modules in |start_dir| which match |pattern|.
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.
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('_'):
34 if os.path.splitext(filename)[1] != '.py':
36 if not fnmatch.fnmatch(filename, pattern):
40 module_rel_path = os.path.relpath(os.path.join(dir_path, filename),
42 module_name = re.sub(r'[/\\]', '.', os.path.splitext(module_rel_path)[0])
45 module = __import__(module_name, fromlist=[True])
47 modules.append(module)
51 # TODO(dtu): Normalize all discoverable classes to have corresponding module
52 # and class names, then always index by class name.
54 def 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|.
58 Base classes that contain subclasses are ignored by default.
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.
69 dict of {module_name: class} or {underscored_class_name: class}
71 modules = DiscoverModules(start_dir, top_level_dir, pattern)
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())
80 def DiscoverClassesInModule(module, base_class, index_by_class_name=False):
81 """Discover all classes in |module| which subclass |base_class|.
83 Base classes that contain subclasses are ignored by default.
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.
92 dict of {module_name: class} or {underscored_class_name: class}
95 for _, obj in inspect.getmembers(module):
96 # Ensure object is a class.
97 if not inspect.isclass(obj):
99 # Include only subclasses of base_class.
100 if not issubclass(obj, base_class):
102 # Exclude the base_class itself.
103 if obj is base_class:
105 # Exclude protected or private classes.
106 if obj.__name__.startswith('_'):
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__:
113 if index_by_class_name:
114 key_name = camel_case.ToUnderscore(obj.__name__)
116 key_name = module.__name__.split('.')[-1]
117 classes[key_name] = obj
123 def _GetUniqueModuleName():
125 return "module_" + str(_counter[0])
128 def 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:
132 if ext_name != '.py':
134 module = util.GetPythonPageSetModule(file_path)
135 return bool(DiscoverClassesInModule(module, page_set.PageSet))
138 def GetAllPageSetFilenames(dir_path):
140 for sub_path, _, filenames in os.walk(dir_path):
142 if f.startswith('.'):
144 filename = os.path.join(sub_path, f)
145 if IsPageSetFile(filename):
146 results.append(filename)