runtests.py 76.4 KB
Newer Older
1
#!/usr/bin/env python
2

3 4 5
import os
import sys
import re
6
import gc
7
import locale
8
import shutil
9
import time
10 11
import unittest
import doctest
12
import operator
Robert Bradshaw's avatar
Robert Bradshaw committed
13
import subprocess
14
import tempfile
15
import traceback
Robert Bradshaw's avatar
Robert Bradshaw committed
16 17
import warnings

18 19 20 21 22 23 24 25
try:
    import platform
    IS_PYPY = platform.python_implementation() == 'PyPy'
    IS_CPYTHON = platform.python_implementation() == 'CPython'
except (ImportError, AttributeError):
    IS_CPYTHON = True
    IS_PYPY = False

26 27 28 29
try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO
30 31 32 33 34 35

try:
    import cPickle as pickle
except ImportError:
    import pickle

36 37 38 39 40
try:
    from io import open as io_open
except ImportError:
    from codecs import open as io_open

41 42 43 44 45
try:
    import threading
except ImportError: # No threads, no problems
    threading = None

Robert Bradshaw's avatar
Robert Bradshaw committed
46 47 48 49 50 51 52 53 54 55 56 57 58
try:
    from collections import defaultdict
except ImportError:
    class defaultdict(object):
        def __init__(self, default_factory=lambda : None):
            self._dict = {}
            self.default_factory = default_factory
        def __getitem__(self, key):
            if key not in self._dict:
                self._dict[key] = self.default_factory()
            return self._dict[key]
        def __setitem__(self, key, value):
            self._dict[key] = value
Stefan Behnel's avatar
Stefan Behnel committed
59 60
        def __contains__(self, key):
            return key in self._dict
Robert Bradshaw's avatar
Robert Bradshaw committed
61 62
        def __repr__(self):
            return repr(self._dict)
Stefan Behnel's avatar
Stefan Behnel committed
63 64
        def __nonzero__(self):
            return bool(self._dict)
Robert Bradshaw's avatar
Robert Bradshaw committed
65

Stefan Behnel's avatar
Stefan Behnel committed
66 67 68 69 70
try:
    basestring
except NameError:
    basestring = str

71
WITH_CYTHON = True
72
CY3_DIR = None
73 74 75 76

from distutils.dist import Distribution
from distutils.core import Extension
from distutils.command.build_ext import build_ext as _build_ext
77
from distutils import sysconfig
78 79
distutils_distro = Distribution()

80

Robert Bradshaw's avatar
Robert Bradshaw committed
81 82 83 84 85 86 87 88 89 90 91 92
if sys.platform == 'win32':
    # TODO: Figure out why this hackery (see http://thread.gmane.org/gmane.comp.python.cython.devel/8280/).
    config_files = distutils_distro.find_config_files()
    try: config_files.remove('setup.cfg')
    except ValueError: pass
    distutils_distro.parse_config_files(config_files)

    cfgfiles = distutils_distro.find_config_files()
    try: cfgfiles.remove('setup.cfg')
    except ValueError: pass
    distutils_distro.parse_config_files(cfgfiles)

93
EXT_DEP_MODULES = {
94 95 96
    'tag:numpy' : 'numpy',
    'tag:pstats': 'pstats',
    'tag:posix' : 'posix',
97
    'tag:array' : 'array',
98
    'tag:ipython': 'IPython'
99 100
}

101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
def patch_inspect_isfunction():
    import inspect
    orig_isfunction = inspect.isfunction
    def isfunction(obj):
        return orig_isfunction(obj) or type(obj).__name__ == 'cython_function_or_method'
    isfunction._orig_isfunction = orig_isfunction
    inspect.isfunction = isfunction

def unpatch_inspect_isfunction():
    import inspect
    try:
        orig_isfunction = inspect.isfunction._orig_isfunction
    except AttributeError:
        pass
    else:
        inspect.isfunction = orig_isfunction

118 119 120 121
def update_linetrace_extension(ext):
    ext.define_macros.append(('CYTHON_TRACE', 1))
    return ext

122
def update_numpy_extension(ext):
123
    import numpy
124 125
    from numpy.distutils.misc_util import get_info

126
    ext.include_dirs.append(numpy.get_include())
127

128 129 130 131 132
    # We need the npymath library for numpy.math.
    # This is typically a static-only library.
    for attr, value in get_info('npymath').items():
        getattr(ext, attr).extend(value)

133
def update_openmp_extension(ext):
134
    ext.openmp = True
135 136 137 138 139 140 141 142 143 144 145 146 147
    language = ext.language

    if language == 'cpp':
        flags = OPENMP_CPP_COMPILER_FLAGS
    else:
        flags = OPENMP_C_COMPILER_FLAGS

    if flags:
        compile_flags, link_flags = flags

        ext.extra_compile_args.extend(compile_flags.split())
        ext.extra_link_args.extend(link_flags.split())
        return ext
148 149
    elif sys.platform == 'win32':
        return ext
150 151 152 153 154 155 156 157 158 159 160 161 162 163

    return EXCLUDE_EXT

def get_openmp_compiler_flags(language):
    """
    As of gcc 4.2, it supports OpenMP 2.5. Gcc 4.4 implements 3.0. We don't
    (currently) check for other compilers.

    returns a two-tuple of (CFLAGS, LDFLAGS) to build the OpenMP extension
    """
    if language == 'cpp':
        cc = sysconfig.get_config_var('CXX')
    else:
        cc = sysconfig.get_config_var('CC')
164 165

    if not cc:
166 167 168
        if sys.platform == 'win32':
            return '/openmp', ''
        return None
169

170 171 172
    # For some reason, cc can be e.g. 'gcc -pthread'
    cc = cc.split()[0]

173 174 175 176
    # Force english output
    env = os.environ.copy()
    env['LC_MESSAGES'] = 'C'

177 178
    matcher = re.compile(r"gcc version (\d+\.\d+)").search
    try:
179
        p = subprocess.Popen([cc, "-v"], stderr=subprocess.PIPE, env=env)
Robert Bradshaw's avatar
Robert Bradshaw committed
180 181 182 183 184 185
    except EnvironmentError:
        # Be compatible with Python 3
        warnings.warn("Unable to find the %s compiler: %s: %s" %
                      (language, os.strerror(sys.exc_info()[1].errno), cc))
        return None
    _, output = p.communicate()
186

Stefan Behnel's avatar
Stefan Behnel committed
187
    output = output.decode(locale.getpreferredencoding() or 'ASCII', 'replace')
188

189 190 191 192 193
    gcc_version = matcher(output)
    if not gcc_version:
        return None # not gcc - FIXME: do something about other compilers

    compiler_version = gcc_version.group(1)
194 195 196
    if compiler_version and compiler_version.split('.') >= ['4', '2']:
        return '-fopenmp', '-fopenmp'

197 198 199 200
try:
    locale.setlocale(locale.LC_ALL, '')
except locale.Error:
    pass
201

202 203 204 205 206 207
OPENMP_C_COMPILER_FLAGS = get_openmp_compiler_flags('c')
OPENMP_CPP_COMPILER_FLAGS = get_openmp_compiler_flags('cpp')

# Return this from the EXT_EXTRAS matcher callback to exclude the extension
EXCLUDE_EXT = object()

208 209
EXT_EXTRAS = {
    'tag:numpy' : update_numpy_extension,
210
    'tag:openmp': update_openmp_extension,
211
    'tag:trace' : update_linetrace_extension,
212
}
213

214 215 216 217 218

def _is_py3_before_32(excluded, version):
    return version[0] >= 3 and version < (3,2)


Robert Bradshaw's avatar
Robert Bradshaw committed
219
# TODO: use tags
220
VER_DEP_MODULES = {
221
    # tests are excluded if 'CurrentPythonVersion OP VersionTuple', i.e.
Stefan Behnel's avatar
Stefan Behnel committed
222 223
    # (2,4) : (operator.lt, ...) excludes ... when PyVer < 2.4.x
    (2,4) : (operator.lt, lambda x: x in ['run.extern_builtins_T258',
224 225
                                          'run.builtin_sorted',
                                          'run.reversed_iteration',
Stefan Behnel's avatar
Stefan Behnel committed
226
                                          ]),
227 228
    (2,5) : (operator.lt, lambda x: x in ['run.any',
                                          'run.all',
229
                                          'run.yield_from_pep380',  # GeneratorExit
230
                                          'run.generator_frame_cycle', # yield in try-finally
231
                                          'run.generator_expressions_in_class',
232
                                          'run.absolute_import',
Haoyu Bai's avatar
Haoyu Bai committed
233
                                          'run.relativeimport_T542',
234
                                          'run.relativeimport_star_T542',
235
                                          'run.initial_file_path',  # relative import
Stefan Behnel's avatar
Stefan Behnel committed
236
                                          'run.pynumber_subtype_conversion',  # bug in Py2.4
237
                                          'build.cythonize_script',  # python2.4 -m a.b.c
Stefan Behnel's avatar
Stefan Behnel committed
238
                                          'build.cythonize_script_excludes',  # python2.4 -m a.b.c
239
                                          'build.cythonize_script_package',  # python2.4 -m a.b.c
240
                                          ]),
241
    (2,6) : (operator.lt, lambda x: x in ['run.print_function',
242
                                          'run.language_level', # print function
243
                                          'run.cython3',
244
                                          'run.property_decorator_T593', # prop.setter etc.
Stefan Behnel's avatar
Stefan Behnel committed
245
                                          'run.generators_py', # generators, with statement
246
                                          'run.pure_py', # decorators, with statement
247
                                          'run.purecdef',
248
                                          'run.struct_conversion',
249 250
                                          'run.bytearray_coercion',
                                          'run.bytearraymethods',
251 252
                                          'run.bytearray_ascii_auto_encoding',
                                          'run.bytearray_default_auto_encoding',
Stefan Behnel's avatar
Stefan Behnel committed
253
                                          # memory views require buffer protocol
254
                                          'memoryview.relaxed_strides',
255 256 257 258 259
                                          'memoryview.cythonarray',
                                          'memoryview.memslice',
                                          'memoryview.numpy_memoryview',
                                          'memoryview.memoryviewattrs',
                                          'memoryview.memoryview',
260
                                          'run.withstat_py',
261
                                          ]),
262
    (2,7) : (operator.lt, lambda x: x in ['run.withstat_py27', # multi context with statement
Stefan Behnel's avatar
Stefan Behnel committed
263
                                          'run.yield_inside_lambda',
264
                                          'run.test_dictviews',
265
                                          'run.pyclass_special_methods',
Stefan Behnel's avatar
Stefan Behnel committed
266
                                          ]),
267 268 269 270
    # The next line should start (3,); but this is a dictionary, so
    # we can only have one (3,) key.  Since 2.7 is supposed to be the
    # last 2.x release, things would have to change drastically for this
    # to be unsafe...
271 272 273
    (2,999): (operator.lt, lambda x: x in ['run.special_methods_T561_py3',
                                           'run.test_raisefrom',
                                           ]),
274
    (3,): (operator.ge, lambda x: x in ['run.non_future_division',
Stefan Behnel's avatar
Stefan Behnel committed
275
                                        'compile.extsetslice',
276
                                        'compile.extdelslice',
277 278 279 280
                                        'run.special_methods_T561_py2'
                                        ]),
    (3,1): (_is_py3_before_32, lambda x: x in ['run.pyclass_special_methods',
                                               ]),
281 282
    (3,3) : (operator.lt, lambda x: x in ['build.package_compilation',
                                          ]),
283 284
    (3,4,0,'beta',3) : (operator.le, lambda x: x in ['run.py34_signature',
                                          ]),
285 286
}

