OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 |
| 3 import os |
| 4 import sys |
| 5 import re |
| 6 import gc |
| 7 import locale |
| 8 import shutil |
| 9 import time |
| 10 import unittest |
| 11 import doctest |
| 12 import operator |
| 13 import subprocess |
| 14 import tempfile |
| 15 import traceback |
| 16 import warnings |
| 17 |
| 18 try: |
| 19 import platform |
| 20 IS_PYPY = platform.python_implementation() == 'PyPy' |
| 21 IS_CPYTHON = platform.python_implementation() == 'CPython' |
| 22 except (ImportError, AttributeError): |
| 23 IS_CPYTHON = True |
| 24 IS_PYPY = False |
| 25 |
| 26 try: |
| 27 from StringIO import StringIO |
| 28 except ImportError: |
| 29 from io import StringIO |
| 30 |
| 31 try: |
| 32 import cPickle as pickle |
| 33 except ImportError: |
| 34 import pickle |
| 35 |
| 36 try: |
| 37 from io import open as io_open |
| 38 except ImportError: |
| 39 from codecs import open as io_open |
| 40 |
| 41 try: |
| 42 import threading |
| 43 except ImportError: # No threads, no problems |
| 44 threading = None |
| 45 |
| 46 try: |
| 47 from collections import defaultdict |
| 48 except ImportError: |
| 49 class defaultdict(object): |
| 50 def __init__(self, default_factory=lambda : None): |
| 51 self._dict = {} |
| 52 self.default_factory = default_factory |
| 53 def __getitem__(self, key): |
| 54 if key not in self._dict: |
| 55 self._dict[key] = self.default_factory() |
| 56 return self._dict[key] |
| 57 def __setitem__(self, key, value): |
| 58 self._dict[key] = value |
| 59 def __contains__(self, key): |
| 60 return key in self._dict |
| 61 def __repr__(self): |
| 62 return repr(self._dict) |
| 63 def __nonzero__(self): |
| 64 return bool(self._dict) |
| 65 |
| 66 try: |
| 67 basestring |
| 68 except NameError: |
| 69 basestring = str |
| 70 |
| 71 WITH_CYTHON = True |
| 72 CY3_DIR = None |
| 73 |
| 74 from distutils.dist import Distribution |
| 75 from distutils.core import Extension |
| 76 from distutils.command.build_ext import build_ext as _build_ext |
| 77 from distutils import sysconfig |
| 78 distutils_distro = Distribution() |
| 79 |
| 80 |
| 81 if sys.platform == 'win32': |
| 82 # TODO: Figure out why this hackery (see http://thread.gmane.org/gmane.comp.
python.cython.devel/8280/). |
| 83 config_files = distutils_distro.find_config_files() |
| 84 try: config_files.remove('setup.cfg') |
| 85 except ValueError: pass |
| 86 distutils_distro.parse_config_files(config_files) |
| 87 |
| 88 cfgfiles = distutils_distro.find_config_files() |
| 89 try: cfgfiles.remove('setup.cfg') |
| 90 except ValueError: pass |
| 91 distutils_distro.parse_config_files(cfgfiles) |
| 92 |
| 93 EXT_DEP_MODULES = { |
| 94 'tag:numpy' : 'numpy', |
| 95 'tag:pstats': 'pstats', |
| 96 'tag:posix' : 'posix', |
| 97 'tag:array' : 'array', |
| 98 } |
| 99 |
| 100 def patch_inspect_isfunction(): |
| 101 import inspect |
| 102 orig_isfunction = inspect.isfunction |
| 103 def isfunction(obj): |
| 104 return orig_isfunction(obj) or type(obj).__name__ == 'cython_function_or
_method' |
| 105 isfunction._orig_isfunction = orig_isfunction |
| 106 inspect.isfunction = isfunction |
| 107 |
| 108 def unpatch_inspect_isfunction(): |
| 109 import inspect |
| 110 try: |
| 111 orig_isfunction = inspect.isfunction._orig_isfunction |
| 112 except AttributeError: |
| 113 pass |
| 114 else: |
| 115 inspect.isfunction = orig_isfunction |
| 116 |
| 117 def update_linetrace_extension(ext): |
| 118 ext.define_macros.append(('CYTHON_TRACE', 1)) |
| 119 return ext |
| 120 |
| 121 def update_numpy_extension(ext): |
| 122 import numpy |
| 123 from numpy.distutils.misc_util import get_info |
| 124 |
| 125 ext.include_dirs.append(numpy.get_include()) |
| 126 |
| 127 # We need the npymath library for numpy.math. |
| 128 # This is typically a static-only library. |
| 129 for attr, value in get_info('npymath').items(): |
| 130 getattr(ext, attr).extend(value) |
| 131 |
| 132 def update_openmp_extension(ext): |
| 133 ext.openmp = True |
| 134 language = ext.language |
| 135 |
| 136 if language == 'cpp': |
| 137 flags = OPENMP_CPP_COMPILER_FLAGS |
| 138 else: |
| 139 flags = OPENMP_C_COMPILER_FLAGS |
| 140 |
| 141 if flags: |
| 142 compile_flags, link_flags = flags |
| 143 |
| 144 ext.extra_compile_args.extend(compile_flags.split()) |
| 145 ext.extra_link_args.extend(link_flags.split()) |
| 146 return ext |
| 147 elif sys.platform == 'win32': |
| 148 return ext |
| 149 |
| 150 return EXCLUDE_EXT |
| 151 |
| 152 def get_openmp_compiler_flags(language): |
| 153 """ |
| 154 As of gcc 4.2, it supports OpenMP 2.5. Gcc 4.4 implements 3.0. We don't |
| 155 (currently) check for other compilers. |
| 156 |
| 157 returns a two-tuple of (CFLAGS, LDFLAGS) to build the OpenMP extension |
| 158 """ |
| 159 if language == 'cpp': |
| 160 cc = sysconfig.get_config_var('CXX') |
| 161 else: |
| 162 cc = sysconfig.get_config_var('CC') |
| 163 |
| 164 if not cc: |
| 165 if sys.platform == 'win32': |
| 166 return '/openmp', '' |
| 167 return None |
| 168 |
| 169 # For some reason, cc can be e.g. 'gcc -pthread' |
| 170 cc = cc.split()[0] |
| 171 |
| 172 # Force english output |
| 173 env = os.environ.copy() |
| 174 env['LC_MESSAGES'] = 'C' |
| 175 |
| 176 matcher = re.compile(r"gcc version (\d+\.\d+)").search |
| 177 try: |
| 178 p = subprocess.Popen([cc, "-v"], stderr=subprocess.PIPE, env=env) |
| 179 except EnvironmentError: |
| 180 # Be compatible with Python 3 |
| 181 warnings.warn("Unable to find the %s compiler: %s: %s" % |
| 182 (language, os.strerror(sys.exc_info()[1].errno), cc)) |
| 183 return None |
| 184 _, output = p.communicate() |
| 185 |
| 186 output = output.decode(locale.getpreferredencoding() or 'ASCII', 'replace') |
| 187 |
| 188 gcc_version = matcher(output) |
| 189 if not gcc_version: |
| 190 return None # not gcc - FIXME: do something about other compilers |
| 191 |
| 192 compiler_version = gcc_version.group(1) |
| 193 if compiler_version and compiler_version.split('.') >= ['4', '2']: |
| 194 return '-fopenmp', '-fopenmp' |
| 195 |
| 196 try: |
| 197 locale.setlocale(locale.LC_ALL, '') |
| 198 except locale.Error: |
| 199 pass |
| 200 |
| 201 OPENMP_C_COMPILER_FLAGS = get_openmp_compiler_flags('c') |
| 202 OPENMP_CPP_COMPILER_FLAGS = get_openmp_compiler_flags('cpp') |
| 203 |
| 204 # Return this from the EXT_EXTRAS matcher callback to exclude the extension |
| 205 EXCLUDE_EXT = object() |
| 206 |
| 207 EXT_EXTRAS = { |
| 208 'tag:numpy' : update_numpy_extension, |
| 209 'tag:openmp': update_openmp_extension, |
| 210 'tag:trace' : update_linetrace_extension, |
| 211 } |
| 212 |
| 213 |
| 214 def _is_py3_before_32(excluded, version): |
| 215 return version[0] >= 3 and version < (3,2) |
| 216 |
| 217 |
| 218 # TODO: use tags |
| 219 VER_DEP_MODULES = { |
| 220 # tests are excluded if 'CurrentPythonVersion OP VersionTuple', i.e. |
| 221 # (2,4) : (operator.lt, ...) excludes ... when PyVer < 2.4.x |
| 222 (2,4) : (operator.lt, lambda x: x in ['run.extern_builtins_T258', |
| 223 'run.builtin_sorted', |
| 224 'run.reversed_iteration', |
| 225 ]), |
| 226 (2,5) : (operator.lt, lambda x: x in ['run.any', |
| 227 'run.all', |
| 228 'run.yield_from_pep380', # GeneratorE
xit |
| 229 'run.generator_frame_cycle', # yield i
n try-finally |
| 230 'run.generator_expressions_in_class', |
| 231 'run.absolute_import', |
| 232 'run.relativeimport_T542', |
| 233 'run.relativeimport_star_T542', |
| 234 'run.initial_file_path', # relative i
mport |
| 235 'run.pynumber_subtype_conversion', #
bug in Py2.4 |
| 236 'build.cythonize_script', # python2.4
-m a.b.c |
| 237 'build.cythonize_script_excludes', #
python2.4 -m a.b.c |
| 238 'build.cythonize_script_package', # p
ython2.4 -m a.b.c |
| 239 ]), |
| 240 (2,6) : (operator.lt, lambda x: x in ['run.print_function', |
| 241 'run.language_level', # print function |
| 242 'run.cython3', |
| 243 'run.property_decorator_T593', # prop.
setter etc. |
| 244 'run.generators_py', # generators, wit
h statement |
| 245 'run.pure_py', # decorators, with stat
ement |
| 246 'run.purecdef', |
| 247 'run.struct_conversion', |
| 248 'run.bytearray_coercion', |
| 249 'run.bytearraymethods', |
| 250 'run.bytearray_ascii_auto_encoding', |
| 251 'run.bytearray_default_auto_encoding', |
| 252 # memory views require buffer protocol |
| 253 'memoryview.relaxed_strides', |
| 254 'memoryview.cythonarray', |
| 255 'memoryview.memslice', |
| 256 'memoryview.numpy_memoryview', |
| 257 'memoryview.memoryviewattrs', |
| 258 'memoryview.memoryview', |
| 259 'run.withstat_py', |
| 260 ]), |
| 261 (2,7) : (operator.lt, lambda x: x in ['run.withstat_py27', # multi context w
ith statement |
| 262 'run.yield_inside_lambda', |
| 263 'run.test_dictviews', |
| 264 'run.pyclass_special_methods', |
| 265 ]), |
| 266 # The next line should start (3,); but this is a dictionary, so |
| 267 # we can only have one (3,) key. Since 2.7 is supposed to be the |
| 268 # last 2.x release, things would have to change drastically for this |
| 269 # to be unsafe... |
| 270 (2,999): (operator.lt, lambda x: x in ['run.special_methods_T561_py3', |
| 271 'run.test_raisefrom', |
| 272 ]), |
| 273 (3,): (operator.ge, lambda x: x in ['run.non_future_division', |
| 274 'compile.extsetslice', |
| 275 'compile.extdelslice', |
| 276 'run.special_methods_T561_py2' |
| 277 ]), |
| 278 (3,1): (_is_py3_before_32, lambda x: x in ['run.pyclass_special_methods', |
| 279 ]), |
| 280 (3,3) : (operator.lt, lambda x: x in ['build.package_compilation', |
| 281 ]), |
| 282 (3,4,0,'beta',3) : (operator.le, lambda x: x in ['run.py34_signature', |
| 283 ]), |
| 284 } |
| 285 |
| 286 # files that should not be converted to Python 3 code with 2to3 |
| 287 KEEP_2X_FILES = [ |
| 288 os.path.join('Cython', 'Debugger', 'Tests', 'test_libcython_in_gdb.py'), |
| 289 os.path.join('Cython', 'Debugger', 'Tests', 'test_libpython_in_gdb.py'), |
| 290 os.path.join('Cython', 'Debugger', 'libcython.py'), |
| 291 os.path.join('Cython', 'Debugger', 'libpython.py'), |
| 292 ] |
| 293 |
| 294 COMPILER = None |
| 295 INCLUDE_DIRS = [ d for d in os.getenv('INCLUDE', '').split(os.pathsep) if d ] |
| 296 CFLAGS = os.getenv('CFLAGS', '').split() |
| 297 CCACHE = os.getenv('CYTHON_RUNTESTS_CCACHE', '').split() |
| 298 TEST_SUPPORT_DIR = 'testsupport' |
| 299 |
| 300 BACKENDS = ['c', 'cpp'] |
| 301 |
| 302 UTF8_BOM_BYTES = r'\xef\xbb\xbf'.encode('ISO-8859-1').decode('unicode_escape') |
| 303 |
| 304 |
| 305 def memoize(f): |
| 306 uncomputed = object() |
| 307 f._cache = {} |
| 308 def func(*args): |
| 309 res = f._cache.get(args, uncomputed) |
| 310 if res is uncomputed: |
| 311 res = f._cache[args] = f(*args) |
| 312 return res |
| 313 return func |
| 314 |
| 315 |
| 316 @memoize |
| 317 def parse_tags(filepath): |
| 318 tags = defaultdict(list) |
| 319 parse_tag = re.compile(r'#\s*(\w+)\s*:(.*)$').match |
| 320 f = io_open(filepath, encoding='ISO-8859-1', errors='ignore') |
| 321 try: |
| 322 for line in f: |
| 323 # ignore BOM-like bytes and whitespace |
| 324 line = line.lstrip(UTF8_BOM_BYTES).strip() |
| 325 if not line: |
| 326 if tags: |
| 327 break # assume all tags are in one block |
| 328 else: |
| 329 continue |
| 330 if line[0] != '#': |
| 331 break |
| 332 parsed = parse_tag(line) |
| 333 if parsed: |
| 334 tag, values = parsed.groups() |
| 335 if tag in ('coding', 'encoding'): |
| 336 continue |
| 337 if tag == 'tags': |
| 338 tag = 'tag' |
| 339 print("WARNING: test tags use the 'tag' directive, not 'tags
' (%s)" % filepath) |
| 340 if tag not in ('mode', 'tag', 'ticket', 'cython', 'distutils'): |
| 341 print("WARNING: unknown test directive '%s' found (%s)" % (t
ag, filepath)) |
| 342 values = values.split(',') |
| 343 tags[tag].extend(filter(None, [value.strip() for value in values
])) |
| 344 elif tags: |
| 345 break # assume all tags are in one block |
| 346 finally: |
| 347 f.close() |
| 348 return tags |
| 349 |
| 350 |
| 351 list_unchanging_dir = memoize(lambda x: os.listdir(x)) |
| 352 |
| 353 |
| 354 @memoize |
| 355 def _list_pyregr_data_files(test_directory): |
| 356 is_data_file = re.compile('(?:[.](txt|pem|db|html)|^bad.*[.]py)$').search |
| 357 return ['__init__.py'] + [ |
| 358 filename for filename in list_unchanging_dir(test_directory) |
| 359 if is_data_file(filename)] |
| 360 |
| 361 |
| 362 def import_ext(module_name, file_path=None): |
| 363 if file_path: |
| 364 import imp |
| 365 return imp.load_dynamic(module_name, file_path) |
| 366 else: |
| 367 try: |
| 368 from importlib import invalidate_caches |
| 369 except ImportError: |
| 370 pass |
| 371 else: |
| 372 invalidate_caches() |
| 373 return __import__(module_name, globals(), locals(), ['*']) |
| 374 |
| 375 |
| 376 class build_ext(_build_ext): |
| 377 def build_extension(self, ext): |
| 378 try: |
| 379 try: # Py2.7+ & Py3.2+ |
| 380 compiler_obj = self.compiler_obj |
| 381 except AttributeError: |
| 382 compiler_obj = self.compiler |
| 383 if ext.language == 'c++': |
| 384 compiler_obj.compiler_so.remove('-Wstrict-prototypes') |
| 385 if CCACHE: |
| 386 compiler_obj.compiler_so = CCACHE + compiler_obj.compiler_so |
| 387 if getattr(ext, 'openmp', None) and compiler_obj.compiler_type == 'm
svc': |
| 388 ext.extra_compile_args.append('/openmp') |
| 389 except Exception: |
| 390 pass |
| 391 _build_ext.build_extension(self, ext) |
| 392 |
| 393 class ErrorWriter(object): |
| 394 match_error = re.compile('(warning:)?(?:.*:)?\s*([-0-9]+)\s*:\s*([-0-9]+)\s*
:\s*(.*)').match |
| 395 def __init__(self): |
| 396 self.output = [] |
| 397 self.write = self.output.append |
| 398 |
| 399 def _collect(self, collect_errors, collect_warnings): |
| 400 s = ''.join(self.output) |
| 401 result = [] |
| 402 for line in s.split('\n'): |
| 403 match = self.match_error(line) |
| 404 if match: |
| 405 is_warning, line, column, message = match.groups() |
| 406 if (is_warning and collect_warnings) or \ |
| 407 (not is_warning and collect_errors): |
| 408 result.append( (int(line), int(column), message.strip()) ) |
| 409 result.sort() |
| 410 return [ "%d:%d: %s" % values for values in result ] |
| 411 |
| 412 def geterrors(self): |
| 413 return self._collect(True, False) |
| 414 |
| 415 def getwarnings(self): |
| 416 return self._collect(False, True) |
| 417 |
| 418 def getall(self): |
| 419 return self._collect(True, True) |
| 420 |
| 421 class TestBuilder(object): |
| 422 def __init__(self, rootdir, workdir, selectors, exclude_selectors, annotate, |
| 423 cleanup_workdir, cleanup_sharedlibs, cleanup_failures, |
| 424 with_pyregr, cython_only, languages, test_bugs, fork, language_
level): |
| 425 self.rootdir = rootdir |
| 426 self.workdir = workdir |
| 427 self.selectors = selectors |
| 428 self.exclude_selectors = exclude_selectors |
| 429 self.annotate = annotate |
| 430 self.cleanup_workdir = cleanup_workdir |
| 431 self.cleanup_sharedlibs = cleanup_sharedlibs |
| 432 self.cleanup_failures = cleanup_failures |
| 433 self.with_pyregr = with_pyregr |
| 434 self.cython_only = cython_only |
| 435 self.languages = languages |
| 436 self.test_bugs = test_bugs |
| 437 self.fork = fork |
| 438 self.language_level = language_level |
| 439 |
| 440 def build_suite(self): |
| 441 suite = unittest.TestSuite() |
| 442 filenames = os.listdir(self.rootdir) |
| 443 filenames.sort() |
| 444 for filename in filenames: |
| 445 path = os.path.join(self.rootdir, filename) |
| 446 if os.path.isdir(path) and filename != TEST_SUPPORT_DIR: |
| 447 if filename == 'pyregr' and not self.with_pyregr: |
| 448 continue |
| 449 if filename == 'broken' and not self.test_bugs: |
| 450 continue |
| 451 suite.addTest( |
| 452 self.handle_directory(path, filename)) |
| 453 if sys.platform not in ['win32']: |
| 454 # Non-Windows makefile. |
| 455 if [1 for selector in self.selectors if selector("embedded")] \ |
| 456 and not [1 for selector in self.exclude_selectors if selector("e
mbedded")]: |
| 457 suite.addTest(unittest.makeSuite(EmbedTest)) |
| 458 return suite |
| 459 |
| 460 def handle_directory(self, path, context): |
| 461 workdir = os.path.join(self.workdir, context) |
| 462 if not os.path.exists(workdir): |
| 463 os.makedirs(workdir) |
| 464 |
| 465 suite = unittest.TestSuite() |
| 466 filenames = list_unchanging_dir(path) |
| 467 filenames.sort() |
| 468 for filename in filenames: |
| 469 filepath = os.path.join(path, filename) |
| 470 module, ext = os.path.splitext(filename) |
| 471 if ext not in ('.py', '.pyx', '.srctree'): |
| 472 continue |
| 473 if filename.startswith('.'): |
| 474 continue # certain emacs backup files |
| 475 if context == 'pyregr': |
| 476 tags = defaultdict(list) |
| 477 else: |
| 478 tags = parse_tags(filepath) |
| 479 fqmodule = "%s.%s" % (context, module) |
| 480 if not [ 1 for match in self.selectors |
| 481 if match(fqmodule, tags) ]: |
| 482 continue |
| 483 if self.exclude_selectors: |
| 484 if [1 for match in self.exclude_selectors |
| 485 if match(fqmodule, tags)]: |
| 486 continue |
| 487 |
| 488 mode = 'run' # default |
| 489 if tags['mode']: |
| 490 mode = tags['mode'][0] |
| 491 elif context == 'pyregr': |
| 492 mode = 'pyregr' |
| 493 |
| 494 if ext == '.srctree': |
| 495 if 'cpp' not in tags['tag'] or 'cpp' in self.languages: |
| 496 suite.addTest(EndToEndTest(filepath, workdir, self.cleanup_w
orkdir)) |
| 497 continue |
| 498 |
| 499 # Choose the test suite. |
| 500 if mode == 'pyregr': |
| 501 if not filename.startswith('test_'): |
| 502 continue |
| 503 test_class = CythonPyregrTestCase |
| 504 elif mode == 'run': |
| 505 if module.startswith("test_"): |
| 506 test_class = CythonUnitTestCase |
| 507 else: |
| 508 test_class = CythonRunTestCase |
| 509 else: |
| 510 test_class = CythonCompileTestCase |
| 511 |
| 512 for test in self.build_tests(test_class, path, workdir, |
| 513 module, mode == 'error', tags): |
| 514 suite.addTest(test) |
| 515 if mode == 'run' and ext == '.py' and not self.cython_only: |
| 516 # additionally test file in real Python |
| 517 suite.addTest(PureDoctestTestCase(module, os.path.join(path, fil
ename))) |
| 518 |
| 519 return suite |
| 520 |
| 521 def build_tests(self, test_class, path, workdir, module, expect_errors, tags
): |
| 522 if 'werror' in tags['tag']: |
| 523 warning_errors = True |
| 524 else: |
| 525 warning_errors = False |
| 526 |
| 527 if expect_errors: |
| 528 if 'cpp' in tags['tag'] and 'cpp' in self.languages: |
| 529 languages = ['cpp'] |
| 530 else: |
| 531 languages = self.languages[:1] |
| 532 else: |
| 533 languages = self.languages |
| 534 |
| 535 if 'cpp' in tags['tag'] and 'c' in languages: |
| 536 languages = list(languages) |
| 537 languages.remove('c') |
| 538 elif 'no-cpp' in tags['tag'] and 'cpp' in self.languages: |
| 539 languages = list(languages) |
| 540 languages.remove('cpp') |
| 541 tests = [ self.build_test(test_class, path, workdir, module, tags, |
| 542 language, expect_errors, warning_errors) |
| 543 for language in languages ] |
| 544 return tests |
| 545 |
| 546 def build_test(self, test_class, path, workdir, module, tags, |
| 547 language, expect_errors, warning_errors): |
| 548 language_workdir = os.path.join(workdir, language) |
| 549 if not os.path.exists(language_workdir): |
| 550 os.makedirs(language_workdir) |
| 551 workdir = os.path.join(language_workdir, module) |
| 552 return test_class(path, workdir, module, tags, |
| 553 language=language, |
| 554 expect_errors=expect_errors, |
| 555 annotate=self.annotate, |
| 556 cleanup_workdir=self.cleanup_workdir, |
| 557 cleanup_sharedlibs=self.cleanup_sharedlibs, |
| 558 cleanup_failures=self.cleanup_failures, |
| 559 cython_only=self.cython_only, |
| 560 fork=self.fork, |
| 561 language_level=self.language_level, |
| 562 warning_errors=warning_errors) |
| 563 |
| 564 class CythonCompileTestCase(unittest.TestCase): |
| 565 def __init__(self, test_directory, workdir, module, tags, language='c', |
| 566 expect_errors=False, annotate=False, cleanup_workdir=True, |
| 567 cleanup_sharedlibs=True, cleanup_failures=True, cython_only=Fal
se, |
| 568 fork=True, language_level=2, warning_errors=False): |
| 569 self.test_directory = test_directory |
| 570 self.tags = tags |
| 571 self.workdir = workdir |
| 572 self.module = module |
| 573 self.language = language |
| 574 self.expect_errors = expect_errors |
| 575 self.annotate = annotate |
| 576 self.cleanup_workdir = cleanup_workdir |
| 577 self.cleanup_sharedlibs = cleanup_sharedlibs |
| 578 self.cleanup_failures = cleanup_failures |
| 579 self.cython_only = cython_only |
| 580 self.fork = fork |
| 581 self.language_level = language_level |
| 582 self.warning_errors = warning_errors |
| 583 unittest.TestCase.__init__(self) |
| 584 |
| 585 def shortDescription(self): |
| 586 return "compiling (%s) %s" % (self.language, self.module) |
| 587 |
| 588 def setUp(self): |
| 589 from Cython.Compiler import Options |
| 590 self._saved_options = [ (name, getattr(Options, name)) |
| 591 for name in ('warning_errors', |
| 592 'clear_to_none', |
| 593 'error_on_unknown_names', |
| 594 'error_on_uninitialized') ] |
| 595 self._saved_default_directives = Options.directive_defaults.items() |
| 596 Options.warning_errors = self.warning_errors |
| 597 if sys.version_info >= (3, 4): |
| 598 Options.directive_defaults['autotestdict'] = False |
| 599 |
| 600 if not os.path.exists(self.workdir): |
| 601 os.makedirs(self.workdir) |
| 602 if self.workdir not in sys.path: |
| 603 sys.path.insert(0, self.workdir) |
| 604 |
| 605 def tearDown(self): |
| 606 from Cython.Compiler import Options |
| 607 for name, value in self._saved_options: |
| 608 setattr(Options, name, value) |
| 609 Options.directive_defaults = dict(self._saved_default_directives) |
| 610 unpatch_inspect_isfunction() |
| 611 |
| 612 try: |
| 613 sys.path.remove(self.workdir) |
| 614 except ValueError: |
| 615 pass |
| 616 try: |
| 617 del sys.modules[self.module] |
| 618 except KeyError: |
| 619 pass |
| 620 cleanup = self.cleanup_failures or self.success |
| 621 cleanup_c_files = WITH_CYTHON and self.cleanup_workdir and cleanup |
| 622 cleanup_lib_files = self.cleanup_sharedlibs and cleanup |
| 623 if os.path.exists(self.workdir): |
| 624 if cleanup_c_files and cleanup_lib_files: |
| 625 shutil.rmtree(self.workdir, ignore_errors=True) |
| 626 else: |
| 627 for rmfile in os.listdir(self.workdir): |
| 628 if not cleanup_c_files: |
| 629 if (rmfile[-2:] in (".c", ".h") or |
| 630 rmfile[-4:] == ".cpp" or |
| 631 rmfile.endswith(".html") and rmfile.startswith(s
elf.module)): |
| 632 continue |
| 633 if not cleanup_lib_files and (rmfile.endswith(".so") or rmfi
le.endswith(".dll")): |
| 634 continue |
| 635 try: |
| 636 rmfile = os.path.join(self.workdir, rmfile) |
| 637 if os.path.isdir(rmfile): |
| 638 shutil.rmtree(rmfile, ignore_errors=True) |
| 639 else: |
| 640 os.remove(rmfile) |
| 641 except IOError: |
| 642 pass |
| 643 |
| 644 def runTest(self): |
| 645 self.success = False |
| 646 self.runCompileTest() |
| 647 self.success = True |
| 648 |
| 649 def runCompileTest(self): |
| 650 return self.compile( |
| 651 self.test_directory, self.module, self.workdir, |
| 652 self.test_directory, self.expect_errors, self.annotate) |
| 653 |
| 654 def find_module_source_file(self, source_file): |
| 655 if not os.path.exists(source_file): |
| 656 source_file = source_file[:-1] |
| 657 return source_file |
| 658 |
| 659 def build_target_filename(self, module_name): |
| 660 target = '%s.%s' % (module_name, self.language) |
| 661 return target |
| 662 |
| 663 def related_files(self, test_directory, module_name): |
| 664 is_related = re.compile('%s_.*[.].*' % module_name).match |
| 665 return [filename for filename in list_unchanging_dir(test_directory) |
| 666 if is_related(filename)] |
| 667 |
| 668 def copy_files(self, test_directory, target_directory, file_list): |
| 669 # use symlink on Unix, copy on Windows |
| 670 try: |
| 671 copy = os.symlink |
| 672 except AttributeError: |
| 673 copy = shutil.copy |
| 674 |
| 675 join = os.path.join |
| 676 for filename in file_list: |
| 677 file_path = join(test_directory, filename) |
| 678 if os.path.exists(file_path): |
| 679 copy(file_path, join(target_directory, filename)) |
| 680 |
| 681 def source_files(self, workdir, module_name, file_list): |
| 682 return ([self.build_target_filename(module_name)] + |
| 683 [filename for filename in file_list |
| 684 if not os.path.isfile(os.path.join(workdir, filename))]) |
| 685 |
| 686 def split_source_and_output(self, test_directory, module, workdir): |
| 687 source_file = self.find_module_source_file(os.path.join(test_directory,
module) + '.pyx') |
| 688 source_and_output = io_open(source_file, 'rU', encoding='ISO-8859-1') |
| 689 try: |
| 690 out = io_open(os.path.join(workdir, module + os.path.splitext(source
_file)[1]), |
| 691 'w', encoding='ISO-8859-1') |
| 692 for line in source_and_output: |
| 693 if line.startswith("_ERRORS"): |
| 694 out.close() |
| 695 out = ErrorWriter() |
| 696 else: |
| 697 out.write(line) |
| 698 finally: |
| 699 source_and_output.close() |
| 700 try: |
| 701 geterrors = out.geterrors |
| 702 except AttributeError: |
| 703 out.close() |
| 704 return [] |
| 705 else: |
| 706 return geterrors() |
| 707 |
| 708 def run_cython(self, test_directory, module, targetdir, incdir, annotate, |
| 709 extra_compile_options=None): |
| 710 include_dirs = INCLUDE_DIRS + [os.path.join(test_directory, '..', TEST_S
UPPORT_DIR)] |
| 711 if incdir: |
| 712 include_dirs.append(incdir) |
| 713 source = self.find_module_source_file( |
| 714 os.path.join(test_directory, module + '.pyx')) |
| 715 target = os.path.join(targetdir, self.build_target_filename(module)) |
| 716 |
| 717 if extra_compile_options is None: |
| 718 extra_compile_options = {} |
| 719 |
| 720 try: |
| 721 CompilationOptions |
| 722 except NameError: |
| 723 from Cython.Compiler.Main import CompilationOptions |
| 724 from Cython.Compiler.Main import compile as cython_compile |
| 725 from Cython.Compiler.Main import default_options |
| 726 |
| 727 options = CompilationOptions( |
| 728 default_options, |
| 729 include_path = include_dirs, |
| 730 output_file = target, |
| 731 annotate = annotate, |
| 732 use_listing_file = False, |
| 733 cplus = self.language == 'cpp', |
| 734 language_level = self.language_level, |
| 735 generate_pxi = False, |
| 736 evaluate_tree_assertions = True, |
| 737 **extra_compile_options |
| 738 ) |
| 739 cython_compile(source, options=options, |
| 740 full_module_name=module) |
| 741 |
| 742 def run_distutils(self, test_directory, module, workdir, incdir, |
| 743 extra_extension_args=None): |
| 744 cwd = os.getcwd() |
| 745 os.chdir(workdir) |
| 746 try: |
| 747 build_extension = build_ext(distutils_distro) |
| 748 build_extension.include_dirs = INCLUDE_DIRS[:] |
| 749 if incdir: |
| 750 build_extension.include_dirs.append(incdir) |
| 751 build_extension.finalize_options() |
| 752 if COMPILER: |
| 753 build_extension.compiler = COMPILER |
| 754 |
| 755 ext_compile_flags = CFLAGS[:] |
| 756 compiler = COMPILER or sysconfig.get_config_var('CC') |
| 757 |
| 758 if self.language == 'c' and compiler == 'gcc': |
| 759 ext_compile_flags.extend(['-std=c89', '-pedantic']) |
| 760 if build_extension.compiler == 'mingw32': |
| 761 ext_compile_flags.append('-Wno-format') |
| 762 if extra_extension_args is None: |
| 763 extra_extension_args = {} |
| 764 |
| 765 related_files = self.related_files(test_directory, module) |
| 766 self.copy_files(test_directory, workdir, related_files) |
| 767 extension = Extension( |
| 768 module, |
| 769 sources = self.source_files(workdir, module, related_files), |
| 770 extra_compile_args = ext_compile_flags, |
| 771 **extra_extension_args |
| 772 ) |
| 773 |
| 774 if self.language == 'cpp': |
| 775 # Set the language now as the fixer might need it |
| 776 extension.language = 'c++' |
| 777 |
| 778 if 'distutils' in self.tags: |
| 779 from Cython.Build.Dependencies import DistutilsInfo |
| 780 pyx_path = os.path.join(self.test_directory, self.module + ".pyx
") |
| 781 DistutilsInfo(open(pyx_path)).apply(extension) |
| 782 |
| 783 for matcher, fixer in list(EXT_EXTRAS.items()): |
| 784 if isinstance(matcher, str): |
| 785 # lazy init |
| 786 del EXT_EXTRAS[matcher] |
| 787 matcher = string_selector(matcher) |
| 788 EXT_EXTRAS[matcher] = fixer |
| 789 if matcher(module, self.tags): |
| 790 newext = fixer(extension) |
| 791 if newext is EXCLUDE_EXT: |
| 792 return |
| 793 extension = newext or extension |
| 794 if self.language == 'cpp': |
| 795 extension.language = 'c++' |
| 796 build_extension.extensions = [extension] |
| 797 build_extension.build_temp = workdir |
| 798 build_extension.build_lib = workdir |
| 799 build_extension.run() |
| 800 finally: |
| 801 os.chdir(cwd) |
| 802 |
| 803 try: |
| 804 get_ext_fullpath = build_extension.get_ext_fullpath |
| 805 except AttributeError: |
| 806 def get_ext_fullpath(ext_name, self=build_extension): |
| 807 # copied from distutils.command.build_ext (missing in Py2.[45]) |
| 808 fullname = self.get_ext_fullname(ext_name) |
| 809 modpath = fullname.split('.') |
| 810 filename = self.get_ext_filename(modpath[-1]) |
| 811 if not self.inplace: |
| 812 filename = os.path.join(*modpath[:-1]+[filename]) |
| 813 return os.path.join(self.build_lib, filename) |
| 814 package = '.'.join(modpath[0:-1]) |
| 815 build_py = self.get_finalized_command('build_py') |
| 816 package_dir = os.path.abspath(build_py.get_package_dir(package)) |
| 817 return os.path.join(package_dir, filename) |
| 818 |
| 819 return get_ext_fullpath(module) |
| 820 |
| 821 def compile(self, test_directory, module, workdir, incdir, |
| 822 expect_errors, annotate): |
| 823 expected_errors = errors = () |
| 824 if expect_errors: |
| 825 expected_errors = self.split_source_and_output( |
| 826 test_directory, module, workdir) |
| 827 test_directory = workdir |
| 828 |
| 829 if WITH_CYTHON: |
| 830 old_stderr = sys.stderr |
| 831 try: |
| 832 sys.stderr = ErrorWriter() |
| 833 self.run_cython(test_directory, module, workdir, incdir, annotat
e) |
| 834 errors = sys.stderr.geterrors() |
| 835 finally: |
| 836 sys.stderr = old_stderr |
| 837 |
| 838 if errors or expected_errors: |
| 839 try: |
| 840 for expected, error in zip(expected_errors, errors): |
| 841 self.assertEquals(expected, error) |
| 842 if len(errors) < len(expected_errors): |
| 843 expected_error = expected_errors[len(errors)] |
| 844 self.assertEquals(expected_error, None) |
| 845 elif len(errors) > len(expected_errors): |
| 846 unexpected_error = errors[len(expected_errors)] |
| 847 self.assertEquals(None, unexpected_error) |
| 848 except AssertionError: |
| 849 print("\n=== Expected errors: ===") |
| 850 print('\n'.join(expected_errors)) |
| 851 print("\n\n=== Got errors: ===") |
| 852 print('\n'.join(errors)) |
| 853 print('\n') |
| 854 raise |
| 855 return None |
| 856 |
| 857 if self.cython_only: |
| 858 so_path = None |
| 859 else: |
| 860 so_path = self.run_distutils(test_directory, module, workdir, incdir
) |
| 861 return so_path |
| 862 |
| 863 class CythonRunTestCase(CythonCompileTestCase): |
| 864 def setUp(self): |
| 865 CythonCompileTestCase.setUp(self) |
| 866 from Cython.Compiler import Options |
| 867 Options.clear_to_none = False |
| 868 |
| 869 def shortDescription(self): |
| 870 if self.cython_only: |
| 871 return CythonCompileTestCase.shortDescription(self) |
| 872 else: |
| 873 return "compiling (%s) and running %s" % (self.language, self.module
) |
| 874 |
| 875 def run(self, result=None): |
| 876 if result is None: |
| 877 result = self.defaultTestResult() |
| 878 result.startTest(self) |
| 879 try: |
| 880 self.setUp() |
| 881 try: |
| 882 self.success = False |
| 883 ext_so_path = self.runCompileTest() |
| 884 failures, errors = len(result.failures), len(result.errors) |
| 885 if not self.cython_only: |
| 886 self.run_tests(result, ext_so_path) |
| 887 if failures == len(result.failures) and errors == len(result.err
ors): |
| 888 # No new errors... |
| 889 self.success = True |
| 890 finally: |
| 891 check_thread_termination() |
| 892 except Exception: |
| 893 result.addError(self, sys.exc_info()) |
| 894 result.stopTest(self) |
| 895 try: |
| 896 self.tearDown() |
| 897 except Exception: |
| 898 pass |
| 899 |
| 900 def run_tests(self, result, ext_so_path): |
| 901 self.run_doctests(self.module, result, ext_so_path) |
| 902 |
| 903 def run_doctests(self, module_or_name, result, ext_so_path): |
| 904 def run_test(result): |
| 905 if isinstance(module_or_name, basestring): |
| 906 module = import_ext(module_or_name, ext_so_path) |
| 907 else: |
| 908 module = module_or_name |
| 909 tests = doctest.DocTestSuite(module) |
| 910 tests.run(result) |
| 911 run_forked_test(result, run_test, self.shortDescription(), self.fork) |
| 912 |
| 913 def run_forked_test(result, run_func, test_name, fork=True): |
| 914 if not fork or sys.version_info[0] >= 3 or not hasattr(os, 'fork'): |
| 915 run_func(result) |
| 916 sys.stdout.flush() |
| 917 sys.stderr.flush() |
| 918 gc.collect() |
| 919 return |
| 920 |
| 921 # fork to make sure we do not keep the tested module loaded |
| 922 result_handle, result_file = tempfile.mkstemp() |
| 923 os.close(result_handle) |
| 924 child_id = os.fork() |
| 925 if not child_id: |
| 926 result_code = 0 |
| 927 output = None |
| 928 try: |
| 929 try: |
| 930 tests = partial_result = None |
| 931 try: |
| 932 partial_result = PartialTestResult(result) |
| 933 run_func(partial_result) |
| 934 sys.stdout.flush() |
| 935 sys.stderr.flush() |
| 936 gc.collect() |
| 937 except Exception: |
| 938 result_code = 1 |
| 939 if partial_result is not None: |
| 940 if tests is None: |
| 941 # importing failed, try to fake a test class |
| 942 tests = _FakeClass( |
| 943 failureException=sys.exc_info()[1], |
| 944 _shortDescription=test_name, |
| 945 module_name=None) |
| 946 partial_result.addError(tests, sys.exc_info()) |
| 947 output = open(result_file, 'wb') |
| 948 pickle.dump(partial_result.data(), output) |
| 949 except: |
| 950 traceback.print_exc() |
| 951 finally: |
| 952 try: sys.stderr.flush() |
| 953 except: pass |
| 954 try: sys.stdout.flush() |
| 955 except: pass |
| 956 try: |
| 957 if output is not None: |
| 958 output.close() |
| 959 except: |
| 960 pass |
| 961 os._exit(result_code) |
| 962 |
| 963 try: |
| 964 cid, result_code = os.waitpid(child_id, 0) |
| 965 module_name = test_name.split()[-1] |
| 966 # os.waitpid returns the child's result code in the |
| 967 # upper byte of result_code, and the signal it was |
| 968 # killed by in the lower byte |
| 969 if result_code & 255: |
| 970 raise Exception("Tests in module '%s' were unexpectedly killed by si
gnal %d"% |
| 971 (module_name, result_code & 255)) |
| 972 result_code >>= 8 |
| 973 if result_code in (0,1): |
| 974 input = open(result_file, 'rb') |
| 975 try: |
| 976 PartialTestResult.join_results(result, pickle.load(input)) |
| 977 finally: |
| 978 input.close() |
| 979 if result_code: |
| 980 raise Exception("Tests in module '%s' exited with status %d" % |
| 981 (module_name, result_code)) |
| 982 finally: |
| 983 try: os.unlink(result_file) |
| 984 except: pass |
| 985 |
| 986 class PureDoctestTestCase(unittest.TestCase): |
| 987 def __init__(self, module_name, module_path): |
| 988 self.module_name = module_name |
| 989 self.module_path = module_path |
| 990 unittest.TestCase.__init__(self, 'run') |
| 991 |
| 992 def shortDescription(self): |
| 993 return "running pure doctests in %s" % self.module_name |
| 994 |
| 995 def run(self, result=None): |
| 996 if result is None: |
| 997 result = self.defaultTestResult() |
| 998 loaded_module_name = 'pure_doctest__' + self.module_name |
| 999 result.startTest(self) |
| 1000 try: |
| 1001 self.setUp() |
| 1002 |
| 1003 import imp |
| 1004 m = imp.load_source(loaded_module_name, self.module_path) |
| 1005 try: |
| 1006 doctest.DocTestSuite(m).run(result) |
| 1007 finally: |
| 1008 del m |
| 1009 if loaded_module_name in sys.modules: |
| 1010 del sys.modules[loaded_module_name] |
| 1011 check_thread_termination() |
| 1012 except Exception: |
| 1013 result.addError(self, sys.exc_info()) |
| 1014 result.stopTest(self) |
| 1015 try: |
| 1016 self.tearDown() |
| 1017 except Exception: |
| 1018 pass |
| 1019 |
| 1020 is_private_field = re.compile('^_[^_]').match |
| 1021 |
| 1022 class _FakeClass(object): |
| 1023 def __init__(self, **kwargs): |
| 1024 self._shortDescription = kwargs.get('module_name') |
| 1025 self.__dict__.update(kwargs) |
| 1026 def shortDescription(self): |
| 1027 return self._shortDescription |
| 1028 |
| 1029 try: # Py2.7+ and Py3.2+ |
| 1030 from unittest.runner import _TextTestResult |
| 1031 except ImportError: |
| 1032 from unittest import _TextTestResult |
| 1033 |
| 1034 class PartialTestResult(_TextTestResult): |
| 1035 def __init__(self, base_result): |
| 1036 _TextTestResult.__init__( |
| 1037 self, self._StringIO(), True, |
| 1038 base_result.dots + base_result.showAll*2) |
| 1039 |
| 1040 def strip_error_results(self, results): |
| 1041 for test_case, error in results: |
| 1042 for attr_name in filter(is_private_field, dir(test_case)): |
| 1043 if attr_name == '_dt_test': |
| 1044 test_case._dt_test = _FakeClass( |
| 1045 name=test_case._dt_test.name) |
| 1046 elif attr_name != '_shortDescription': |
| 1047 setattr(test_case, attr_name, None) |
| 1048 |
| 1049 def data(self): |
| 1050 self.strip_error_results(self.failures) |
| 1051 self.strip_error_results(self.errors) |
| 1052 return (self.failures, self.errors, self.testsRun, |
| 1053 self.stream.getvalue()) |
| 1054 |
| 1055 def join_results(result, data): |
| 1056 """Static method for merging the result back into the main |
| 1057 result object. |
| 1058 """ |
| 1059 failures, errors, tests_run, output = data |
| 1060 if output: |
| 1061 result.stream.write(output) |
| 1062 result.errors.extend(errors) |
| 1063 result.failures.extend(failures) |
| 1064 result.testsRun += tests_run |
| 1065 |
| 1066 join_results = staticmethod(join_results) |
| 1067 |
| 1068 class _StringIO(StringIO): |
| 1069 def writeln(self, line): |
| 1070 self.write("%s\n" % line) |
| 1071 |
| 1072 |
| 1073 class CythonUnitTestCase(CythonRunTestCase): |
| 1074 def shortDescription(self): |
| 1075 return "compiling (%s) tests in %s" % (self.language, self.module) |
| 1076 |
| 1077 def run_tests(self, result, ext_so_path): |
| 1078 module = import_ext(self.module, ext_so_path) |
| 1079 unittest.defaultTestLoader.loadTestsFromModule(module).run(result) |
| 1080 |
| 1081 |
| 1082 class CythonPyregrTestCase(CythonRunTestCase): |
| 1083 def setUp(self): |
| 1084 CythonRunTestCase.setUp(self) |
| 1085 from Cython.Compiler import Options |
| 1086 Options.error_on_unknown_names = False |
| 1087 Options.error_on_uninitialized = False |
| 1088 Options.directive_defaults.update(dict( |
| 1089 binding=True, always_allow_keywords=True, |
| 1090 set_initial_path="SOURCEFILE")) |
| 1091 patch_inspect_isfunction() |
| 1092 |
| 1093 def related_files(self, test_directory, module_name): |
| 1094 return _list_pyregr_data_files(test_directory) |
| 1095 |
| 1096 def _run_unittest(self, result, *classes): |
| 1097 """Run tests from unittest.TestCase-derived classes.""" |
| 1098 valid_types = (unittest.TestSuite, unittest.TestCase) |
| 1099 suite = unittest.TestSuite() |
| 1100 for cls in classes: |
| 1101 if isinstance(cls, str): |
| 1102 if cls in sys.modules: |
| 1103 suite.addTest(unittest.findTestCases(sys.modules[cls])) |
| 1104 else: |
| 1105 raise ValueError("str arguments must be keys in sys.modules"
) |
| 1106 elif isinstance(cls, valid_types): |
| 1107 suite.addTest(cls) |
| 1108 else: |
| 1109 suite.addTest(unittest.makeSuite(cls)) |
| 1110 suite.run(result) |
| 1111 |
| 1112 def _run_doctest(self, result, module): |
| 1113 self.run_doctests(module, result, None) |
| 1114 |
| 1115 def run_tests(self, result, ext_so_path): |
| 1116 try: |
| 1117 from test import support |
| 1118 except ImportError: # Python2.x |
| 1119 from test import test_support as support |
| 1120 |
| 1121 def run_test(result): |
| 1122 def run_unittest(*classes): |
| 1123 return self._run_unittest(result, *classes) |
| 1124 def run_doctest(module, verbosity=None): |
| 1125 return self._run_doctest(result, module) |
| 1126 |
| 1127 backup = (support.run_unittest, support.run_doctest) |
| 1128 support.run_unittest = run_unittest |
| 1129 support.run_doctest = run_doctest |
| 1130 |
| 1131 try: |
| 1132 try: |
| 1133 sys.stdout.flush() # helps in case of crashes |
| 1134 module = import_ext(self.module, ext_so_path) |
| 1135 sys.stdout.flush() # helps in case of crashes |
| 1136 if hasattr(module, 'test_main'): |
| 1137 module.test_main() |
| 1138 sys.stdout.flush() # helps in case of crashes |
| 1139 except (unittest.SkipTest, support.ResourceDenied): |
| 1140 result.addSkip(self, 'ok') |
| 1141 finally: |
| 1142 support.run_unittest, support.run_doctest = backup |
| 1143 |
| 1144 run_forked_test(result, run_test, self.shortDescription(), self.fork) |
| 1145 |
| 1146 include_debugger = IS_CPYTHON and sys.version_info[:2] > (2, 5) |
| 1147 |
| 1148 def collect_unittests(path, module_prefix, suite, selectors, exclude_selectors): |
| 1149 def file_matches(filename): |
| 1150 return filename.startswith("Test") and filename.endswith(".py") |
| 1151 |
| 1152 def package_matches(dirname): |
| 1153 return dirname == "Tests" |
| 1154 |
| 1155 loader = unittest.TestLoader() |
| 1156 |
| 1157 if include_debugger: |
| 1158 skipped_dirs = [] |
| 1159 else: |
| 1160 skipped_dirs = ['Cython' + os.path.sep + 'Debugger' + os.path.sep] |
| 1161 |
| 1162 for dirpath, dirnames, filenames in os.walk(path): |
| 1163 if dirpath != path and "__init__.py" not in filenames: |
| 1164 skipped_dirs.append(dirpath + os.path.sep) |
| 1165 continue |
| 1166 skip = False |
| 1167 for dir in skipped_dirs: |
| 1168 if dirpath.startswith(dir): |
| 1169 skip = True |
| 1170 if skip: |
| 1171 continue |
| 1172 parentname = os.path.split(dirpath)[-1] |
| 1173 if package_matches(parentname): |
| 1174 for f in filenames: |
| 1175 if file_matches(f): |
| 1176 filepath = os.path.join(dirpath, f)[:-len(".py")] |
| 1177 modulename = module_prefix + filepath[len(path)+1:].replace(
os.path.sep, '.') |
| 1178 if not [ 1 for match in selectors if match(modulename) ]: |
| 1179 continue |
| 1180 if [ 1 for match in exclude_selectors if match(modulename) ]
: |
| 1181 continue |
| 1182 module = __import__(modulename) |
| 1183 for x in modulename.split('.')[1:]: |
| 1184 module = getattr(module, x) |
| 1185 suite.addTests([loader.loadTestsFromModule(module)]) |
| 1186 |
| 1187 |
| 1188 |
| 1189 def collect_doctests(path, module_prefix, suite, selectors, exclude_selectors): |
| 1190 def package_matches(dirname): |
| 1191 if dirname == 'Debugger' and not include_debugger: |
| 1192 return False |
| 1193 return dirname not in ("Mac", "Distutils", "Plex") |
| 1194 def file_matches(filename): |
| 1195 filename, ext = os.path.splitext(filename) |
| 1196 blacklist = ['libcython', 'libpython', 'test_libcython_in_gdb', |
| 1197 'TestLibCython'] |
| 1198 return (ext == '.py' and not |
| 1199 '~' in filename and not |
| 1200 '#' in filename and not |
| 1201 filename.startswith('.') and not |
| 1202 filename in blacklist) |
| 1203 import doctest |
| 1204 for dirpath, dirnames, filenames in os.walk(path): |
| 1205 for dir in list(dirnames): |
| 1206 if not package_matches(dir): |
| 1207 dirnames.remove(dir) |
| 1208 for f in filenames: |
| 1209 if file_matches(f): |
| 1210 if not f.endswith('.py'): continue |
| 1211 filepath = os.path.join(dirpath, f) |
| 1212 if os.path.getsize(filepath) == 0: continue |
| 1213 filepath = filepath[:-len(".py")] |
| 1214 modulename = module_prefix + filepath[len(path)+1:].replace(os.p
ath.sep, '.') |
| 1215 if not [ 1 for match in selectors if match(modulename) ]: |
| 1216 continue |
| 1217 if [ 1 for match in exclude_selectors if match(modulename) ]: |
| 1218 continue |
| 1219 if 'in_gdb' in modulename: |
| 1220 # These should only be imported from gdb. |
| 1221 continue |
| 1222 module = __import__(modulename) |
| 1223 for x in modulename.split('.')[1:]: |
| 1224 module = getattr(module, x) |
| 1225 if hasattr(module, "__doc__") or hasattr(module, "__test__"): |
| 1226 try: |
| 1227 suite.addTest(doctest.DocTestSuite(module)) |
| 1228 except ValueError: # no tests |
| 1229 pass |
| 1230 |
| 1231 |
| 1232 class EndToEndTest(unittest.TestCase): |
| 1233 """ |
| 1234 This is a test of build/*.srctree files, where srctree defines a full |
| 1235 directory structure and its header gives a list of commands to run. |
| 1236 """ |
| 1237 cython_root = os.path.dirname(os.path.abspath(__file__)) |
| 1238 |
| 1239 def __init__(self, treefile, workdir, cleanup_workdir=True): |
| 1240 self.name = os.path.splitext(os.path.basename(treefile))[0] |
| 1241 self.treefile = treefile |
| 1242 self.workdir = os.path.join(workdir, self.name) |
| 1243 self.cleanup_workdir = cleanup_workdir |
| 1244 cython_syspath = [self.cython_root] |
| 1245 for path in sys.path: |
| 1246 if path.startswith(self.cython_root) and path not in cython_syspath: |
| 1247 # Py3 installation and refnanny build prepend their |
| 1248 # fixed paths to sys.path => prefer that over the |
| 1249 # generic one (cython_root itself goes last) |
| 1250 cython_syspath.append(path) |
| 1251 self.cython_syspath = os.pathsep.join(cython_syspath[::-1]) |
| 1252 unittest.TestCase.__init__(self) |
| 1253 |
| 1254 def shortDescription(self): |
| 1255 return "End-to-end %s" % self.name |
| 1256 |
| 1257 def setUp(self): |
| 1258 from Cython.TestUtils import unpack_source_tree |
| 1259 _, self.commands = unpack_source_tree(self.treefile, self.workdir) |
| 1260 self.old_dir = os.getcwd() |
| 1261 os.chdir(self.workdir) |
| 1262 if self.workdir not in sys.path: |
| 1263 sys.path.insert(0, self.workdir) |
| 1264 |
| 1265 def tearDown(self): |
| 1266 if self.cleanup_workdir: |
| 1267 for trial in range(5): |
| 1268 try: |
| 1269 shutil.rmtree(self.workdir) |
| 1270 except OSError: |
| 1271 time.sleep(0.1) |
| 1272 else: |
| 1273 break |
| 1274 os.chdir(self.old_dir) |
| 1275 |
| 1276 def _try_decode(self, content): |
| 1277 try: |
| 1278 return content.decode() |
| 1279 except UnicodeDecodeError: |
| 1280 return content.decode('iso-8859-1') |
| 1281 |
| 1282 def runTest(self): |
| 1283 self.success = False |
| 1284 commands = (self.commands |
| 1285 .replace("CYTHON", "PYTHON %s" % os.path.join(self.cython_root, 'cyt
hon.py')) |
| 1286 .replace("PYTHON", sys.executable)) |
| 1287 old_path = os.environ.get('PYTHONPATH') |
| 1288 os.environ['PYTHONPATH'] = self.cython_syspath + os.pathsep + (old_path
or '') |
| 1289 try: |
| 1290 for command in filter(None, commands.splitlines()): |
| 1291 p = subprocess.Popen(command, |
| 1292 stderr=subprocess.PIPE, |
| 1293 stdout=subprocess.PIPE, |
| 1294 shell=True) |
| 1295 out, err = p.communicate() |
| 1296 res = p.returncode |
| 1297 if res != 0: |
| 1298 print(command) |
| 1299 print(self._try_decode(out)) |
| 1300 print(self._try_decode(err)) |
| 1301 self.assertEqual(0, res, "non-zero exit status") |
| 1302 finally: |
| 1303 if old_path: |
| 1304 os.environ['PYTHONPATH'] = old_path |
| 1305 else: |
| 1306 del os.environ['PYTHONPATH'] |
| 1307 self.success = True |
| 1308 |
| 1309 |
| 1310 # TODO: Support cython_freeze needed here as well. |
| 1311 # TODO: Windows support. |
| 1312 |
| 1313 class EmbedTest(unittest.TestCase): |
| 1314 |
| 1315 working_dir = "Demos/embed" |
| 1316 |
| 1317 def setUp(self): |
| 1318 self.old_dir = os.getcwd() |
| 1319 os.chdir(self.working_dir) |
| 1320 os.system( |
| 1321 "make PYTHON='%s' clean > /dev/null" % sys.executable) |
| 1322 |
| 1323 def tearDown(self): |
| 1324 try: |
| 1325 os.system( |
| 1326 "make PYTHON='%s' clean > /dev/null" % sys.executable) |
| 1327 except: |
| 1328 pass |
| 1329 os.chdir(self.old_dir) |
| 1330 |
| 1331 def test_embed(self): |
| 1332 from distutils import sysconfig |
| 1333 libname = sysconfig.get_config_var('LIBRARY') |
| 1334 libdir = sysconfig.get_config_var('LIBDIR') |
| 1335 if not os.path.isdir(libdir) or libname not in os.listdir(libdir): |
| 1336 libdir = os.path.join(os.path.dirname(sys.executable), '..', 'lib') |
| 1337 if not os.path.isdir(libdir) or libname not in os.listdir(libdir): |
| 1338 libdir = os.path.join(libdir, 'python%d.%d' % sys.version_info[:
2], 'config') |
| 1339 if not os.path.isdir(libdir) or libname not in os.listdir(libdir
): |
| 1340 # report the error for the original directory |
| 1341 libdir = sysconfig.get_config_var('LIBDIR') |
| 1342 cython = 'cython.py' |
| 1343 if sys.version_info[0] >=3 and CY3_DIR: |
| 1344 cython = os.path.join(CY3_DIR, cython) |
| 1345 cython = os.path.abspath(os.path.join('..', '..', cython)) |
| 1346 self.assert_(os.system( |
| 1347 "make PYTHON='%s' CYTHON='%s' LIBDIR1='%s' test > make.output" % (sy
s.executable, cython, libdir)) == 0) |
| 1348 try: |
| 1349 os.remove('make.output') |
| 1350 except OSError: |
| 1351 pass |
| 1352 |
| 1353 class MissingDependencyExcluder: |
| 1354 def __init__(self, deps): |
| 1355 # deps: { matcher func : module name } |
| 1356 self.exclude_matchers = [] |
| 1357 for matcher, mod in deps.items(): |
| 1358 try: |
| 1359 __import__(mod) |
| 1360 except ImportError: |
| 1361 self.exclude_matchers.append(string_selector(matcher)) |
| 1362 self.tests_missing_deps = [] |
| 1363 def __call__(self, testname, tags=None): |
| 1364 for matcher in self.exclude_matchers: |
| 1365 if matcher(testname, tags): |
| 1366 self.tests_missing_deps.append(testname) |
| 1367 return True |
| 1368 return False |
| 1369 |
| 1370 class VersionDependencyExcluder: |
| 1371 def __init__(self, deps): |
| 1372 # deps: { version : matcher func } |
| 1373 from sys import version_info |
| 1374 self.exclude_matchers = [] |
| 1375 for ver, (compare, matcher) in deps.items(): |
| 1376 if compare(version_info, ver): |
| 1377 self.exclude_matchers.append(matcher) |
| 1378 self.tests_missing_deps = [] |
| 1379 def __call__(self, testname, tags=None): |
| 1380 for matcher in self.exclude_matchers: |
| 1381 if matcher(testname): |
| 1382 self.tests_missing_deps.append(testname) |
| 1383 return True |
| 1384 return False |
| 1385 |
| 1386 class FileListExcluder: |
| 1387 |
| 1388 def __init__(self, list_file): |
| 1389 self.excludes = {} |
| 1390 f = open(list_file) |
| 1391 try: |
| 1392 for line in f.readlines(): |
| 1393 line = line.strip() |
| 1394 if line and line[0] != '#': |
| 1395 self.excludes[line.split()[0]] = True |
| 1396 finally: |
| 1397 f.close() |
| 1398 |
| 1399 def __call__(self, testname, tags=None): |
| 1400 return testname in self.excludes or testname.split('.')[-1] in self.excl
udes |
| 1401 |
| 1402 class TagsSelector: |
| 1403 |
| 1404 def __init__(self, tag, value): |
| 1405 self.tag = tag |
| 1406 self.value = value |
| 1407 |
| 1408 def __call__(self, testname, tags=None): |
| 1409 if tags is None: |
| 1410 return False |
| 1411 else: |
| 1412 return self.value in tags[self.tag] |
| 1413 |
| 1414 class RegExSelector: |
| 1415 |
| 1416 def __init__(self, pattern_string): |
| 1417 try: |
| 1418 self.pattern = re.compile(pattern_string, re.I|re.U) |
| 1419 except re.error: |
| 1420 print('Invalid pattern: %r' % pattern_string) |
| 1421 raise |
| 1422 |
| 1423 def __call__(self, testname, tags=None): |
| 1424 return self.pattern.search(testname) |
| 1425 |
| 1426 def string_selector(s): |
| 1427 ix = s.find(':') |
| 1428 if ix == -1: |
| 1429 return RegExSelector(s) |
| 1430 else: |
| 1431 return TagsSelector(s[:ix], s[ix+1:]) |
| 1432 |
| 1433 class ShardExcludeSelector: |
| 1434 # This is an exclude selector so it can override the (include) selectors. |
| 1435 # It may not provide uniform distribution (in time or count), but is a |
| 1436 # determanistic partition of the tests which is important. |
| 1437 def __init__(self, shard_num, shard_count): |
| 1438 self.shard_num = shard_num |
| 1439 self.shard_count = shard_count |
| 1440 |
| 1441 def __call__(self, testname, tags=None): |
| 1442 return abs(hash(testname)) % self.shard_count != self.shard_num |
| 1443 |
| 1444 |
| 1445 def refactor_for_py3(distdir, cy3_dir): |
| 1446 # need to convert Cython sources first |
| 1447 import lib2to3.refactor |
| 1448 from distutils.util import copydir_run_2to3 |
| 1449 fixers = [ fix for fix in lib2to3.refactor.get_fixers_from_package("lib2to3.
fixes") |
| 1450 if fix.split('fix_')[-1] not in ('next',) |
| 1451 ] |
| 1452 if not os.path.exists(cy3_dir): |
| 1453 os.makedirs(cy3_dir) |
| 1454 import distutils.log as dlog |
| 1455 dlog.set_threshold(dlog.INFO) |
| 1456 copydir_run_2to3(distdir, cy3_dir, fixer_names=fixers, |
| 1457 template = ''' |
| 1458 global-exclude * |
| 1459 graft Cython |
| 1460 recursive-exclude Cython * |
| 1461 recursive-include Cython *.py *.pyx *.pxd |
| 1462 recursive-include Cython/Debugger/Tests * |
| 1463 recursive-include Cython/Utility * |
| 1464 recursive-exclude pyximport test |
| 1465 include pyximport/*.py |
| 1466 include runtests.py |
| 1467 include cython.py |
| 1468 ''') |
| 1469 sys.path.insert(0, cy3_dir) |
| 1470 |
| 1471 for keep_2x_file in KEEP_2X_FILES: |
| 1472 destfile = os.path.join(cy3_dir, keep_2x_file) |
| 1473 shutil.copy(keep_2x_file, destfile) |
| 1474 |
| 1475 class PendingThreadsError(RuntimeError): |
| 1476 pass |
| 1477 |
| 1478 threads_seen = [] |
| 1479 |
| 1480 def check_thread_termination(ignore_seen=True): |
| 1481 if threading is None: # no threading enabled in CPython |
| 1482 return |
| 1483 current = threading.currentThread() |
| 1484 blocking_threads = [] |
| 1485 for t in threading.enumerate(): |
| 1486 if not t.isAlive() or t == current: |
| 1487 continue |
| 1488 t.join(timeout=2) |
| 1489 if t.isAlive(): |
| 1490 if not ignore_seen: |
| 1491 blocking_threads.append(t) |
| 1492 continue |
| 1493 for seen in threads_seen: |
| 1494 if t is seen: |
| 1495 break |
| 1496 else: |
| 1497 threads_seen.append(t) |
| 1498 blocking_threads.append(t) |
| 1499 if not blocking_threads: |
| 1500 return |
| 1501 sys.stderr.write("warning: left-over threads found after running test:\n") |
| 1502 for t in blocking_threads: |
| 1503 sys.stderr.write('...%s\n' % repr(t)) |
| 1504 raise PendingThreadsError("left-over threads found after running test") |
| 1505 |
| 1506 def subprocess_output(cmd): |
| 1507 try: |
| 1508 p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDO
UT) |
| 1509 return p.communicate()[0].decode('UTF-8') |
| 1510 except OSError: |
| 1511 return '' |
| 1512 |
| 1513 def get_version(): |
| 1514 from Cython.Compiler.Version import version as cython_version |
| 1515 full_version = cython_version |
| 1516 top = os.path.dirname(os.path.abspath(__file__)) |
| 1517 if os.path.exists(os.path.join(top, '.git')): |
| 1518 old_dir = os.getcwd() |
| 1519 try: |
| 1520 os.chdir(top) |
| 1521 head_commit = subprocess_output(['git', 'rev-parse', 'HEAD']).strip(
) |
| 1522 version_commit = subprocess_output(['git', 'rev-parse', cython_versi
on]).strip() |
| 1523 diff = subprocess_output(['git', 'diff', '--stat']).strip() |
| 1524 if head_commit != version_commit: |
| 1525 full_version += " " + head_commit |
| 1526 if diff: |
| 1527 full_version += ' + uncommitted changes' |
| 1528 finally: |
| 1529 os.chdir(old_dir) |
| 1530 return full_version |
| 1531 |
| 1532 _orig_stdout, _orig_stderr = sys.stdout, sys.stderr |
| 1533 def flush_and_terminate(status): |
| 1534 try: |
| 1535 _orig_stdout.flush() |
| 1536 _orig_stderr.flush() |
| 1537 finally: |
| 1538 os._exit(status) |
| 1539 |
| 1540 def main(): |
| 1541 |
| 1542 global DISTDIR, WITH_CYTHON |
| 1543 DISTDIR = os.path.join(os.getcwd(), os.path.dirname(sys.argv[0])) |
| 1544 |
| 1545 from optparse import OptionParser |
| 1546 parser = OptionParser() |
| 1547 parser.add_option("--no-cleanup", dest="cleanup_workdir", |
| 1548 action="store_false", default=True, |
| 1549 help="do not delete the generated C files (allows passing
--no-cython on next run)") |
| 1550 parser.add_option("--no-cleanup-sharedlibs", dest="cleanup_sharedlibs", |
| 1551 action="store_false", default=True, |
| 1552 help="do not delete the generated shared libary files (all
ows manual module experimentation)") |
| 1553 parser.add_option("--no-cleanup-failures", dest="cleanup_failures", |
| 1554 action="store_false", default=True, |
| 1555 help="enable --no-cleanup and --no-cleanup-sharedlibs for
failed tests only") |
| 1556 parser.add_option("--no-cython", dest="with_cython", |
| 1557 action="store_false", default=True, |
| 1558 help="do not run the Cython compiler, only the C compiler"
) |
| 1559 parser.add_option("--compiler", dest="compiler", default=None, |
| 1560 help="C compiler type") |
| 1561 backend_list = ','.join(BACKENDS) |
| 1562 parser.add_option("--backends", dest="backends", default=backend_list, |
| 1563 help="select backends to test (default: %s)" % backend_lis
t) |
| 1564 parser.add_option("--no-c", dest="use_c", |
| 1565 action="store_false", default=True, |
| 1566 help="do not test C compilation backend") |
| 1567 parser.add_option("--no-cpp", dest="use_cpp", |
| 1568 action="store_false", default=True, |
| 1569 help="do not test C++ compilation backend") |
| 1570 parser.add_option("--no-unit", dest="unittests", |
| 1571 action="store_false", default=True, |
| 1572 help="do not run the unit tests") |
| 1573 parser.add_option("--no-doctest", dest="doctests", |
| 1574 action="store_false", default=True, |
| 1575 help="do not run the doctests") |
| 1576 parser.add_option("--no-file", dest="filetests", |
| 1577 action="store_false", default=True, |
| 1578 help="do not run the file based tests") |
| 1579 parser.add_option("--no-pyregr", dest="pyregr", |
| 1580 action="store_false", default=True, |
| 1581 help="do not run the regression tests of CPython in tests/
pyregr/") |
| 1582 parser.add_option("--cython-only", dest="cython_only", |
| 1583 action="store_true", default=False, |
| 1584 help="only compile pyx to c, do not run C compiler or run
the tests") |
| 1585 parser.add_option("--no-refnanny", dest="with_refnanny", |
| 1586 action="store_false", default=True, |
| 1587 help="do not regression test reference counting") |
| 1588 parser.add_option("--no-fork", dest="fork", |
| 1589 action="store_false", default=True, |
| 1590 help="do not fork to run tests") |
| 1591 parser.add_option("--sys-pyregr", dest="system_pyregr", |
| 1592 action="store_true", default=False, |
| 1593 help="run the regression tests of the CPython installation
") |
| 1594 parser.add_option("-x", "--exclude", dest="exclude", |
| 1595 action="append", metavar="PATTERN", |
| 1596 help="exclude tests matching the PATTERN") |
| 1597 parser.add_option("--shard_count", dest="shard_count", metavar="N", |
| 1598 type=int, default=1, |
| 1599 help="shard this run into several parallel runs") |
| 1600 parser.add_option("--shard_num", dest="shard_num", metavar="K", |
| 1601 type=int, default=-1, |
| 1602 help="test only this single shard") |
| 1603 parser.add_option("-C", "--coverage", dest="coverage", |
| 1604 action="store_true", default=False, |
| 1605 help="collect source coverage data for the Compiler") |
| 1606 parser.add_option("--coverage-xml", dest="coverage_xml", |
| 1607 action="store_true", default=False, |
| 1608 help="collect source coverage data for the Compiler in XML
format") |
| 1609 parser.add_option("--coverage-html", dest="coverage_html", |
| 1610 action="store_true", default=False, |
| 1611 help="collect source coverage data for the Compiler in HTM
L format") |
| 1612 parser.add_option("-A", "--annotate", dest="annotate_source", |
| 1613 action="store_true", default=True, |
| 1614 help="generate annotated HTML versions of the test source
files") |
| 1615 parser.add_option("--no-annotate", dest="annotate_source", |
| 1616 action="store_false", |
| 1617 help="do not generate annotated HTML versions of the test
source files") |
| 1618 parser.add_option("-v", "--verbose", dest="verbosity", |
| 1619 action="count", default=0, |
| 1620 help="display test progress, pass twice to print test name
s") |
| 1621 parser.add_option("-T", "--ticket", dest="tickets", |
| 1622 action="append", |
| 1623 help="a bug ticket number to run the respective test in 't
ests/*'") |
| 1624 parser.add_option("-3", dest="language_level", |
| 1625 action="store_const", const=3, default=2, |
| 1626 help="set language level to Python 3 (useful for running t
he CPython regression tests)'") |
| 1627 parser.add_option("--xml-output", dest="xml_output_dir", metavar="DIR", |
| 1628 help="write test results in XML to directory DIR") |
| 1629 parser.add_option("--exit-ok", dest="exit_ok", default=False, |
| 1630 action="store_true", |
| 1631 help="exit without error code even on test failures") |
| 1632 parser.add_option("--root-dir", dest="root_dir", default=os.path.join(DISTDI
R, 'tests'), |
| 1633 help="working directory") |
| 1634 parser.add_option("--work-dir", dest="work_dir", default=os.path.join(os.get
cwd(), 'BUILD'), |
| 1635 help="working directory") |
| 1636 parser.add_option("--cython-dir", dest="cython_dir", default=os.getcwd(), |
| 1637 help="Cython installation directory (default: use local so
urce version)") |
| 1638 parser.add_option("--debug", dest="for_debugging", default=False, action="st
ore_true", |
| 1639 help="configure for easier use with a debugger (e.g. gdb)"
) |
| 1640 parser.add_option("--pyximport-py", dest="pyximport_py", default=False, acti
on="store_true", |
| 1641 help="use pyximport to automatically compile imported .pyx
and .py files") |
| 1642 parser.add_option("--watermark", dest="watermark", default=None, |
| 1643 help="deterministic generated by string") |
| 1644 |
| 1645 options, cmd_args = parser.parse_args() |
| 1646 |
| 1647 WORKDIR = os.path.abspath(options.work_dir) |
| 1648 |
| 1649 if sys.version_info[0] >= 3: |
| 1650 options.doctests = False |
| 1651 if options.with_cython: |
| 1652 sys.path.insert(0, options.cython_dir) |
| 1653 try: |
| 1654 # try if Cython is installed in a Py3 version |
| 1655 import Cython.Compiler.Main |
| 1656 except Exception: |
| 1657 # back out anything the import process loaded, then |
| 1658 # 2to3 the Cython sources to make them re-importable |
| 1659 cy_modules = [ name for name in sys.modules |
| 1660 if name == 'Cython' or name.startswith('Cython.')
] |
| 1661 for name in cy_modules: |
| 1662 del sys.modules[name] |
| 1663 # hasn't been refactored yet - do it now |
| 1664 global CY3_DIR |
| 1665 CY3_DIR = cy3_dir = os.path.join(WORKDIR, 'Cy3') |
| 1666 if sys.version_info >= (3,1): |
| 1667 refactor_for_py3(DISTDIR, cy3_dir) |
| 1668 elif os.path.isdir(cy3_dir): |
| 1669 sys.path.insert(0, cy3_dir) |
| 1670 else: |
| 1671 options.with_cython = False |
| 1672 |
| 1673 if options.watermark: |
| 1674 import Cython.Compiler.Version |
| 1675 Cython.Compiler.Version.watermark = options.watermark |
| 1676 |
| 1677 WITH_CYTHON = options.with_cython |
| 1678 |
| 1679 coverage = None |
| 1680 if options.coverage or options.coverage_xml or options.coverage_html: |
| 1681 if options.shard_count <= 1 and options.shard_num < 0: |
| 1682 if not WITH_CYTHON: |
| 1683 options.coverage = options.coverage_xml = options.coverage_html
= False |
| 1684 else: |
| 1685 print("Enabling coverage analysis") |
| 1686 from coverage import coverage as _coverage |
| 1687 coverage = _coverage(branch=True, omit=['Test*']) |
| 1688 coverage.erase() |
| 1689 coverage.start() |
| 1690 |
| 1691 if WITH_CYTHON: |
| 1692 global CompilationOptions, pyrex_default_options, cython_compile |
| 1693 from Cython.Compiler.Main import \ |
| 1694 CompilationOptions, \ |
| 1695 default_options as pyrex_default_options, \ |
| 1696 compile as cython_compile |
| 1697 from Cython.Compiler import Errors |
| 1698 Errors.LEVEL = 0 # show all warnings |
| 1699 from Cython.Compiler import Options |
| 1700 Options.generate_cleanup_code = 3 # complete cleanup code |
| 1701 from Cython.Compiler import DebugFlags |
| 1702 DebugFlags.debug_temp_code_comments = 1 |
| 1703 |
| 1704 if options.shard_count > 1 and options.shard_num == -1: |
| 1705 import multiprocessing |
| 1706 pool = multiprocessing.Pool(options.shard_count) |
| 1707 tasks = [(options, cmd_args, shard_num) for shard_num in range(options.s
hard_count)] |
| 1708 errors = [] |
| 1709 for shard_num, return_code in pool.imap_unordered(runtests_callback, tas
ks): |
| 1710 if return_code != 0: |
| 1711 errors.append(shard_num) |
| 1712 print("FAILED (%s/%s)" % (shard_num, options.shard_count)) |
| 1713 print("ALL DONE (%s/%s)" % (shard_num, options.shard_count)) |
| 1714 pool.close() |
| 1715 pool.join() |
| 1716 if errors: |
| 1717 print("Errors for shards %s" % ", ".join([str(e) for e in errors])) |
| 1718 return_code = 1 |
| 1719 else: |
| 1720 return_code = 0 |
| 1721 else: |
| 1722 _, return_code = runtests(options, cmd_args, coverage) |
| 1723 print("ALL DONE") |
| 1724 |
| 1725 try: |
| 1726 check_thread_termination(ignore_seen=False) |
| 1727 except PendingThreadsError: |
| 1728 # normal program exit won't kill the threads, do it the hard way here |
| 1729 flush_and_terminate(return_code) |
| 1730 else: |
| 1731 sys.exit(return_code) |
| 1732 |
| 1733 |
| 1734 def runtests_callback(args): |
| 1735 options, cmd_args, shard_num = args |
| 1736 options.shard_num = shard_num |
| 1737 return runtests(options, cmd_args) |
| 1738 |
| 1739 def runtests(options, cmd_args, coverage=None): |
| 1740 |
| 1741 WITH_CYTHON = options.with_cython |
| 1742 ROOTDIR = os.path.abspath(options.root_dir) |
| 1743 WORKDIR = os.path.abspath(options.work_dir) |
| 1744 |
| 1745 if options.shard_num > -1: |
| 1746 WORKDIR = os.path.join(WORKDIR, str(options.shard_num)) |
| 1747 |
| 1748 # RUN ALL TESTS! |
| 1749 UNITTEST_MODULE = "Cython" |
| 1750 UNITTEST_ROOT = os.path.join(os.path.dirname(__file__), UNITTEST_MODULE) |
| 1751 if WITH_CYTHON: |
| 1752 if os.path.exists(WORKDIR): |
| 1753 for path in os.listdir(WORKDIR): |
| 1754 if path in ("support", "Cy3"): continue |
| 1755 shutil.rmtree(os.path.join(WORKDIR, path), ignore_errors=True) |
| 1756 if not os.path.exists(WORKDIR): |
| 1757 os.makedirs(WORKDIR) |
| 1758 |
| 1759 if options.shard_num <= 0: |
| 1760 sys.stderr.write("Python %s\n" % sys.version) |
| 1761 sys.stderr.write("\n") |
| 1762 if WITH_CYTHON: |
| 1763 sys.stderr.write("Running tests against Cython %s\n" % get_version()
) |
| 1764 else: |
| 1765 sys.stderr.write("Running tests without Cython.\n") |
| 1766 |
| 1767 if options.for_debugging: |
| 1768 options.cleanup_workdir = False |
| 1769 options.cleanup_sharedlibs = False |
| 1770 options.fork = False |
| 1771 if WITH_CYTHON and include_debugger: |
| 1772 from Cython.Compiler.Main import default_options as compiler_default
_options |
| 1773 compiler_default_options['gdb_debug'] = True |
| 1774 compiler_default_options['output_dir'] = os.getcwd() |
| 1775 |
| 1776 if options.with_refnanny: |
| 1777 from pyximport.pyxbuild import pyx_to_dll |
| 1778 libpath = pyx_to_dll(os.path.join("Cython", "Runtime", "refnanny.pyx"), |
| 1779 build_in_temp=True, |
| 1780 pyxbuild_dir=os.path.join(WORKDIR, "support")) |
| 1781 sys.path.insert(0, os.path.split(libpath)[0]) |
| 1782 CFLAGS.append("-DCYTHON_REFNANNY=1") |
| 1783 |
| 1784 if options.xml_output_dir and options.fork: |
| 1785 # doesn't currently work together |
| 1786 sys.stderr.write("Disabling forked testing to support XML test output\n"
) |
| 1787 options.fork = False |
| 1788 |
| 1789 if WITH_CYTHON and options.language_level == 3: |
| 1790 sys.stderr.write("Using Cython language level 3.\n") |
| 1791 |
| 1792 test_bugs = False |
| 1793 if options.tickets: |
| 1794 for ticket_number in options.tickets: |
| 1795 test_bugs = True |
| 1796 cmd_args.append('ticket:%s' % ticket_number) |
| 1797 if not test_bugs: |
| 1798 for selector in cmd_args: |
| 1799 if selector.startswith('bugs'): |
| 1800 test_bugs = True |
| 1801 |
| 1802 selectors = [ string_selector(r) for r in cmd_args ] |
| 1803 if not selectors: |
| 1804 selectors = [ lambda x, tags=None: True ] |
| 1805 |
| 1806 # Chech which external modules are not present and exclude tests |
| 1807 # which depends on them (by prefix) |
| 1808 |
| 1809 missing_dep_excluder = MissingDependencyExcluder(EXT_DEP_MODULES) |
| 1810 version_dep_excluder = VersionDependencyExcluder(VER_DEP_MODULES) |
| 1811 exclude_selectors = [missing_dep_excluder, version_dep_excluder] # want to p
rint msg at exit |
| 1812 |
| 1813 if options.exclude: |
| 1814 exclude_selectors += [ string_selector(r) for r in options.exclude ] |
| 1815 |
| 1816 if options.shard_num > -1: |
| 1817 exclude_selectors.append(ShardExcludeSelector(options.shard_num, options
.shard_count)) |
| 1818 |
| 1819 if not test_bugs: |
| 1820 exclude_selectors += [ FileListExcluder(os.path.join(ROOTDIR, "bugs.txt"
)) ] |
| 1821 |
| 1822 if sys.platform in ['win32', 'cygwin'] and sys.version_info < (2,6): |
| 1823 exclude_selectors += [ lambda x: x == "run.specialfloat" ] |
| 1824 |
| 1825 global COMPILER |
| 1826 if options.compiler: |
| 1827 COMPILER = options.compiler |
| 1828 |
| 1829 selected_backends = [ name.strip() for name in options.backends.split(',') i
f name.strip() ] |
| 1830 backends = [] |
| 1831 for backend in selected_backends: |
| 1832 if backend == 'c' and not options.use_c: |
| 1833 continue |
| 1834 elif backend == 'cpp' and not options.use_cpp: |
| 1835 continue |
| 1836 elif backend not in BACKENDS: |
| 1837 sys.stderr.write("Unknown backend requested: '%s' not one of [%s]\n"
% ( |
| 1838 backend, ','.join(BACKENDS))) |
| 1839 sys.exit(1) |
| 1840 backends.append(backend) |
| 1841 if options.shard_num <= 0: |
| 1842 sys.stderr.write("Backends: %s\n" % ','.join(backends)) |
| 1843 languages = backends |
| 1844 |
| 1845 sys.stderr.write("\n") |
| 1846 |
| 1847 test_suite = unittest.TestSuite() |
| 1848 |
| 1849 if options.unittests: |
| 1850 collect_unittests(UNITTEST_ROOT, UNITTEST_MODULE + ".", test_suite, sele
ctors, exclude_selectors) |
| 1851 |
| 1852 if options.doctests: |
| 1853 collect_doctests(UNITTEST_ROOT, UNITTEST_MODULE + ".", test_suite, selec
tors, exclude_selectors) |
| 1854 |
| 1855 if options.filetests and languages: |
| 1856 filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors, |
| 1857 options.annotate_source, options.cleanup_workdir
, |
| 1858 options.cleanup_sharedlibs, options.cleanup_fail
ures, |
| 1859 options.pyregr, |
| 1860 options.cython_only, languages, test_bugs, |
| 1861 options.fork, options.language_level) |
| 1862 test_suite.addTest(filetests.build_suite()) |
| 1863 |
| 1864 if options.system_pyregr and languages: |
| 1865 sys_pyregr_dir = os.path.join(sys.prefix, 'lib', 'python'+sys.version[:3
], 'test') |
| 1866 if os.path.isdir(sys_pyregr_dir): |
| 1867 filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selecto
rs, |
| 1868 options.annotate_source, options.cleanup_wor
kdir, |
| 1869 options.cleanup_sharedlibs, options.cleanup_
failures, |
| 1870 True, |
| 1871 options.cython_only, languages, test_bugs, |
| 1872 options.fork, sys.version_info[0]) |
| 1873 sys.stderr.write("Including CPython regression tests in %s\n" % sys_
pyregr_dir) |
| 1874 test_suite.addTest(filetests.handle_directory(sys_pyregr_dir, 'pyreg
r')) |
| 1875 |
| 1876 if options.xml_output_dir: |
| 1877 from Cython.Tests.xmlrunner import XMLTestRunner |
| 1878 test_runner = XMLTestRunner(output=options.xml_output_dir, |
| 1879 verbose=options.verbosity > 0) |
| 1880 else: |
| 1881 test_runner = unittest.TextTestRunner(verbosity=options.verbosity) |
| 1882 |
| 1883 if options.pyximport_py: |
| 1884 from pyximport import pyximport |
| 1885 pyximport.install(pyimport=True, build_dir=os.path.join(WORKDIR, '_pyxim
port'), |
| 1886 load_py_module_on_import_failure=True, inplace=True) |
| 1887 |
| 1888 result = test_runner.run(test_suite) |
| 1889 |
| 1890 if coverage is not None: |
| 1891 coverage.stop() |
| 1892 ignored_modules = ('Options', 'Version', 'DebugFlags', 'CmdLine') |
| 1893 modules = [ module for name, module in sys.modules.items() |
| 1894 if module is not None and |
| 1895 name.startswith('Cython.Compiler.') and |
| 1896 name[len('Cython.Compiler.'):] not in ignored_modules ] |
| 1897 if options.coverage: |
| 1898 coverage.report(modules, show_missing=0) |
| 1899 if options.coverage_xml: |
| 1900 coverage.xml_report(modules, outfile="coverage-report.xml") |
| 1901 if options.coverage_html: |
| 1902 coverage.html_report(modules, directory="coverage-report-html") |
| 1903 |
| 1904 if missing_dep_excluder.tests_missing_deps: |
| 1905 sys.stderr.write("Following tests excluded because of missing dependenci
es on your system:\n") |
| 1906 for test in missing_dep_excluder.tests_missing_deps: |
| 1907 sys.stderr.write(" %s\n" % test) |
| 1908 |
| 1909 if options.with_refnanny: |
| 1910 import refnanny |
| 1911 sys.stderr.write("\n".join([repr(x) for x in refnanny.reflog])) |
| 1912 |
| 1913 if options.exit_ok: |
| 1914 return options.shard_num, 0 |
| 1915 else: |
| 1916 return options.shard_num, not result.wasSuccessful() |
| 1917 |
| 1918 |
| 1919 if __name__ == '__main__': |
| 1920 try: |
| 1921 main() |
| 1922 except SystemExit: # <= Py2.4 ... |
| 1923 raise |
| 1924 except Exception: |
| 1925 traceback.print_exc() |
| 1926 try: |
| 1927 check_thread_termination(ignore_seen=False) |
| 1928 except PendingThreadsError: |
| 1929 # normal program exit won't kill the threads, do it the hard way her
e |
| 1930 flush_and_terminate(1) |
OLD | NEW |