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', # GeneratorExit 229 'run.generator_frame_cycle', # yield in 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 import 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', # python2.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, with statement 245 'run.pure_py', # decorators, with statement 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 with 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)" % (tag, 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 == 'msvc': 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("embedded")]: 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_workdir)) 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, filename))) 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=False, 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(self.module)): 632 continue 633 if not cleanup_lib_files and (rmfile.endswith(".so") or rmfile.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_SUPPORT_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, annotate) 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.errors): 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 signal %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.path.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, 'cython.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" % (sys.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.excludes 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.STDOUT) 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_version]).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 (allows 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_list) 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 HTML 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 names") 1621 parser.add_option("-T", "--ticket", dest="tickets", 1622 action="append", 1623 help="a bug ticket number to run the respective test in 'tests/*'") 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 the 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(DISTDIR, 'tests'), 1633 help="working directory") 1634 parser.add_option("--work-dir", dest="work_dir", default=os.path.join(os.getcwd(), '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 source version)") 1638 parser.add_option("--debug", dest="for_debugging", default=False, action="store_true", 1639 help="configure for easier use with a debugger (e.g. gdb)") 1640 parser.add_option("--pyximport-py", dest="pyximport_py", default=False, action="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.shard_count)] 1708 errors = [] 1709 for shard_num, return_code in pool.imap_unordered(runtests_callback, tasks): 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 print 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(',') if 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, selectors, exclude_selectors) 1851 1852 if options.doctests: 1853 collect_doctests(UNITTEST_ROOT, UNITTEST_MODULE + ".", test_suite, selectors, 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_failures, 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_selectors, 1868 options.annotate_source, options.cleanup_workdir, 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, 'pyregr')) 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, '_pyximport'), 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 dependencies 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 here 1930 flush_and_terminate(1) 1931