287 288 289 290 291 292 293 294
# files that should not be converted to Python 3 code with 2to3
KEEP_2X_FILES = [
    os.path.join('Cython', 'Debugger', 'Tests', 'test_libcython_in_gdb.py'),
    os.path.join('Cython', 'Debugger', 'Tests', 'test_libpython_in_gdb.py'),
    os.path.join('Cython', 'Debugger', 'libcython.py'),
    os.path.join('Cython', 'Debugger', 'libpython.py'),
]

295
COMPILER = None
296
INCLUDE_DIRS = [ d for d in os.getenv('INCLUDE', '').split(os.pathsep) if d ]
297
CFLAGS = os.getenv('CFLAGS', '').split()
298
CCACHE = os.getenv('CYTHON_RUNTESTS_CCACHE', '').split()
299
TEST_SUPPORT_DIR = 'testsupport'
300

301
BACKENDS = ['c', 'cpp']
302

303 304 305
UTF8_BOM_BYTES = r'\xef\xbb\xbf'.encode('ISO-8859-1').decode('unicode_escape')


306 307 308 309 310 311 312 313 314 315
def memoize(f):
    uncomputed = object()
    f._cache = {}
    def func(*args):
        res = f._cache.get(args, uncomputed)
        if res is uncomputed:
            res = f._cache[args] = f(*args)
        return res
    return func

316

Robert Bradshaw's avatar
Robert Bradshaw committed
317
@memoize
Robert Bradshaw's avatar
Robert Bradshaw committed
318 319
def parse_tags(filepath):
    tags = defaultdict(list)
320
    parse_tag = re.compile(r'#\s*(\w+)\s*:(.*)$').match
321
    f = io_open(filepath, encoding='ISO-8859-1', errors='ignore')
322 323
    try:
        for line in f:
324 325
            # ignore BOM-like bytes and whitespace
            line = line.lstrip(UTF8_BOM_BYTES).strip()
326
            if not line:
327 328 329 330
                if tags:
                    break  # assume all tags are in one block
                else:
                    continue
331 332
            if line[0] != '#':
                break
333 334 335 336 337 338 339 340
            parsed = parse_tag(line)
            if parsed:
                tag, values = parsed.groups()
                if tag in ('coding', 'encoding'):
                    continue
                if tag == 'tags':
                    tag = 'tag'
                    print("WARNING: test tags use the 'tag' directive, not 'tags' (%s)" % filepath)
341
                if tag not in ('mode', 'tag', 'ticket', 'cython', 'distutils'):
342 343 344 345 346
                    print("WARNING: unknown test directive '%s' found (%s)" % (tag, filepath))
                values = values.split(',')
                tags[tag].extend(filter(None, [value.strip() for value in values]))
            elif tags:
                break  # assume all tags are in one block
347 348
    finally:
        f.close()
Robert Bradshaw's avatar
Robert Bradshaw committed
349 350
    return tags

351

Robert Bradshaw's avatar
Robert Bradshaw committed
352 353
list_unchanging_dir = memoize(lambda x: os.listdir(x))

Stefan Behnel's avatar
Stefan Behnel committed
354

355 356
@memoize
def _list_pyregr_data_files(test_directory):
357 358 359 360
    is_data_file = re.compile('(?:[.](txt|pem|db|html)|^bad.*[.]py)$').search
    return ['__init__.py'] + [
        filename for filename in list_unchanging_dir(test_directory)
        if is_data_file(filename)]
361 362


363 364 365 366 367 368 369 370 371 372 373 374 375 376
def import_ext(module_name, file_path=None):
    if file_path:
        import imp
        return imp.load_dynamic(module_name, file_path)
    else:
        try:
            from importlib import invalidate_caches
        except ImportError:
            pass
        else:
            invalidate_caches()
        return __import__(module_name, globals(), locals(), ['*'])


377 378
class build_ext(_build_ext):
    def build_extension(self, ext):
379 380 381 382 383 384
        try:
            try: # Py2.7+ & Py3.2+
                compiler_obj = self.compiler_obj
            except AttributeError:
                compiler_obj = self.compiler
            if ext.language == 'c++':
385
                compiler_obj.compiler_so.remove('-Wstrict-prototypes')
386 387
            if CCACHE:
                compiler_obj.compiler_so = CCACHE + compiler_obj.compiler_so
388 389
            if getattr(ext, 'openmp', None) and compiler_obj.compiler_type == 'msvc':
                ext.extra_compile_args.append('/openmp')
390 391
        except Exception:
            pass
392
        _build_ext.build_extension(self, ext)
393 394

class ErrorWriter(object):
395
    match_error = re.compile('(warning:)?(?:.*:)?\s*([-0-9]+)\s*:\s*([-0-9]+)\s*:\s*(.*)').match
396 397 398 399
    def __init__(self):
        self.output = []
        self.write = self.output.append

400
    def _collect(self, collect_errors, collect_warnings):
401
        s = ''.join(self.output)
402
        result = []
403 404 405
        for line in s.split('\n'):
            match = self.match_error(line)
            if match:
406 407 408
                is_warning, line, column, message = match.groups()
                if (is_warning and collect_warnings) or \
                        (not is_warning and collect_errors):
409 410
                    result.append( (int(line), int(column), message.strip()) )
        result.sort()
411
        return [ "%d:%d: %s" % values for values in result ]
412 413 414 415 416 417 418 419 420

    def geterrors(self):
        return self._collect(True, False)

    def getwarnings(self):
        return self._collect(False, True)

    def getall(self):
        return self._collect(True, True)
421

422
class TestBuilder(object):
423
    def __init__(self, rootdir, workdir, selectors, exclude_selectors, annotate,
424 425
                 cleanup_workdir, cleanup_sharedlibs, cleanup_failures,
                 with_pyregr, cython_only, languages, test_bugs, fork, language_level):
426 427
        self.rootdir = rootdir
        self.workdir = workdir
428
        self.selectors = selectors
429
        self.exclude_selectors = exclude_selectors
430
        self.annotate = annotate
431
        self.cleanup_workdir = cleanup_workdir
432
        self.cleanup_sharedlibs = cleanup_sharedlibs
433
        self.cleanup_failures = cleanup_failures
434
        self.with_pyregr = with_pyregr
435 436
        self.cython_only = cython_only
        self.languages = languages
437
        self.test_bugs = test_bugs
438
        self.fork = fork
439
        self.language_level = language_level
440 441 442

    def build_suite(self):
        suite = unittest.TestSuite()
443 444 445
        filenames = os.listdir(self.rootdir)
        filenames.sort()
        for filename in filenames:
446
            path = os.path.join(self.rootdir, filename)
447
            if os.path.isdir(path) and filename != TEST_SUPPORT_DIR:
448 449
                if filename == 'pyregr' and not self.with_pyregr:
                    continue
450 451
                if filename == 'broken' and not self.test_bugs:
                    continue
452
                suite.addTest(
453
                    self.handle_directory(path, filename))
454 455
        if sys.platform not in ['win32']:
            # Non-Windows makefile.
456 457
            if [1 for selector in self.selectors if selector("embedded")] \
                and not [1 for selector in self.exclude_selectors if selector("embedded")]:
458
                suite.addTest(unittest.makeSuite(EmbedTest))
459 460
        return suite

461
    def handle_directory(self, path, context):
462 463 464 465
        workdir = os.path.join(self.workdir, context)
        if not os.path.exists(workdir):
            os.makedirs(workdir)

466
        suite = unittest.TestSuite()
Robert Bradshaw's avatar
Robert Bradshaw committed
467
        filenames = list_unchanging_dir(path)
468 469
        filenames.sort()
        for filename in filenames:
470 471 472
            filepath = os.path.join(path, filename)
            module, ext = os.path.splitext(filename)
            if ext not in ('.py', '.pyx', '.srctree'):
473
                continue
474 475
            if filename.startswith('.'):
                continue # certain emacs backup files
476 477 478 479
            if context == 'pyregr':
                tags = defaultdict(list)
            else:
                tags = parse_tags(filepath)
Robert Bradshaw's avatar
Robert Bradshaw committed
480
            fqmodule = "%s.%s" % (context, module)
481
            if not [ 1 for match in self.selectors
Robert Bradshaw's avatar
Robert Bradshaw committed
482
                     if match(fqmodule, tags) ]:
483
                continue
484
            if self.exclude_selectors:
485
                if [1 for match in self.exclude_selectors
Robert Bradshaw's avatar
Robert Bradshaw committed
486
                        if match(fqmodule, tags)]:
487
                    continue
488 489 490 491 492 493 494 495

            mode = 'run' # default
            if tags['mode']:
                mode = tags['mode'][0]
            elif context == 'pyregr':
                mode = 'pyregr'

            if ext == '.srctree':
496 497
                if 'cpp' not in tags['tag'] or 'cpp' in self.languages:
                    suite.addTest(EndToEndTest(filepath, workdir, self.cleanup_workdir))
498 499 500 501 502 503
                continue

            # Choose the test suite.
            if mode == 'pyregr':
                if not filename.startswith('test_'):
                    continue
504
                test_class = CythonPyregrTestCase
505
            elif mode == 'run':
506
                if module.startswith("test_"):
507
                    test_class = CythonUnitTestCase
508
                else:
509
                    test_class = CythonRunTestCase
510
            else:
511
                test_class = CythonCompileTestCase
512

513
            for test in self.build_tests(test_class, path, workdir,
Robert Bradshaw's avatar
Robert Bradshaw committed
514
                                         module, mode == 'error', tags):
515
                suite.addTest(test)
Stefan Behnel's avatar
fix  
Stefan Behnel committed
516
            if mode == 'run' and ext == '.py' and not self.cython_only:
