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