|
5 | 5 | ~~~~~~~~~~~ |
6 | 6 |
|
7 | 7 | """ |
8 | | - |
9 | 8 | from __future__ import absolute_import, division, print_function, \ |
10 | 9 | with_statement, unicode_literals |
11 | 10 |
|
12 | | -from ..server import Server |
13 | | -t = Server() |
14 | | -t.socket_name = 'tmuxp_test' |
| 11 | +import logging |
| 12 | +import unittest |
| 13 | +import sys |
15 | 14 |
|
16 | 15 | from .. import log |
17 | | -import logging |
| 16 | +from .._compat import string_types, PY2, reraise |
| 17 | +from ..server import Server |
| 18 | + |
| 19 | +import pkgutil |
| 20 | + |
18 | 21 | logger = logging.getLogger() |
19 | 22 |
|
| 23 | +t = Server() |
| 24 | +t.socket_name = 'tmuxp_test' |
20 | 25 |
|
21 | 26 | if not logger.handlers: |
22 | 27 | channel = logging.StreamHandler() |
|
30 | 35 | testsuite_logger.setLevel('INFO') |
31 | 36 |
|
32 | 37 |
|
33 | | -def suite(): |
34 | | - """Return TestSuite.""" |
| 38 | +class ImportStringError(ImportError): |
| 39 | + """Provides information about a failed :func:`import_string` attempt.""" |
| 40 | + |
| 41 | + #: String in dotted notation that failed to be imported. |
| 42 | + import_name = None |
| 43 | + #: Wrapped exception. |
| 44 | + exception = None |
| 45 | + |
| 46 | + def __init__(self, import_name, exception): |
| 47 | + self.import_name = import_name |
| 48 | + self.exception = exception |
| 49 | + |
| 50 | + msg = ( |
| 51 | + 'import_string() failed for %r. Possible reasons are:\n\n' |
| 52 | + '- missing __init__.py in a package;\n' |
| 53 | + '- package or module path not included in sys.path;\n' |
| 54 | + '- duplicated package or module name taking precedence in ' |
| 55 | + 'sys.path;\n' |
| 56 | + '- missing module, class, function or variable;\n\n' |
| 57 | + 'Debugged import:\n\n%s\n\n' |
| 58 | + 'Original exception:\n\n%s: %s') |
| 59 | + |
| 60 | + name = '' |
| 61 | + tracked = [] |
| 62 | + for part in import_name.replace(':', '.').split('.'): |
| 63 | + name += (name and '.') + part |
| 64 | + imported = import_string(name, silent=True) |
| 65 | + if imported: |
| 66 | + tracked.append((name, getattr(imported, '__file__', None))) |
| 67 | + else: |
| 68 | + track = ['- %r found in %r.' % (n, i) for n, i in tracked] |
| 69 | + track.append('- %r not found.' % name) |
| 70 | + msg = msg % (import_name, '\n'.join(track), |
| 71 | + exception.__class__.__name__, str(exception)) |
| 72 | + break |
| 73 | + |
| 74 | + ImportError.__init__(self, msg) |
| 75 | + |
| 76 | + def __repr__(self): |
| 77 | + return '<%s(%r, %r)>' % (self.__class__.__name__, self.import_name, |
| 78 | + self.exception) |
| 79 | + |
| 80 | + |
| 81 | +def import_string(import_name, silent=False): |
| 82 | + """Imports an object based on a string. This is useful if you want to |
| 83 | + use import paths as endpoints or something similar. An import path can |
| 84 | + be specified either in dotted notation (``xml.sax.saxutils.escape``) |
| 85 | + or with a colon as object delimiter (``xml.sax.saxutils:escape``). |
| 86 | +
|
| 87 | + If `silent` is True the return value will be `None` if the import fails. |
| 88 | +
|
| 89 | + :param import_name: the dotted name for the object to import. |
| 90 | + :param silent: if set to `True` import errors are ignored and |
| 91 | + `None` is returned instead. |
| 92 | + :return: imported object |
| 93 | + """ |
| 94 | + #XXX: py3 review needed |
| 95 | + assert isinstance(import_name, string_types) |
| 96 | + # force the import name to automatically convert to strings |
| 97 | + import_name = str(import_name) |
35 | 98 | try: |
36 | | - import unittest2 as unittest |
37 | | - except ImportError: # Python 2.7 |
38 | | - import unittest |
| 99 | + if ':' in import_name: |
| 100 | + module, obj = import_name.split(':', 1) |
| 101 | + elif '.' in import_name: |
| 102 | + module, obj = import_name.rsplit('.', 1) |
| 103 | + else: |
| 104 | + return __import__(import_name) |
| 105 | + # __import__ is not able to handle unicode strings in the fromlist |
| 106 | + # if the module is a package |
| 107 | + if PY2 and isinstance(obj, unicode): |
| 108 | + obj = obj.encode('utf-8') |
| 109 | + try: |
| 110 | + return getattr(__import__(module, None, None, [obj]), obj) |
| 111 | + except (ImportError, AttributeError): |
| 112 | + # support importing modules not yet set up by the parent module |
| 113 | + # (or package for that matter) |
| 114 | + modname = module + '.' + obj |
| 115 | + __import__(modname) |
| 116 | + return sys.modules[modname] |
| 117 | + except ImportError as e: |
| 118 | + if not silent: |
| 119 | + reraise( |
| 120 | + ImportStringError, |
| 121 | + ImportStringError(import_name, e), |
| 122 | + sys.exc_info()[2]) |
| 123 | + |
| 124 | + |
| 125 | +def find_modules(import_path, include_packages=False, recursive=False): |
| 126 | + """Find all the modules below a package. This can be useful to |
| 127 | + automatically import all views / controllers so that their metaclasses / |
| 128 | + function decorators have a chance to register themselves on the |
| 129 | + application. |
| 130 | +
|
| 131 | + Packages are not returned unless `include_packages` is `True`. This can |
| 132 | + also recursively list modules but in that case it will import all the |
| 133 | + packages to get the correct load path of that module. |
| 134 | +
|
| 135 | + :param import_name: the dotted name for the package to find child modules. |
| 136 | + :param include_packages: set to `True` if packages should be returned, too. |
| 137 | + :param recursive: set to `True` if recursion should happen. |
| 138 | + :return: generator |
| 139 | + """ |
| 140 | + module = import_string(import_path) |
| 141 | + path = getattr(module, '__path__', None) |
| 142 | + if path is None: |
| 143 | + raise ValueError('%r is not a package' % import_path) |
| 144 | + basename = module.__name__ + '.' |
| 145 | + for importer, modname, ispkg in pkgutil.iter_modules(path): |
| 146 | + modname = basename + modname |
| 147 | + if ispkg: |
| 148 | + if include_packages: |
| 149 | + yield modname |
| 150 | + if recursive: |
| 151 | + for item in find_modules(modname, include_packages, True): |
| 152 | + yield item |
| 153 | + else: |
| 154 | + yield modname |
| 155 | + |
| 156 | + |
| 157 | +def iter_suites(package): |
| 158 | + """Yields all testsuites.""" |
| 159 | + for module in find_modules(package, include_packages=True): |
| 160 | + mod = __import__(module, fromlist=['*']) |
| 161 | + if hasattr(mod, 'suite'): |
| 162 | + yield mod.suite() |
| 163 | + |
| 164 | + |
| 165 | +def find_all_tests(suite): |
| 166 | + """Yields all the tests and their names from a given suite.""" |
| 167 | + suites = [suite] |
| 168 | + while suites: |
| 169 | + s = suites.pop() |
| 170 | + try: |
| 171 | + suites.extend(s) |
| 172 | + except TypeError: |
| 173 | + yield s, '%s.%s.%s' % ( |
| 174 | + s.__class__.__module__, |
| 175 | + s.__class__.__name__, |
| 176 | + s._testMethodName |
| 177 | + ) |
| 178 | + |
| 179 | + |
| 180 | +class BetterLoader(unittest.TestLoader): |
| 181 | + """A nicer loader that solves two problems. First of all we are setting |
| 182 | + up tests from different sources and we're doing this programmatically |
| 183 | + which breaks the default loading logic so this is required anyways. |
| 184 | + Secondly this loader has a nicer interpolation for test names than the |
| 185 | + default one so you can just do ``run-tests.py ViewTestCase`` and it |
| 186 | + will work. |
| 187 | + """ |
39 | 188 |
|
40 | | - return unittest.TestLoader().discover('.', pattern="test_*.py") |
| 189 | + def getRootSuite(self): |
| 190 | + return suite() |
| 191 | + |
| 192 | + def loadTestsFromName(self, name, module=None): |
| 193 | + root = self.getRootSuite() |
| 194 | + if name == 'suite': |
| 195 | + return root |
| 196 | + |
| 197 | + all_tests = [] |
| 198 | + for testcase, testname in find_all_tests(root): |
| 199 | + if testname == name or \ |
| 200 | + testname.endswith('.' + name) or \ |
| 201 | + ('.' + name + '.') in testname or \ |
| 202 | + testname.startswith(name + '.'): |
| 203 | + all_tests.append(testcase) |
| 204 | + |
| 205 | + if not all_tests: |
| 206 | + raise LookupError('could not find test case for "%s"' % name) |
| 207 | + |
| 208 | + if len(all_tests) == 1: |
| 209 | + return all_tests[0] |
| 210 | + rv = unittest.TestSuite() |
| 211 | + for test in all_tests: |
| 212 | + rv.addTest(test) |
| 213 | + return rv |
| 214 | + |
| 215 | + |
| 216 | +def suite(): |
| 217 | + """A testsuite that has all the Flask tests. You can use this |
| 218 | + function to integrate the Flask tests into your own testsuite |
| 219 | + in case you want to test that monkeypatches to Flask do not |
| 220 | + break it. |
| 221 | + """ |
| 222 | + suite = unittest.TestSuite() |
| 223 | + for other_suite in iter_suites(__name__): |
| 224 | + suite.addTest(other_suite) |
| 225 | + return suite |
| 226 | + |
| 227 | + |
| 228 | +def main(): |
| 229 | + """Runs the testsuite as command line application.""" |
| 230 | + try: |
| 231 | + unittest.main(testLoader=BetterLoader(), defaultTest='suite') |
| 232 | + except Exception: |
| 233 | + import sys |
| 234 | + import traceback |
| 235 | + traceback.print_exc() |
| 236 | + sys.exit(1) |
0 commit comments