517 518
                # additionally test file in real Python
                suite.addTest(PureDoctestTestCase(module, os.path.join(path, filename)))
519

520 521
        return suite

Robert Bradshaw's avatar
Robert Bradshaw committed
522
    def build_tests(self, test_class, path, workdir, module, expect_errors, tags):
Robert Bradshaw's avatar
Robert Bradshaw committed
523
        if 'werror' in tags['tag']:
Vitja Makarov's avatar
Vitja Makarov committed
524 525 526 527
            warning_errors = True
        else:
            warning_errors = False

528
        if expect_errors:
Robert Bradshaw's avatar
Robert Bradshaw committed
529
            if 'cpp' in tags['tag'] and 'cpp' in self.languages:
Robert Bradshaw's avatar
Robert Bradshaw committed
530 531 532
                languages = ['cpp']
            else:
                languages = self.languages[:1]
533 534
        else:
            languages = self.languages
Robert Bradshaw's avatar
Robert Bradshaw committed
535

Robert Bradshaw's avatar
Robert Bradshaw committed
536
        if 'cpp' in tags['tag'] and 'c' in languages:
537 538
            languages = list(languages)
            languages.remove('c')
Robert Bradshaw's avatar
Robert Bradshaw committed
539 540 541
        elif 'no-cpp' in tags['tag'] and 'cpp' in self.languages:
            languages = list(languages)
            languages.remove('cpp')
542
        tests = [ self.build_test(test_class, path, workdir, module, tags,
Vitja Makarov's avatar
Vitja Makarov committed
543
                                  language, expect_errors, warning_errors)
544
                  for language in languages ]
545 546
        return tests

547
    def build_test(self, test_class, path, workdir, module, tags,
Vitja Makarov's avatar
Vitja Makarov committed
548
                   language, expect_errors, warning_errors):
549 550 551 552
        language_workdir = os.path.join(workdir, language)
        if not os.path.exists(language_workdir):
            os.makedirs(language_workdir)
        workdir = os.path.join(language_workdir, module)
553
        return test_class(path, workdir, module, tags,
554 555 556 557 558
                          language=language,
                          expect_errors=expect_errors,
                          annotate=self.annotate,
                          cleanup_workdir=self.cleanup_workdir,
                          cleanup_sharedlibs=self.cleanup_sharedlibs,
559
                          cleanup_failures=self.cleanup_failures,
560
                          cython_only=self.cython_only,
561
                          fork=self.fork,
Vitja Makarov's avatar
Vitja Makarov committed
562 563
                          language_level=self.language_level,
                          warning_errors=warning_errors)
564

565
class CythonCompileTestCase(unittest.TestCase):
566
    def __init__(self, test_directory, workdir, module, tags, language='c',
567
                 expect_errors=False, annotate=False, cleanup_workdir=True,
568 569
                 cleanup_sharedlibs=True, cleanup_failures=True, cython_only=False,
                 fork=True, language_level=2, warning_errors=False):
570
        self.test_directory = test_directory
571
        self.tags = tags
572 573
        self.workdir = workdir
        self.module = module
574
        self.language = language
575
        self.expect_errors = expect_errors
576
        self.annotate = annotate
577
        self.cleanup_workdir = cleanup_workdir
578
        self.cleanup_sharedlibs = cleanup_sharedlibs
579
        self.cleanup_failures = cleanup_failures
580
        self.cython_only = cython_only
581
        self.fork = fork
582
        self.language_level = language_level
Vitja Makarov's avatar
Vitja Makarov committed
583
        self.warning_errors = warning_errors
584 585 586
        unittest.TestCase.__init__(self)

    def shortDescription(self):
587
        return "compiling (%s) %s" % (self.language, self.module)
588

Stefan Behnel's avatar
Stefan Behnel committed
589
    def setUp(self):
Vitja Makarov's avatar
Vitja Makarov committed
590
        from Cython.Compiler import Options
591
        self._saved_options = [ (name, getattr(Options, name))
592
                                for name in ('warning_errors',
593
                                             'clear_to_none',
594 595
                                             'error_on_unknown_names',
                                             'error_on_uninitialized') ]
596
        self._saved_default_directives = Options.directive_defaults.items()
Vitja Makarov's avatar
Vitja Makarov committed
597
        Options.warning_errors = self.warning_errors
598 599
        if sys.version_info >= (3, 4):
            Options.directive_defaults['autotestdict'] = False
Vitja Makarov's avatar
Vitja Makarov committed
600

601 602
        if not os.path.exists(self.workdir):
            os.makedirs(self.workdir)
Stefan Behnel's avatar
Stefan Behnel committed
603 604 605
        if self.workdir not in sys.path:
            sys.path.insert(0, self.workdir)

606
    def tearDown(self):
Vitja Makarov's avatar
Vitja Makarov committed
607
        from Cython.Compiler import Options
608 609
        for name, value in self._saved_options:
            setattr(Options, name, value)
610
        Options.directive_defaults = dict(self._saved_default_directives)
611
        unpatch_inspect_isfunction()
Vitja Makarov's avatar
Vitja Makarov committed
612

Stefan Behnel's avatar
Stefan Behnel committed
613 614 615 616 617 618 619 620
        try:
            sys.path.remove(self.workdir)
        except ValueError:
            pass
        try:
            del sys.modules[self.module]
        except KeyError:
            pass
621 622 623
        cleanup = self.cleanup_failures or self.success
        cleanup_c_files = WITH_CYTHON and self.cleanup_workdir and cleanup
        cleanup_lib_files = self.cleanup_sharedlibs and cleanup
624
        if os.path.exists(self.workdir):
625 626 627 628 629 630
            if cleanup_c_files and cleanup_lib_files:
                shutil.rmtree(self.workdir, ignore_errors=True)
            else:
                for rmfile in os.listdir(self.workdir):
                    if not cleanup_c_files:
                        if (rmfile[-2:] in (".c", ".h") or
631 632
                                rmfile[-4:] == ".cpp" or
                                rmfile.endswith(".html") and rmfile.startswith(self.module)):
633 634
                            continue
                    if not cleanup_lib_files and (rmfile.endswith(".so") or rmfile.endswith(".dll")):
635
                        continue
636 637 638 639 640 641 642 643
                    try:
                        rmfile = os.path.join(self.workdir, rmfile)
                        if os.path.isdir(rmfile):
                            shutil.rmtree(rmfile, ignore_errors=True)
                        else:
                            os.remove(rmfile)
                    except IOError:
                        pass
644

645
    def runTest(self):
646
        self.success = False
647
        self.runCompileTest()
648
        self.success = True
649 650

    def runCompileTest(self):
651 652 653
        return self.compile(
            self.test_directory, self.module, self.workdir,
            self.test_directory, self.expect_errors, self.annotate)
654

655 656 657 658 659
    def find_module_source_file(self, source_file):
        if not os.path.exists(source_file):
            source_file = source_file[:-1]
        return source_file

Stefan Behnel's avatar
Stefan Behnel committed
660 661 662
    def build_target_filename(self, module_name):
        target = '%s.%s' % (module_name, self.language)
        return target
663

Robert Bradshaw's avatar
Robert Bradshaw committed
664
    def related_files(self, test_directory, module_name):
665
        is_related = re.compile('%s_.*[.].*' % module_name).match
Robert Bradshaw's avatar
Robert Bradshaw committed
666
        return [filename for filename in list_unchanging_dir(test_directory)
667
                if is_related(filename)]
Robert Bradshaw's avatar
Robert Bradshaw committed
668 669

    def copy_files(self, test_directory, target_directory, file_list):
670 671 672 673 674 675 676
        # use symlink on Unix, copy on Windows
        try:
            copy = os.symlink
        except AttributeError:
            copy = shutil.copy

        join = os.path.join
Robert Bradshaw's avatar
Robert Bradshaw committed
677
        for filename in file_list:
678
            file_path = join(test_directory, filename)
679
            if os.path.exists(file_path):
680
                copy(file_path, join(target_directory, filename))
681

Robert Bradshaw's avatar
Robert Bradshaw committed
682 683 684
    def source_files(self, workdir, module_name, file_list):
        return ([self.build_target_filename(module_name)] +
            [filename for filename in file_list
685
             if not os.path.isfile(os.path.join(workdir, filename))])
686 687

    def split_source_and_output(self, test_directory, module, workdir):
688
        source_file = self.find_module_source_file(os.path.join(test_directory, module) + '.pyx')
689
        source_and_output = io_open(source_file, 'rU', encoding='ISO-8859-1')
690
        try:
691 692
            out = io_open(os.path.join(workdir, module + os.path.splitext(source_file)[1]),
                              'w', encoding='ISO-8859-1')
693 694 695 696 697 698 699 700
            for line in source_and_output:
                if line.startswith("_ERRORS"):
                    out.close()
                    out = ErrorWriter()
                else:
                    out.write(line)
        finally:
            source_and_output.close()
701

702 703 704
        try:
            geterrors = out.geterrors
        except AttributeError:
705
            out.close()
706
            return []
707 708 709
        else:
            return geterrors()

710 711
    def run_cython(self, test_directory, module, targetdir, incdir, annotate,
                   extra_compile_options=None):
712
        include_dirs = INCLUDE_DIRS + [os.path.join(test_directory, '..', TEST_SUPPORT_DIR)]
713 714
        if incdir:
            include_dirs.append(incdir)
715
        source = self.find_module_source_file(
716
            os.path.join(test_directory, module + '.pyx'))
Stefan Behnel's avatar
Stefan Behnel committed
717
        target = os.path.join(targetdir, self.build_target_filename(module))
718

719 720
        if extra_compile_options is None:
            extra_compile_options = {}
721

722 723 724 725 726 727
        try:
            CompilationOptions
        except NameError:
            from Cython.Compiler.Main import CompilationOptions
            from Cython.Compiler.Main import compile as cython_compile
            from Cython.Compiler.Main import default_options
728

729
        options = CompilationOptions(
730
            default_options,
731 732
            include_path = include_dirs,
            output_file = target,
733
            annotate = annotate,
734 735
            use_listing_file = False,
            cplus = self.language == 'cpp',
736
            language_level = self.language_level,
737
            generate_pxi = False,
738
            evaluate_tree_assertions = True,
739
            **extra_compile_options
740
            )
741 742 743
        cython_compile(source, options=options,
                       full_module_name=module)

744
    def run_distutils(self, test_directory, module, workdir, incdir,
745
                      extra_extension_args=None):
746 747 748 749 750 751 752 753
        cwd = os.getcwd()
        os.chdir(workdir)
        try:
            build_extension = build_ext(distutils_distro)
            build_extension.include_dirs = INCLUDE_DIRS[:]
            if incdir:
                build_extension.include_dirs.append(incdir)
            build_extension.finalize_options()
