| 1 | """ |
|---|
| 2 | Test Selection |
|---|
| 3 | -------------- |
|---|
| 4 | |
|---|
| 5 | Test selection is handled by a Selector. The test loader calls the |
|---|
| 6 | appropriate selector method for each object it encounters that it |
|---|
| 7 | thinks may be a test. |
|---|
| 8 | """ |
|---|
| 9 | import logging |
|---|
| 10 | import os |
|---|
| 11 | import unittest |
|---|
| 12 | from nose.config import Config |
|---|
| 13 | from nose.util import split_test_name, src, getfilename, getpackage, ispackage |
|---|
| 14 | |
|---|
| 15 | log = logging.getLogger(__name__) |
|---|
| 16 | |
|---|
| 17 | __all__ = ['Selector', 'defaultSelector', 'TestAddress'] |
|---|
| 18 | |
|---|
| 19 | |
|---|
| 20 | # for efficiency and easier mocking |
|---|
| 21 | op_join = os.path.join |
|---|
| 22 | op_basename = os.path.basename |
|---|
| 23 | op_exists = os.path.exists |
|---|
| 24 | op_splitext = os.path.splitext |
|---|
| 25 | op_isabs = os.path.isabs |
|---|
| 26 | op_abspath = os.path.abspath |
|---|
| 27 | |
|---|
| 28 | |
|---|
| 29 | class Selector(object): |
|---|
| 30 | """Core test selector. Examines test candidates and determines whether, |
|---|
| 31 | given the specified configuration, the test candidate should be selected |
|---|
| 32 | as a test. |
|---|
| 33 | """ |
|---|
| 34 | def __init__(self, config): |
|---|
| 35 | if config is None: |
|---|
| 36 | config = Config() |
|---|
| 37 | self.configure(config) |
|---|
| 38 | |
|---|
| 39 | def configure(self, config): |
|---|
| 40 | self.config = config |
|---|
| 41 | self.exclude = config.exclude |
|---|
| 42 | self.ignoreFiles = config.ignoreFiles |
|---|
| 43 | self.include = config.include |
|---|
| 44 | self.plugins = config.plugins |
|---|
| 45 | self.match = config.testMatch |
|---|
| 46 | |
|---|
| 47 | def matches(self, name): |
|---|
| 48 | """Does the name match my requirements? |
|---|
| 49 | |
|---|
| 50 | To match, a name must match config.testMatch OR config.include |
|---|
| 51 | and it must not match config.exclude |
|---|
| 52 | """ |
|---|
| 53 | return ((self.match.search(name) |
|---|
| 54 | or (self.include and |
|---|
| 55 | filter(None, |
|---|
| 56 | [inc.search(name) for inc in self.include]))) |
|---|
| 57 | and ((not self.exclude) |
|---|
| 58 | or not filter(None, |
|---|
| 59 | [exc.search(name) for exc in self.exclude]) |
|---|
| 60 | )) |
|---|
| 61 | |
|---|
| 62 | def wantClass(self, cls): |
|---|
| 63 | """Is the class a wanted test class? |
|---|
| 64 | |
|---|
| 65 | A class must be a unittest.TestCase subclass, or match test name |
|---|
| 66 | requirements. Classes that start with _ are always excluded. |
|---|
| 67 | """ |
|---|
| 68 | declared = getattr(cls, '__test__', None) |
|---|
| 69 | if declared is not None: |
|---|
| 70 | wanted = declared |
|---|
| 71 | else: |
|---|
| 72 | wanted = (not cls.__name__.startswith('_') |
|---|
| 73 | and (issubclass(cls, unittest.TestCase) |
|---|
| 74 | or self.matches(cls.__name__))) |
|---|
| 75 | |
|---|
| 76 | plug_wants = self.plugins.wantClass(cls) |
|---|
| 77 | if plug_wants is not None: |
|---|
| 78 | log.debug("Plugin setting selection of %s to %s", cls, plug_wants) |
|---|
| 79 | wanted = plug_wants |
|---|
| 80 | log.debug("wantClass %s? %s", cls, wanted) |
|---|
| 81 | return wanted |
|---|
| 82 | |
|---|
| 83 | def wantDirectory(self, dirname): |
|---|
| 84 | """Is the directory a wanted test directory? |
|---|
| 85 | |
|---|
| 86 | All package directories match, so long as they do not match exclude. |
|---|
| 87 | All other directories must match test requirements. |
|---|
| 88 | """ |
|---|
| 89 | tail = op_basename(dirname) |
|---|
| 90 | if ispackage(dirname): |
|---|
| 91 | wanted = (not self.exclude |
|---|
| 92 | or not filter(None, |
|---|
| 93 | [exc.search(tail) for exc in self.exclude] |
|---|
| 94 | )) |
|---|
| 95 | else: |
|---|
| 96 | wanted = (self.matches(tail) |
|---|
| 97 | or (self.config.srcDirs |
|---|
| 98 | and tail in self.config.srcDirs)) |
|---|
| 99 | plug_wants = self.plugins.wantDirectory(dirname) |
|---|
| 100 | if plug_wants is not None: |
|---|
| 101 | log.debug("Plugin setting selection of %s to %s", |
|---|
| 102 | dirname, plug_wants) |
|---|
| 103 | wanted = plug_wants |
|---|
| 104 | log.debug("wantDirectory %s? %s", dirname, wanted) |
|---|
| 105 | return wanted |
|---|
| 106 | |
|---|
| 107 | def wantFile(self, file): |
|---|
| 108 | """Is the file a wanted test file? |
|---|
| 109 | |
|---|
| 110 | The file must be a python source file and match testMatch or |
|---|
| 111 | include, and not match exclude. Files that match ignore are *never* |
|---|
| 112 | wanted, regardless of plugin, testMatch, include or exclude settings. |
|---|
| 113 | """ |
|---|
| 114 | # never, ever load files that match anything in ignore |
|---|
| 115 | # (.* _* and *setup*.py by default) |
|---|
| 116 | base = op_basename(file) |
|---|
| 117 | ignore_matches = [ ignore_this for ignore_this in self.ignoreFiles |
|---|
| 118 | if ignore_this.search(base) ] |
|---|
| 119 | if ignore_matches: |
|---|
| 120 | log.debug('%s matches ignoreFiles pattern; skipped', |
|---|
| 121 | base) |
|---|
| 122 | return False |
|---|
| 123 | if not self.config.includeExe and os.access(file, os.X_OK): |
|---|
| 124 | log.info('%s is executable; skipped', file) |
|---|
| 125 | return False |
|---|
| 126 | dummy, ext = op_splitext(base) |
|---|
| 127 | pysrc = ext == '.py' |
|---|
| 128 | |
|---|
| 129 | wanted = pysrc and self.matches(base) |
|---|
| 130 | plug_wants = self.plugins.wantFile(file) |
|---|
| 131 | if plug_wants is not None: |
|---|
| 132 | log.debug("plugin setting want %s to %s", file, plug_wants) |
|---|
| 133 | wanted = plug_wants |
|---|
| 134 | log.debug("wantFile %s? %s", file, wanted) |
|---|
| 135 | return wanted |
|---|
| 136 | |
|---|
| 137 | def wantFunction(self, function): |
|---|
| 138 | """Is the function a test function? |
|---|
| 139 | """ |
|---|
| 140 | try: |
|---|
| 141 | if hasattr(function, 'compat_func_name'): |
|---|
| 142 | funcname = function.compat_func_name |
|---|
| 143 | else: |
|---|
| 144 | funcname = function.__name__ |
|---|
| 145 | except AttributeError: |
|---|
| 146 | # not a function |
|---|
| 147 | return False |
|---|
| 148 | declared = getattr(function, '__test__', None) |
|---|
| 149 | if declared is not None: |
|---|
| 150 | wanted = declared |
|---|
| 151 | else: |
|---|
| 152 | wanted = not funcname.startswith('_') and self.matches(funcname) |
|---|
| 153 | plug_wants = self.plugins.wantFunction(function) |
|---|
| 154 | if plug_wants is not None: |
|---|
| 155 | wanted = plug_wants |
|---|
| 156 | log.debug("wantFunction %s? %s", function, wanted) |
|---|
| 157 | return wanted |
|---|
| 158 | |
|---|
| 159 | def wantMethod(self, method): |
|---|
| 160 | """Is the method a test method? |
|---|
| 161 | """ |
|---|
| 162 | try: |
|---|
| 163 | method_name = method.__name__ |
|---|
| 164 | except AttributeError: |
|---|
| 165 | # not a method |
|---|
| 166 | return False |
|---|
| 167 | if method_name.startswith('_'): |
|---|
| 168 | # never collect 'private' methods |
|---|
| 169 | return False |
|---|
| 170 | declared = getattr(method, '__test__', None) |
|---|
| 171 | if declared is not None: |
|---|
| 172 | wanted = declared |
|---|
| 173 | else: |
|---|
| 174 | wanted = self.matches(method_name) |
|---|
| 175 | plug_wants = self.plugins.wantMethod(method) |
|---|
| 176 | if plug_wants is not None: |
|---|
| 177 | wanted = plug_wants |
|---|
| 178 | log.debug("wantMethod %s? %s", method, wanted) |
|---|
| 179 | return wanted |
|---|
| 180 | |
|---|
| 181 | def wantModule(self, module): |
|---|
| 182 | """Is the module a test module? |
|---|
| 183 | |
|---|
| 184 | The tail of the module name must match test requirements. One exception: |
|---|
| 185 | we always want __main__. |
|---|
| 186 | """ |
|---|
| 187 | declared = getattr(module, '__test__', None) |
|---|
| 188 | if declared is not None: |
|---|
| 189 | wanted = declared |
|---|
| 190 | else: |
|---|
| 191 | wanted = self.matches(module.__name__.split('.')[-1]) \ |
|---|
| 192 | or module.__name__ == '__main__' |
|---|
| 193 | plug_wants = self.plugins.wantModule(module) |
|---|
| 194 | if plug_wants is not None: |
|---|
| 195 | wanted = plug_wants |
|---|
| 196 | log.debug("wantModule %s? %s", module, wanted) |
|---|
| 197 | return wanted |
|---|
| 198 | |
|---|
| 199 | defaultSelector = Selector |
|---|
| 200 | |
|---|
| 201 | |
|---|
| 202 | class TestAddress(object): |
|---|
| 203 | """A test address represents a user's request to run a particular |
|---|
| 204 | test. The user may specify a filename or module (or neither), |
|---|
| 205 | and/or a callable (a class, function, or method). The naming |
|---|
| 206 | format for test addresses is: |
|---|
| 207 | |
|---|
| 208 | filename_or_module:callable |
|---|
| 209 | |
|---|
| 210 | Filenames that are not absolute will be made absolute relative to |
|---|
| 211 | the working dir. |
|---|
| 212 | |
|---|
| 213 | The filename or module part will be considered a module name if it |
|---|
| 214 | doesn't look like a file, that is, if it doesn't exist on the file |
|---|
| 215 | system and it doesn't contain any directory separators and it |
|---|
| 216 | doesn't end in .py. |
|---|
| 217 | |
|---|
| 218 | Callables may be a class name, function name, method name, or |
|---|
| 219 | class.method specification. |
|---|
| 220 | """ |
|---|
| 221 | def __init__(self, name, workingDir=None): |
|---|
| 222 | if workingDir is None: |
|---|
| 223 | workingDir = os.getcwd() |
|---|
| 224 | self.name = name |
|---|
| 225 | self.workingDir = workingDir |
|---|
| 226 | self.filename, self.module, self.call = split_test_name(name) |
|---|
| 227 | log.debug('Test name %s resolved to file %s, module %s, call %s', |
|---|
| 228 | name, self.filename, self.module, self.call) |
|---|
| 229 | if self.filename is None: |
|---|
| 230 | if self.module is not None: |
|---|
| 231 | self.filename = getfilename(self.module, self.workingDir) |
|---|
| 232 | if self.filename: |
|---|
| 233 | self.filename = src(self.filename) |
|---|
| 234 | if not op_isabs(self.filename): |
|---|
| 235 | self.filename = op_abspath(op_join(workingDir, |
|---|
| 236 | self.filename)) |
|---|
| 237 | if self.module is None: |
|---|
| 238 | self.module = getpackage(self.filename) |
|---|
| 239 | log.debug( |
|---|
| 240 | 'Final resolution of test name %s: file %s module %s call %s', |
|---|
| 241 | name, self.filename, self.module, self.call) |
|---|
| 242 | |
|---|
| 243 | def totuple(self): |
|---|
| 244 | return (self.filename, self.module, self.call) |
|---|
| 245 | |
|---|
| 246 | def __str__(self): |
|---|
| 247 | return self.name |
|---|
| 248 | |
|---|
| 249 | def __repr__(self): |
|---|
| 250 | return "%s: (%s, %s, %s)" % (self.name, self.filename, |
|---|
| 251 | self.module, self.call) |
|---|