754 755
            if COMPILER:
                build_extension.compiler = COMPILER
756

757
            ext_compile_flags = CFLAGS[:]
758 759 760 761
            compiler = COMPILER or sysconfig.get_config_var('CC')

            if self.language == 'c' and compiler == 'gcc':
                ext_compile_flags.extend(['-std=c89', '-pedantic'])
762 763
            if  build_extension.compiler == 'mingw32':
                ext_compile_flags.append('-Wno-format')
764 765
            if extra_extension_args is None:
                extra_extension_args = {}
766

Robert Bradshaw's avatar
Robert Bradshaw committed
767 768
            related_files = self.related_files(test_directory, module)
            self.copy_files(test_directory, workdir, related_files)
769 770
            extension = Extension(
                module,
Robert Bradshaw's avatar
Robert Bradshaw committed
771
                sources = self.source_files(workdir, module, related_files),
772
                extra_compile_args = ext_compile_flags,
773
                **extra_extension_args
774
                )
775 776 777 778 779

            if self.language == 'cpp':
                # Set the language now as the fixer might need it
                extension.language = 'c++'

780 781 782 783 784
            if 'distutils' in self.tags:
                from Cython.Build.Dependencies import DistutilsInfo
                pyx_path = os.path.join(self.test_directory, self.module + ".pyx")
                DistutilsInfo(open(pyx_path)).apply(extension)

Stefan Behnel's avatar
Stefan Behnel committed
785
            for matcher, fixer in list(EXT_EXTRAS.items()):
786
                if isinstance(matcher, str):
Stefan Behnel's avatar
Stefan Behnel committed
787
                    # lazy init
788 789 790
                    del EXT_EXTRAS[matcher]
                    matcher = string_selector(matcher)
                    EXT_EXTRAS[matcher] = fixer
791
                if matcher(module, self.tags):
792 793 794 795
                    newext = fixer(extension)
                    if newext is EXCLUDE_EXT:
                        return
                    extension = newext or extension
796 797
            if self.language == 'cpp':
                extension.language = 'c++'
798 799 800 801 802 803
            build_extension.extensions = [extension]
            build_extension.build_temp = workdir
            build_extension.build_lib  = workdir
            build_extension.run()
        finally:
            os.chdir(cwd)
804

805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
        try:
            get_ext_fullpath = build_extension.get_ext_fullpath
        except AttributeError:
            def get_ext_fullpath(ext_name, self=build_extension):
                # copied from distutils.command.build_ext (missing in Py2.[45])
                fullname = self.get_ext_fullname(ext_name)
                modpath = fullname.split('.')
                filename = self.get_ext_filename(modpath[-1])
                if not self.inplace:
                    filename = os.path.join(*modpath[:-1]+[filename])
                    return os.path.join(self.build_lib, filename)
                package = '.'.join(modpath[0:-1])
                build_py = self.get_finalized_command('build_py')
                package_dir = os.path.abspath(build_py.get_package_dir(package))
                return os.path.join(package_dir, filename)

        return get_ext_fullpath(module)
822

823
    def compile(self, test_directory, module, workdir, incdir,
824
                expect_errors, annotate):
825 826
        expected_errors = errors = ()
        if expect_errors:
827
            expected_errors = self.split_source_and_output(
828 829
                test_directory, module, workdir)
            test_directory = workdir
830

831 832 833 834
        if WITH_CYTHON:
            old_stderr = sys.stderr
            try:
                sys.stderr = ErrorWriter()
835
                self.run_cython(test_directory, module, workdir, incdir, annotate)
836
                errors = sys.stderr.geterrors()
837 838
            finally:
                sys.stderr = old_stderr
839

840
        tostderr = sys.__stderr__.write
841
        if 'cerror' in self.tags['tag']:
842
            if errors:
843 844 845 846
                tostderr("\n=== Expected C compile error ===\n")
                tostderr("\n=== Got Cython errors: ===\n")
                tostderr('\n'.join(errors))
                tostderr('\n\n')
847 848
                raise RuntimeError('should have generated extension code')
        elif errors or expected_errors:
849 850 851 852 853 854 855 856 857 858
            try:
                for expected, error in zip(expected_errors, errors):
                    self.assertEquals(expected, error)
                if len(errors) < len(expected_errors):
                    expected_error = expected_errors[len(errors)]
                    self.assertEquals(expected_error, None)
                elif len(errors) > len(expected_errors):
                    unexpected_error = errors[len(expected_errors)]
                    self.assertEquals(None, unexpected_error)
            except AssertionError:
859 860 861 862 863
                tostderr("\n=== Expected errors: ===\n")
                tostderr('\n'.join(expected_errors))
                tostderr("\n\n=== Got errors: ===\n")
                tostderr('\n'.join(errors))
                tostderr('\n\n')
864
                raise
865 866
            return None

867 868
        so_path = None
        if not self.cython_only:
869
            from Cython.Utils import captured_fd, print_bytes
870 871
            show_output = True
            get_stderr = get_stdout = None
872
            try:
873 874 875
                with captured_fd(1) as get_stdout:
                    with captured_fd(2) as get_stderr:
                        so_path = self.run_distutils(test_directory, module, workdir, incdir)
876
            except Exception:
877
                if 'cerror' in self.tags['tag'] and get_stderr and get_stderr():
878
                    show_output = False  # expected C compiler failure
879 880 881
                else:
                    raise
            else:
882
                if 'cerror' in self.tags['tag']:
883
                    raise RuntimeError('should have failed C compile')
884 885 886 887
            finally:
                if show_output:
                    stdout = get_stdout and get_stdout().strip()
                    if stdout:
888
                        tostderr("\n=== C/C++ compiler output: ===\n")
889
                        print_bytes(stdout, end=None, file=sys.__stderr__)
890 891
                    stderr = get_stderr and get_stderr().strip()
                    if stderr:
892
                        tostderr("\n=== C/C++ compiler error output: ===\n")
893
                        print_bytes(stderr, end=None, file=sys.__stderr__)
894
                    if stdout or stderr:
895
                        tostderr("\n==============================\n")
896
        return so_path
897

898

899
class CythonRunTestCase(CythonCompileTestCase):
900 901 902 903 904
    def setUp(self):
        CythonCompileTestCase.setUp(self)
        from Cython.Compiler import Options
        Options.clear_to_none = False

905
    def shortDescription(self):
Stefan Behnel's avatar
Stefan Behnel committed
906 907 908 909
        if self.cython_only:
            return CythonCompileTestCase.shortDescription(self)
        else:
            return "compiling (%s) and running %s" % (self.language, self.module)
910 911

    def run(self, result=None):
912 913
        if result is None:
            result = self.defaultTestResult()
Stefan Behnel's avatar
Stefan Behnel committed
914
        result.startTest(self)
915
        try:
Stefan Behnel's avatar
Stefan Behnel committed
916
            self.setUp()
917
            try:
918
                self.success = False
919
                ext_so_path = self.runCompileTest()
920
                failures, errors = len(result.failures), len(result.errors)
921
                if not self.cython_only and ext_so_path is not None:
922
                    self.run_tests(result, ext_so_path)
923 924 925
                if failures == len(result.failures) and errors == len(result.errors):
                    # No new errors...
                    self.success = True
926 927
            finally:
                check_thread_termination()
928 929 930
        except Exception:
            result.addError(self, sys.exc_info())
            result.stopTest(self)
931 932 933 934
        try:
            self.tearDown()
        except Exception:
            pass
935

936
    def run_tests(self, result, ext_so_path):
Stefan Behnel's avatar
Stefan Behnel committed
937
        self.run_doctests(self.module, result, ext_so_path)
938

Stefan Behnel's avatar
Stefan Behnel committed
939
    def run_doctests(self, module_or_name, result, ext_so_path):
940
        def run_test(result):
Stefan Behnel's avatar
Stefan Behnel committed
941 942 943 944
            if isinstance(module_or_name, basestring):
                module = import_ext(module_or_name, ext_so_path)
            else:
                module = module_or_name
945
            tests = doctest.DocTestSuite(module)
946 947 948 949
            tests.run(result)
        run_forked_test(result, run_test, self.shortDescription(), self.fork)

def run_forked_test(result, run_func, test_name, fork=True):
Stefan Behnel's avatar
Stefan Behnel committed
950 951
    if not fork or sys.version_info[0] >= 3 or not hasattr(os, 'fork'):
        run_func(result)
952 953
        sys.stdout.flush()
        sys.stderr.flush()
954 955 956 957 958 959 960 961 962
        gc.collect()
        return

    # fork to make sure we do not keep the tested module loaded
    result_handle, result_file = tempfile.mkstemp()
    os.close(result_handle)
    child_id = os.fork()
    if not child_id:
        result_code = 0
963
        output = None
964
        try:
965
            try:
966
                tests = partial_result = None
967
                try:
968 969
                    partial_result = PartialTestResult(result)
                    run_func(partial_result)
970 971
                    sys.stdout.flush()
                    sys.stderr.flush()
972 973 974
                    gc.collect()
                except Exception:
                    result_code = 1
975 976 977 978 979 980 981 982
                    if partial_result is not None:
                        if tests is None:
                            # importing failed, try to fake a test class
                            tests = _FakeClass(
                                failureException=sys.exc_info()[1],
                                _shortDescription=test_name,
                                module_name=None)
                        partial_result.addError(tests, sys.exc_info())
983 984 985 986
                output = open(result_file, 'wb')
                pickle.dump(partial_result.data(), output)
            except:
                traceback.print_exc()
987
        finally:
988
            try: sys.stderr.flush()
989
            except: pass
990 991 992 993 994 995 996
            try: sys.stdout.flush()
            except: pass
            try:
                if output is not None:
                    output.close()
            except:
                pass
997 998 999 1000
            os._exit(result_code)

    try:
        cid, result_code = os.waitpid(child_id, 0)
1001
        module_name = test_name.split()[-1]
1002 1003 1004 1005 1006 1007
        # os.waitpid returns the child's result code in the
        # upper byte of result_code, and the signal it was
        # killed by in the lower byte
        if result_code & 255:
            raise Exception("Tests in module '%s' were unexpectedly killed by signal %d"%
                            (module_name, result_code & 255))
1008
        result_code >>= 8
1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
        if result_code in (0,1):
            input = open(result_file, 'rb')
            try:
                PartialTestResult.join_results(result, pickle.load(input))
            finally:
                input.close()
        if result_code:
            raise Exception("Tests in module '%s' exited with status %d" %
                            (module_name, result_code))
    finally:
        try: os.unlink(result_file)
        except: pass
1021

1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046
class PureDoctestTestCase(unittest.TestCase):
    def __init__(self, module_name, module_path):
        self.module_name = module_name
        self.module_path = module_path
        unittest.TestCase.__init__(self, 'run')

    def shortDescription(self):
        return "running pure doctests in %s" % self.module_name

    def run(self, result=None):
        if result is None:
            result = self.defaultTestResult()
        loaded_module_name = 'pure_doctest__' + self.module_name
        result.startTest(self)
        try:
            self.setUp()

            import imp
            m = imp.load_source(loaded_module_name, self.module_path)
            try:
                doctest.DocTestSuite(m).run(result)
            finally:
                del m
                if loaded_module_name in sys.modules:
                    del sys.modules[loaded_module_name]
1047
                check_thread_termination()
1048 1049 1050 1051 1052 1053 1054
        except Exception:
            result.addError(self, sys.exc_info())
            result.stopTest(self)
        try:
            self.tearDown()
        except Exception:
            pass
1055

1056 1057 1058 1059
is_private_field = re.compile('^_[^_]').match

class _FakeClass(object):
    def __init__(self, **kwargs):
1060
        self._shortDescription = kwargs.get('module_name')
1061
        self.__dict__.update(kwargs)
1062 1063
    def shortDescription(self):
        return self._shortDescription
1064

1065 1066 1067 1068 1069 1070
try: # Py2.7+ and Py3.2+
    from unittest.runner import _TextTestResult
except ImportError:
    from unittest import _TextTestResult

class PartialTestResult(_TextTestResult):
1071
    def __init__(self, base_result):
1072
        _TextTestResult.__init__(
1073 1074 1075
            self, self._StringIO(), True,
            base_result.dots + base_result.showAll*2)

1076 1077 1078 1079 1080 1081
    def strip_error_results(self, results):
        for test_case, error in results:
            for attr_name in filter(is_private_field, dir(test_case)):
                if attr_name == '_dt_test':
                    test_case._dt_test = _FakeClass(
                        name=test_case._dt_test.name)
Craig Citro's avatar
Craig Citro committed
1082
                elif attr_name != '_shortDescription':
1083 1084
                    setattr(test_case, attr_name, None)

1085
    def data(self):
1086 1087
        self.strip_error_results(self.failures)
        self.strip_error_results(self.errors)
1088 1089 1090 1091 1092 1093 1094
        return (self.failures, self.errors, self.testsRun,
                self.stream.getvalue())

    def join_results(result, data):
        """Static method for merging the result back into the main
        result object.
        """
Craig Citro's avatar
Craig Citro committed
1095
        failures, errors, tests_run, output = data
1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108
        if output:
            result.stream.write(output)
        result.errors.extend(errors)
        result.failures.extend(failures)
        result.testsRun += tests_run

    join_results = staticmethod(join_results)

    class _StringIO(StringIO):
        def writeln(self, line):
            self.write("%s\n" % line)


1109
class CythonUnitTestCase(CythonRunTestCase):
1110
    def shortDescription(self):
1111
        return "compiling (%s) tests in %s" % (self.language, self.module)
1112

1113 1114 1115
    def run_tests(self, result, ext_so_path):
        module = import_ext(self.module, ext_so_path)
        unittest.defaultTestLoader.loadTestsFromModule(module).run(result)
1116 1117 1118


class CythonPyregrTestCase(CythonRunTestCase):
1119 1120 1121 1122
    def setUp(self):
        CythonRunTestCase.setUp(self)
        from Cython.Compiler import Options
        Options.error_on_unknown_names = False
1123
        Options.error_on_uninitialized = False
1124 1125 1126
        Options.directive_defaults.update(dict(
            binding=True, always_allow_keywords=True,
            set_initial_path="SOURCEFILE"))
1127
        patch_inspect_isfunction()
1128

1129 1130 1131
    def related_files(self, test_directory, module_name):
        return _list_pyregr_data_files(test_directory)

1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148
    def _run_unittest(self, result, *classes):
        """Run tests from unittest.TestCase-derived classes."""
        valid_types = (unittest.TestSuite, unittest.TestCase)
        suite = unittest.TestSuite()
        for cls in classes:
            if isinstance(cls, str):
                if cls in sys.modules:
                    suite.addTest(unittest.findTestCases(sys.modules[cls]))
                else:
                    raise ValueError("str arguments must be keys in sys.modules")
            elif isinstance(cls, valid_types):
                suite.addTest(cls)
            else:
                suite.addTest(unittest.makeSuite(cls))
        suite.run(result)

    def _run_doctest(self, result, module):
Stefan Behnel's avatar
Stefan Behnel committed
1149
        self.run_doctests(module, result, None)
1150

1151
    def run_tests(self, result, ext_so_path):
1152
        try:
1153
            from test import support
Vitja Makarov's avatar
Vitja Makarov committed
1154 1155
        except ImportError: # Python2.x
            from test import test_support as support
1156

1157 1158 1159 1160 1161
        def run_test(result):
            def run_unittest(*classes):
                return self._run_unittest(result, *classes)
            def run_doctest(module, verbosity=None):
                return self._run_doctest(result, module)
1162

1163
            backup = (support.run_unittest, support.run_doctest)
1164 1165
            support.run_unittest = run_unittest
            support.run_doctest = run_doctest
1166

1167
            try:
1168 1169
                try:
                    sys.stdout.flush() # helps in case of crashes
1170
                    module = import_ext(self.module, ext_so_path)
1171 1172 1173 1174 1175 1176 1177 1178
                    sys.stdout.flush() # helps in case of crashes
                    if hasattr(module, 'test_main'):
                        module.test_main()
                        sys.stdout.flush() # helps in case of crashes
                except (unittest.SkipTest, support.ResourceDenied):
                    result.addSkip(self, 'ok')
            finally:
                support.run_unittest, support.run_doctest = backup
1179 1180

        run_forked_test(result, run_test, self.shortDescription(), self.fork)
1181

1182
include_debugger = IS_CPYTHON and sys.version_info[:2] > (2, 5)
1183

1184
def collect_unittests(path, module_prefix, suite, selectors, exclude_selectors):
1185 1186 1187 1188 1189 1190 1191
    def file_matches(filename):
        return filename.startswith("Test") and filename.endswith(".py")

    def package_matches(dirname):
        return dirname == "Tests"

    loader = unittest.TestLoader()
1192

1193 1194 1195
    if include_debugger:
        skipped_dirs = []
    else:
1196
        skipped_dirs = ['Cython' + os.path.sep + 'Debugger' + os.path.sep]
1197

1198
    for dirpath, dirnames, filenames in os.walk(path):
1199 1200 1201 1202 1203 1204 1205 1206 1207
        if dirpath != path and "__init__.py" not in filenames:
            skipped_dirs.append(dirpath + os.path.sep)
            continue
        skip = False
        for dir in skipped_dirs:
            if dirpath.startswith(dir):
                skip = True
        if skip:
            continue
1208 1209 1210 1211 1212
        parentname = os.path.split(dirpath)[-1]
        if package_matches(parentname):
            for f in filenames:
                if file_matches(f):
                    filepath = os.path.join(dirpath, f)[:-len(".py")]
1213
                    modulename = module_prefix + filepath[len(path)+1:].replace(os.path.sep, '.')
1214 1215
                    if not [ 1 for match in selectors if match(modulename) ]:
                        continue
1216 1217
                    if [ 1 for match in exclude_selectors if match(modulename) ]:
                        continue
1218 1219 1220
                    module = __import__(modulename)
                    for x in modulename.split('.')[1:]:
                        module = getattr(module, x)
Robert Bradshaw's avatar
Robert Bradshaw committed
1221
                    suite.addTests([loader.loadTestsFromModule(module)])
1222

1223 1224


1225
def collect_doctests(path, module_prefix, suite, selectors, exclude_selectors):
1226
    def package_matches(dirname):
1227 1228
        if dirname == 'Debugger' and not include_debugger:
            return False
1229 1230
        return dirname not in ("Mac", "Distutils", "Plex")
    def file_matches(filename):
Mark Florisson's avatar
Mark Florisson committed
1231
        filename, ext = os.path.splitext(filename)
1232
        blacklist = ['libcython', 'libpython', 'test_libcython_in_gdb',
1233
                     'TestLibCython']
Mark Florisson's avatar
Mark Florisson committed
1234 1235 1236 1237 1238
        return (ext == '.py' and not
                '~' in filename and not
                '#' in filename and not
                filename.startswith('.') and not
                filename in blacklist)
Stefan Behnel's avatar
Stefan Behnel committed
1239
    import doctest
1240
    for dirpath, dirnames, filenames in os.walk(path):
Robert Bradshaw's avatar
Robert Bradshaw committed
1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252
        for dir in list(dirnames):
            if not package_matches(dir):
                dirnames.remove(dir)
        for f in filenames:
            if file_matches(f):
                if not f.endswith('.py'): continue
                filepath = os.path.join(dirpath, f)
                if os.path.getsize(filepath) == 0: continue
                filepath = filepath[:-len(".py")]
                modulename = module_prefix + filepath[len(path)+1:].replace(os.path.sep, '.')
                if not [ 1 for match in selectors if match(modulename) ]:
                    continue
1253 1254
                if [ 1 for match in exclude_selectors if match(modulename) ]:
                    continue
Robert Bradshaw's avatar
Robert Bradshaw committed
1255 1256 1257
                if 'in_gdb' in modulename:
                    # These should only be imported from gdb.
                    continue
Robert Bradshaw's avatar
Robert Bradshaw committed
1258 1259 1260 1261 1262 1263 1264 1265
                module = __import__(modulename)
                for x in modulename.split('.')[1:]:
                    module = getattr(module, x)
                if hasattr(module, "__doc__") or hasattr(module, "__test__"):
                    try:
                        suite.addTest(doctest.DocTestSuite(module))
                    except ValueError: # no tests
                        pass
1266

1267 1268 1269 1270 1271 1272

class EndToEndTest(unittest.TestCase):
    """
    This is a test of build/*.srctree files, where srctree defines a full
    directory structure and its header gives a list of commands to run.
    """
Robert Bradshaw's avatar
Robert Bradshaw committed
1273
    cython_root = os.path.dirname(os.path.abspath(__file__))
1274

1275
    def __init__(self, treefile, workdir, cleanup_workdir=True):
1276
        self.name = os.path.splitext(os.path.basename(treefile))[0]
1277
        self.treefile = treefile
1278
        self.workdir = os.path.join(workdir, self.name)
1279
        self.cleanup_workdir = cleanup_workdir
1280 1281 1282
        cython_syspath = [self.cython_root]
        for path in sys.path:
            if path.startswith(self.cython_root) and path not in cython_syspath:
1283 1284
                # Py3 installation and refnanny build prepend their
                # fixed paths to sys.path => prefer that over the
1285 1286 1287
                # generic one (cython_root itself goes last)
                cython_syspath.append(path)
        self.cython_syspath = os.pathsep.join(cython_syspath[::-1])
1288 1289 1290
        unittest.TestCase.__init__(self)

    def shortDescription(self):
1291
        return "End-to-end %s" % self.name
1292 1293 1294

    def setUp(self):
        from Cython.TestUtils import unpack_source_tree
1295
        _, self.commands = unpack_source_tree(self.treefile, self.workdir)
1296 1297 1298 1299 1300 1301 1302
        self.old_dir = os.getcwd()
        os.chdir(self.workdir)
        if self.workdir not in sys.path:
            sys.path.insert(0, self.workdir)

    def tearDown(self):
        if self.cleanup_workdir:
1303 1304 1305 1306 1307 1308 1309
            for trial in range(5):
                try:
                    shutil.rmtree(self.workdir)
                except OSError:
                    time.sleep(0.1)
                else:
                    break
1310
        os.chdir(self.old_dir)
1311

1312 1313 1314 1315 1316 1317
    def _try_decode(self, content):
        try:
            return content.decode()
        except UnicodeDecodeError:
            return content.decode('iso-8859-1')

1318
    def runTest(self):
1319
        self.success = False
1320
        commands = (self.commands
Robert Bradshaw's avatar
Robert Bradshaw committed
1321
            .replace("CYTHON", "PYTHON %s" % os.path.join(self.cython_root, 'cython.py'))
1322
            .replace("PYTHON", sys.executable))
1323 1324
        old_path = os.environ.get('PYTHONPATH')
        os.environ['PYTHONPATH'] = self.cython_syspath + os.pathsep + (old_path or '')
1325
        try:
1326 1327
            for command in filter(None, commands.splitlines()):
                p = subprocess.Popen(command,
Robert Bradshaw's avatar
Robert Bradshaw committed
1328 1329 1330 1331 1332 1333 1334
                                     stderr=subprocess.PIPE,
                                     stdout=subprocess.PIPE,
                                     shell=True)
                out, err = p.communicate()
                res = p.returncode
                if res != 0:
                    print(command)
1335 1336
                    print(self._try_decode(out))
                    print(self._try_decode(err))
Robert Bradshaw's avatar
Robert Bradshaw committed
1337
                self.assertEqual(0, res, "non-zero exit status")
1338
        finally:
1339 1340 1341 1342
            if old_path:
                os.environ['PYTHONPATH'] = old_path
            else:
                del os.environ['PYTHONPATH']
1343
        self.success = True
1344 1345


1346 1347 1348 1349
# TODO: Support cython_freeze needed here as well.
# TODO: Windows support.

class EmbedTest(unittest.TestCase):
1350

1351
    working_dir = "Demos/embed"
1352

1353 1354 1355
    def setUp(self):
        self.old_dir = os.getcwd()
        os.chdir(self.working_dir)
1356
        os.system(
1357
            "make PYTHON='%s' clean > /dev/null" % sys.executable)
1358

1359 1360
    def tearDown(self):
        try:
1361 1362
            os.system(
                "make PYTHON='%s' clean > /dev/null" % sys.executable)
1363 1364 1365
        except:
            pass
        os.chdir(self.old_dir)
1366

1367
    def test_embed(self):
1368
        from distutils import sysconfig
1369
        libname = sysconfig.get_config_var('LIBRARY')
1370
        libdir = sysconfig.get_config_var('LIBDIR')
1371 1372 1373 1374 1375 1376 1377
        if not os.path.isdir(libdir) or libname not in os.listdir(libdir):
            libdir = os.path.join(os.path.dirname(sys.executable), '..', 'lib')
            if not os.path.isdir(libdir) or libname not in os.listdir(libdir):
                libdir = os.path.join(libdir, 'python%d.%d' % sys.version_info[:2], 'config')
                if not os.path.isdir(libdir) or libname not in os.listdir(libdir):
                    # report the error for the original directory
                    libdir = sysconfig.get_config_var('LIBDIR')
1378
        cython = 'cython.py'
Stefan Behnel's avatar
Stefan Behnel committed
1379
        if sys.version_info[0] >=3 and CY3_DIR:
1380 1381
            cython = os.path.join(CY3_DIR, cython)
        cython = os.path.abspath(os.path.join('..', '..', cython))
1382
        self.assert_(os.system(
1383
            "make PYTHON='%s' CYTHON='%s' LIBDIR1='%s' test > make.output" % (sys.executable, cython, libdir)) == 0)
1384 1385 1386 1387
        try:
            os.remove('make.output')
        except OSError:
            pass
1388

1389 1390
class MissingDependencyExcluder:
    def __init__(self, deps):
1391
        # deps: { matcher func : module name }
1392
        self.exclude_matchers = []
1393
        for matcher, mod in deps.items():
1394 1395 1396
            try:
                __import__(mod)
            except ImportError:
Robert Bradshaw's avatar
Robert Bradshaw committed
1397
                self.exclude_matchers.append(string_selector(matcher))
1398
        self.tests_missing_deps = []
Robert Bradshaw's avatar
Robert Bradshaw committed
1399
    def __call__(self, testname, tags=None):
1400
        for matcher in self.exclude_matchers:
Robert Bradshaw's avatar
Robert Bradshaw committed
1401
            if matcher(testname, tags):
1402 1403 1404 1405
                self.tests_missing_deps.append(testname)
                return True
        return False

1406 1407 1408 1409 1410
class VersionDependencyExcluder:
    def __init__(self, deps):
        # deps: { version : matcher func }
        from sys import version_info
        self.exclude_matchers = []
1411 1412
        for ver, (compare, matcher) in deps.items():
            if compare(version_info, ver):
1413 1414
                self.exclude_matchers.append(matcher)
        self.tests_missing_deps = []
Robert Bradshaw's avatar
Robert Bradshaw committed
1415
    def __call__(self, testname, tags=None):
1416 1417 1418 1419 1420 1421
        for matcher in self.exclude_matchers:
            if matcher(testname):
                self.tests_missing_deps.append(testname)
                return True
        return False

1422 1423 1424 1425
class FileListExcluder:

    def __init__(self, list_file):
        self.excludes = {}
1426 1427 1428 1429 1430 1431 1432 1433
        f = open(list_file)
        try:
            for line in f.readlines():
                line = line.strip()
                if line and line[0] != '#':
                    self.excludes[line.split()[0]] = True
        finally:
            f.close()
1434

Robert Bradshaw's avatar
Robert Bradshaw committed
1435
    def __call__(self, testname, tags=None):
1436
        return testname in self.excludes or testname.split('.')[-1] in self.excludes
1437

Robert Bradshaw's avatar
Robert Bradshaw committed
1438 1439 1440 1441 1442
class TagsSelector:

    def __init__(self, tag, value):
        self.tag = tag
        self.value = value
1443

Robert Bradshaw's avatar
Robert Bradshaw committed
1444 1445 1446 1447 1448 1449 1450
    def __call__(self, testname, tags=None):
        if tags is None:
            return False
        else:
            return self.value in tags[self.tag]

class RegExSelector:
1451

Robert Bradshaw's avatar
Robert Bradshaw committed
1452
    def __init__(self, pattern_string):
1453 1454 1455 1456 1457
        try:
            self.pattern = re.compile(pattern_string, re.I|re.U)
        except re.error:
            print('Invalid pattern: %r' % pattern_string)
            raise
Robert Bradshaw's avatar
Robert Bradshaw committed
1458 1459 1460 1461 1462 1463 1464 1465 1466 1467

    def __call__(self, testname, tags=None):
        return self.pattern.search(testname)

def string_selector(s):
    ix = s.find(':')
    if ix == -1:
        return RegExSelector(s)
    else:
        return TagsSelector(s[:ix], s[ix+1:])
1468 1469 1470

class ShardExcludeSelector:
    # This is an exclude selector so it can override the (include) selectors.
1471 1472
    # It may not provide uniform distribution (in time or count), but is a
    # determanistic partition of the tests which is important.
1473 1474 1475 1476 1477 1478
    def __init__(self, shard_num, shard_count):
        self.shard_num = shard_num
        self.shard_count = shard_count

    def __call__(self, testname, tags=None):
        return abs(hash(testname)) % self.shard_count != self.shard_num
1479

Robert Bradshaw's avatar
Robert Bradshaw committed
1480

1481 1482 1483 1484 1485 1486 1487 1488 1489 1490
def refactor_for_py3(distdir, cy3_dir):
    # need to convert Cython sources first
    import lib2to3.refactor
    from distutils.util import copydir_run_2to3
    fixers = [ fix for fix in lib2to3.refactor.get_fixers_from_package("lib2to3.fixes")
               if fix.split('fix_')[-1] not in ('next',)
               ]
    if not os.path.exists(cy3_dir):
        os.makedirs(cy3_dir)
    import distutils.log as dlog
1491
    dlog.set_threshold(dlog.INFO)
1492 1493 1494 1495 1496 1497
    copydir_run_2to3(distdir, cy3_dir, fixer_names=fixers,
                     template = '''
                     global-exclude *
                     graft Cython
                     recursive-exclude Cython *
                     recursive-include Cython *.py *.pyx *.pxd
1498
                     recursive-include Cython/Debugger/Tests *
1499
                     recursive-include Cython/Utility *
1500 1501
                     recursive-exclude pyximport test
                     include pyximport/*.py
1502
                     include runtests.py
1503
                     include cython.py
1504 1505 1506
                     ''')
    sys.path.insert(0, cy3_dir)

1507 1508 1509 1510
    for keep_2x_file in KEEP_2X_FILES:
        destfile = os.path.join(cy3_dir, keep_2x_file)
        shutil.copy(keep_2x_file, destfile)

1511 1512
class PendingThreadsError(RuntimeError):
    pass
1513

1514 1515 1516
threads_seen = []

def check_thread_termination(ignore_seen=True):
1517 1518 1519 1520 1521 1522 1523 1524 1525
    if threading is None: # no threading enabled in CPython
        return
    current = threading.currentThread()
    blocking_threads = []
    for t in threading.enumerate():
        if not t.isAlive() or t == current:
            continue
        t.join(timeout=2)
        if t.isAlive():
1526 1527 1528
            if not ignore_seen:
                blocking_threads.append(t)
                continue
1529 1530 1531 1532 1533 1534
            for seen in threads_seen:
                if t is seen:
                    break
            else:
                threads_seen.append(t)
                blocking_threads.append(t)
1535 1536 1537 1538 1539
    if not blocking_threads:
        return
    sys.stderr.write("warning: left-over threads found after running test:\n")
    for t in blocking_threads:
        sys.stderr.write('...%s\n'  % repr(t))
1540
    raise PendingThreadsError("left-over threads found after running test")
1541

1542 1543
def subprocess_output(cmd):
    try:
Mark Florisson's avatar
Mark Florisson committed
1544 1545
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        return p.communicate()[0].decode('UTF-8')
1546 1547 1548 1549 1550 1551 1552 1553
    except OSError:
        return ''

def get_version():
    from Cython.Compiler.Version import version as cython_version
    full_version = cython_version
    top = os.path.dirname(os.path.abspath(__file__))
    if os.path.exists(os.path.join(top, '.git')):
Stefan Behnel's avatar
Stefan Behnel committed
1554
        old_dir = os.getcwd()
1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567
        try:
            os.chdir(top)
            head_commit = subprocess_output(['git', 'rev-parse', 'HEAD']).strip()
            version_commit = subprocess_output(['git', 'rev-parse', cython_version]).strip()
            diff = subprocess_output(['git', 'diff', '--stat']).strip()
            if head_commit != version_commit:
                full_version += " " + head_commit
            if diff:
                full_version += ' + uncommitted changes'
        finally:
            os.chdir(old_dir)
    return full_version

1568 1569 1570 1571 1572 1573 1574 1575
_orig_stdout, _orig_stderr = sys.stdout, sys.stderr
def flush_and_terminate(status):
    try:
        _orig_stdout.flush()
        _orig_stderr.flush()
    finally:
        os._exit(status)

1576
def main():
1577

1578
    global DISTDIR, WITH_CYTHON
1579 1580
    DISTDIR = os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]))

1581 1582 1583
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option("--no-cleanup", dest="cleanup_workdir",
Stefan Behnel's avatar
Stefan Behnel committed
1584 1585
                      action="store_false", default=True,
                      help="do not delete the generated C files (allows passing --no-cython on next run)")
1586 1587 1588
    parser.add_option("--no-cleanup-sharedlibs", dest="cleanup_sharedlibs",
                      action="store_false", default=True,
                      help="do not delete the generated shared libary files (allows manual module experimentation)")
1589 1590 1591
    parser.add_option("--no-cleanup-failures", dest="cleanup_failures",
                      action="store_false", default=True,
                      help="enable --no-cleanup and --no-cleanup-sharedlibs for failed tests only")
Stefan Behnel's avatar
Stefan Behnel committed
1592 1593 1594
    parser.add_option("--no-cython", dest="with_cython",
                      action="store_false", default=True,
                      help="do not run the Cython compiler, only the C compiler")
1595 1596
    parser.add_option("--compiler", dest="compiler", default=None,
                      help="C compiler type")
1597 1598 1599
    backend_list = ','.join(BACKENDS)
    parser.add_option("--backends", dest="backends", default=backend_list,
                      help="select backends to test (default: %s)" % backend_list)
1600 1601
    parser.add_option("--no-c", dest="use_c",
                      action="store_false", default=True,
1602
                      help="do not test C compilation backend")
1603 1604
    parser.add_option("--no-cpp", dest="use_cpp",
                      action="store_false", default=True,
1605
                      help="do not test C++ compilation backend")
1606 1607 1608
    parser.add_option("--no-unit", dest="unittests",
                      action="store_false", default=True,
                      help="do not run the unit tests")
1609 1610 1611
    parser.add_option("--no-doctest", dest="doctests",
                      action="store_false", default=True,
                      help="do not run the doctests")
1612 1613 1614
    parser.add_option("--no-file", dest="filetests",
                      action="store_false", default=True,
                      help="do not run the file based tests")
1615 1616
    parser.add_option("--no-pyregr", dest="pyregr",
                      action="store_false", default=True,
1617
                      help="do not run the regression tests of CPython in tests/pyregr/")
1618
    parser.add_option("--cython-only", dest="cython_only",
1619 1620
                      action="store_true", default=False,
                      help="only compile pyx to c, do not run C compiler or run the tests")
1621
    parser.add_option("--no-refnanny", dest="with_refnanny",
Stefan Behnel's avatar
Stefan Behnel committed
1622
                      action="store_false", default=True,
1623
                      help="do not regression test reference counting")
1624 1625 1626
    parser.add_option("--no-fork", dest="fork",
                      action="store_false", default=True,
                      help="do not fork to run tests")
1627 1628 1629
    parser.add_option("--sys-pyregr", dest="system_pyregr",
                      action="store_true", default=False,
                      help="run the regression tests of the CPython installation")
1630 1631 1632
    parser.add_option("-x", "--exclude", dest="exclude",
                      action="append", metavar="PATTERN",
                      help="exclude tests matching the PATTERN")
1633 1634 1635 1636 1637 1638
    parser.add_option("--shard_count", dest="shard_count", metavar="N",
                      type=int, default=1,
                      help="shard this run into several parallel runs")
    parser.add_option("--shard_num", dest="shard_num", metavar="K",
                      type=int, default=-1,
                      help="test only this single shard")
1639
    parser.add_option("-C", "--coverage", dest="coverage",
Stefan Behnel's avatar
Stefan Behnel committed
1640 1641
                      action="store_true", default=False,
                      help="collect source coverage data for the Compiler")
1642 1643 1644
    parser.add_option("--coverage-xml", dest="coverage_xml",
                      action="store_true", default=False,
                      help="collect source coverage data for the Compiler in XML format")
1645 1646 1647
    parser.add_option("--coverage-html", dest="coverage_html",
                      action="store_true", default=False,
                      help="collect source coverage data for the Compiler in HTML format")
Stefan Behnel's avatar
Stefan Behnel committed
1648
    parser.add_option("-A", "--annotate", dest="annotate_source",
1649
                      action="store_true", default=True,
Stefan Behnel's avatar
Stefan Behnel committed
1650
                      help="generate annotated HTML versions of the test source files")
1651 1652 1653
    parser.add_option("--no-annotate", dest="annotate_source",
                      action="store_false",
                      help="do not generate annotated HTML versions of the test source files")
1654
    parser.add_option("-v", "--verbose", dest="verbosity",
Stefan Behnel's avatar
Stefan Behnel committed
1655 1656
                      action="count", default=0,
                      help="display test progress, pass twice to print test names")
1657 1658
    parser.add_option("-T", "--ticket", dest="tickets",
                      action="append",
1659
                      help="a bug ticket number to run the respective test in 'tests/*'")
1660 1661 1662
    parser.add_option("-3", dest="language_level",
                      action="store_const", const=3, default=2,
                      help="set language level to Python 3 (useful for running the CPython regression tests)'")
1663 1664
    parser.add_option("--xml-output", dest="xml_output_dir", metavar="DIR",
                      help="write test results in XML to directory DIR")
1665 1666 1667
    parser.add_option("--exit-ok", dest="exit_ok", default=False,
                      action="store_true",
                      help="exit without error code even on test failures")
1668 1669 1670 1671
    parser.add_option("--root-dir", dest="root_dir", default=os.path.join(DISTDIR, 'tests'),
                      help="working directory")
    parser.add_option("--work-dir", dest="work_dir", default=os.path.join(os.getcwd(), 'BUILD'),
                      help="working directory")
1672 1673
    parser.add_option("--cython-dir", dest="cython_dir", default=os.getcwd(),
                      help="Cython installation directory (default: use local source version)")
1674 1675
    parser.add_option("--debug", dest="for_debugging", default=False, action="store_true",
                      help="configure for easier use with a debugger (e.g. gdb)")
1676 1677
    parser.add_option("--pyximport-py", dest="pyximport_py", default=False, action="store_true",
                      help="use pyximport to automatically compile imported .pyx and .py files")
1678 1679
    parser.add_option("--watermark", dest="watermark", default=None,
                      help="deterministic generated by string")
1680 1681 1682

    options, cmd_args = parser.parse_args()

1683
    WORKDIR = os.path.abspath(options.work_dir)
1684

1685 1686
    if sys.version_info[0] >= 3:
        options.doctests = False
1687
        if options.with_cython:
1688
            sys.path.insert(0, options.cython_dir)
1689 1690 1691 1692
            try:
                # try if Cython is installed in a Py3 version
                import Cython.Compiler.Main
            except Exception:
Stefan Behnel's avatar
Stefan Behnel committed
1693 1694
                # back out anything the import process loaded, then
                # 2to3 the Cython sources to make them re-importable
1695
                cy_modules = [ name for name in sys.modules
Stefan Behnel's avatar
Stefan Behnel committed
1696
                               if name == 'Cython' or name.startswith('Cython.') ]
1697 1698 1699
                for name in cy_modules:
                    del sys.modules[name]
                # hasn't been refactored yet - do it now
1700 1701
                global CY3_DIR
                CY3_DIR = cy3_dir = os.path.join(WORKDIR, 'Cy3')
1702 1703 1704 1705 1706 1707
                if sys.version_info >= (3,1):
                    refactor_for_py3(DISTDIR, cy3_dir)
                elif os.path.isdir(cy3_dir):
                    sys.path.insert(0, cy3_dir)
                else:
                    options.with_cython = False
1708

1709 1710 1711 1712
    if options.watermark:
        import Cython.Compiler.Version
        Cython.Compiler.Version.watermark = options.watermark

1713
    WITH_CYTHON = options.with_cython
1714

1715
    coverage = None
1716
    if options.coverage or options.coverage_xml or options.coverage_html:
1717
        if options.shard_count <= 1 and options.shard_num < 0:
1718 1719 1720
            if not WITH_CYTHON:
                options.coverage = options.coverage_xml = options.coverage_html = False
            else:
1721
                print("Enabling coverage analysis")
1722
                from coverage import coverage as _coverage
1723
                coverage = _coverage(branch=True, omit=['Test*'])
1724 1725
                coverage.erase()
                coverage.start()
1726

1727
    if WITH_CYTHON:
1728
        global CompilationOptions, pyrex_default_options, cython_compile
1729 1730 1731 1732
        from Cython.Compiler.Main import \
            CompilationOptions, \
            default_options as pyrex_default_options, \
            compile as cython_compile
1733 1734
        from Cython.Compiler import Errors
        Errors.LEVEL = 0 # show all warnings
Stefan Behnel's avatar
Stefan Behnel committed
1735
        from Cython.Compiler import Options
1736
        Options.generate_cleanup_code = 3   # complete cleanup code
Stefan Behnel's avatar
Stefan Behnel committed
1737 1738
        from Cython.Compiler import DebugFlags
        DebugFlags.debug_temp_code_comments = 1
1739

1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752
    if options.shard_count > 1 and options.shard_num == -1:
        import multiprocessing
        pool = multiprocessing.Pool(options.shard_count)
        tasks = [(options, cmd_args, shard_num) for shard_num in range(options.shard_count)]
        errors = []
        for shard_num, return_code in pool.imap_unordered(runtests_callback, tasks):
            if return_code != 0:
                errors.append(shard_num)
                print("FAILED (%s/%s)" % (shard_num, options.shard_count))
            print("ALL DONE (%s/%s)" % (shard_num, options.shard_count))
        pool.close()
        pool.join()
        if errors:
Stefan Behnel's avatar
Stefan Behnel committed
1753
            print("Errors for shards %s" % ", ".join([str(e) for e in errors]))
1754 1755 1756 1757
            return_code = 1
        else:
            return_code = 0
    else:
1758
        _, return_code = runtests(options, cmd_args, coverage)
1759 1760 1761 1762 1763 1764 1765
    print("ALL DONE")

    try:
        check_thread_termination(ignore_seen=False)
    except PendingThreadsError:
        # normal program exit won't kill the threads, do it the hard way here
        flush_and_terminate(return_code)
Stefan Behnel's avatar
Stefan Behnel committed
1766 1767
    else:
        sys.exit(return_code)
1768 1769 1770 1771 1772 1773 1774


def runtests_callback(args):
    options, cmd_args, shard_num = args
    options.shard_num = shard_num
    return runtests(options, cmd_args)

1775
def runtests(options, cmd_args, coverage=None):
1776

1777 1778 1779 1780 1781 1782
    WITH_CYTHON = options.with_cython
    ROOTDIR = os.path.abspath(options.root_dir)
    WORKDIR = os.path.abspath(options.work_dir)

    if options.shard_num > -1:
        WORKDIR = os.path.join(WORKDIR, str(options.shard_num))
1783

1784
    # RUN ALL TESTS!
1785
    UNITTEST_MODULE = "Cython"
1786
    UNITTEST_ROOT = os.path.join(os.path.dirname(__file__), UNITTEST_MODULE)
1787 1788
    if WITH_CYTHON:
        if os.path.exists(WORKDIR):
1789
            for path in os.listdir(WORKDIR):
1790
                if path in ("support", "Cy3"): continue
1791
                shutil.rmtree(os.path.join(WORKDIR, path), ignore_errors=True)
1792 1793
    if not os.path.exists(WORKDIR):
        os.makedirs(WORKDIR)
1794

1795 1796 1797 1798 1799 1800 1801
    if options.shard_num <= 0:
        sys.stderr.write("Python %s\n" % sys.version)
        sys.stderr.write("\n")
        if WITH_CYTHON:
            sys.stderr.write("Running tests against Cython %s\n" % get_version())
        else:
            sys.stderr.write("Running tests without Cython.\n")
1802

1803 1804 1805 1806
    if options.for_debugging:
        options.cleanup_workdir = False
        options.cleanup_sharedlibs = False
        options.fork = False
1807
        if WITH_CYTHON and include_debugger:
1808 1809 1810
            from Cython.Compiler.Main import default_options as compiler_default_options
            compiler_default_options['gdb_debug'] = True
            compiler_default_options['output_dir'] = os.getcwd()
1811

1812 1813 1814 1815 1816
    if options.with_refnanny:
        from pyximport.pyxbuild import pyx_to_dll
        libpath = pyx_to_dll(os.path.join("Cython", "Runtime", "refnanny.pyx"),
                             build_in_temp=True,
                             pyxbuild_dir=os.path.join(WORKDIR, "support"))
1817
        sys.path.insert(0, os.path.split(libpath)[0])
1818
        CFLAGS.append("-DCYTHON_REFNANNY=1")
1819

Stefan Behnel's avatar
Stefan Behnel committed
1820 1821 1822 1823 1824
    if options.xml_output_dir and options.fork:
        # doesn't currently work together
        sys.stderr.write("Disabling forked testing to support XML test output\n")
        options.fork = False

1825 1826 1827
    if WITH_CYTHON and options.language_level == 3:
        sys.stderr.write("Using Cython language level 3.\n")

1828
    test_bugs = False
Stefan Behnel's avatar
Stefan Behnel committed
1829 1830 1831
    if options.tickets:
        for ticket_number in options.tickets:
            test_bugs = True
1832
            cmd_args.append('ticket:%s' % ticket_number)
1833 1834 1835 1836
    if not test_bugs:
        for selector in cmd_args:
            if selector.startswith('bugs'):
                test_bugs = True
1837

Robert Bradshaw's avatar
Robert Bradshaw committed
1838
    selectors = [ string_selector(r) for r in cmd_args ]
1839
    if not selectors:
Robert Bradshaw's avatar
Robert Bradshaw committed
1840
        selectors = [ lambda x, tags=None: True ]
1841

1842 1843 1844
    # Chech which external modules are not present and exclude tests
    # which depends on them (by prefix)

1845 1846
    missing_dep_excluder = MissingDependencyExcluder(EXT_DEP_MODULES)
    version_dep_excluder = VersionDependencyExcluder(VER_DEP_MODULES)
Robert Bradshaw's avatar
Robert Bradshaw committed
1847
    exclude_selectors = [missing_dep_excluder, version_dep_excluder] # want to print msg at exit
1848

1849
    if options.exclude:
Robert Bradshaw's avatar
Robert Bradshaw committed
1850
        exclude_selectors += [ string_selector(r) for r in options.exclude ]
1851

1852 1853 1854
    if options.shard_num > -1:
        exclude_selectors.append(ShardExcludeSelector(options.shard_num, options.shard_count))

1855
    if not test_bugs:
1856
        exclude_selectors += [ FileListExcluder(os.path.join(ROOTDIR, "bugs.txt")) ]
1857

1858 1859
    if sys.platform in ['win32', 'cygwin'] and sys.version_info < (2,6):
        exclude_selectors += [ lambda x: x == "run.specialfloat" ]
1860

1861 1862 1863
    global COMPILER
    if options.compiler:
        COMPILER = options.compiler
1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876

    selected_backends = [ name.strip() for name in options.backends.split(',') if name.strip() ]
    backends = []
    for backend in selected_backends:
        if backend == 'c' and not options.use_c:
            continue
        elif backend == 'cpp' and not options.use_cpp:
            continue
        elif backend not in BACKENDS:
            sys.stderr.write("Unknown backend requested: '%s' not one of [%s]\n" % (
                backend, ','.join(BACKENDS)))
            sys.exit(1)
        backends.append(backend)
1877 1878
    if options.shard_num <= 0:
        sys.stderr.write("Backends: %s\n" % ','.join(backends))
1879 1880 1881
    languages = backends

    sys.stderr.write("\n")
1882

1883 1884 1885
    test_suite = unittest.TestSuite()

    if options.unittests:
1886
        collect_unittests(UNITTEST_ROOT, UNITTEST_MODULE + ".", test_suite, selectors, exclude_selectors)
1887

1888
    if options.doctests:
1889
        collect_doctests(UNITTEST_ROOT, UNITTEST_MODULE + ".", test_suite, selectors, exclude_selectors)
1890

1891
    if options.filetests and languages:
1892
        filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors,
1893
                                options.annotate_source, options.cleanup_workdir,
1894 1895
                                options.cleanup_sharedlibs, options.cleanup_failures,
                                options.pyregr,
1896
                                options.cython_only, languages, test_bugs,
1897
                                options.fork, options.language_level)
1898 1899
        test_suite.addTest(filetests.build_suite())

1900
    if options.system_pyregr and languages:
1901 1902 1903 1904
        sys_pyregr_dir = os.path.join(sys.prefix, 'lib', 'python'+sys.version[:3], 'test')
        if os.path.isdir(sys_pyregr_dir):
            filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors,
                                    options.annotate_source, options.cleanup_workdir,
1905 1906
                                    options.cleanup_sharedlibs, options.cleanup_failures,
                                    True,
1907
                                    options.cython_only, languages, test_bugs,
1908
                                    options.fork, sys.version_info[0])
1909 1910
            sys.stderr.write("Including CPython regression tests in %s\n" % sys_pyregr_dir)
            test_suite.addTest(filetests.handle_directory(sys_pyregr_dir, 'pyregr'))
1911

Stefan Behnel's avatar
Stefan Behnel committed
1912
    if options.xml_output_dir:
1913
        from Cython.Tests.xmlrunner import XMLTestRunner
Stefan Behnel's avatar
Stefan Behnel committed
1914 1915
        test_runner = XMLTestRunner(output=options.xml_output_dir,
                                    verbose=options.verbosity > 0)
1916 1917 1918
    else:
        test_runner = unittest.TextTestRunner(verbosity=options.verbosity)

1919
    if options.pyximport_py:
1920
        from pyximport import pyximport
1921
        pyximport.install(pyimport=True, build_dir=os.path.join(WORKDIR, '_pyximport'),
1922
                          load_py_module_on_import_failure=True, inplace=True)
1923

1924
    result = test_runner.run(test_suite)
1925

1926
    if coverage is not None:
1927
        coverage.stop()
1928
        ignored_modules = ('Options', 'Version', 'DebugFlags', 'CmdLine')
1929 1930
        modules = [ module for name, module in sys.modules.items()
                    if module is not None and
1931
                    name.startswith('Cython.Compiler.') and
1932
                    name[len('Cython.Compiler.'):] not in ignored_modules ]
1933 1934 1935
        if options.coverage:
            coverage.report(modules, show_missing=0)
        if options.coverage_xml:
1936
            coverage.xml_report(modules, outfile="coverage-report.xml")
1937 1938
        if options.coverage_html:
            coverage.html_report(modules, directory="coverage-report-html")
1939 1940 1941 1942 1943

    if missing_dep_excluder.tests_missing_deps:
        sys.stderr.write("Following tests excluded because of missing dependencies on your system:\n")
        for test in missing_dep_excluder.tests_missing_deps:
            sys.stderr.write("   %s\n" % test)
1944

1945
    if options.with_refnanny:
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
1946
        import refnanny
1947
        sys.stderr.write("\n".join([repr(x) for x in refnanny.reflog]))
1948

1949
    if options.exit_ok:
1950
        return options.shard_num, 0
1951
    else:
1952
        return options.shard_num, not result.wasSuccessful()
1953 1954 1955 1956 1957


if __name__ == '__main__':
    try:
        main()
Stefan Behnel's avatar
Stefan Behnel committed
1958 1959
    except SystemExit: # <= Py2.4 ...
        raise
1960 1961 1962 1963 1964 1965
    except Exception:
        traceback.print_exc()
        try:
            check_thread_termination(ignore_seen=False)
        except PendingThreadsError:
            # normal program exit won't kill the threads, do it the hard way here
1966
            flush_and_terminate(1)