runtests.py 80.1 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 30
from io import open as io_open
try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO  # doesn't accept 'str' in Py2
31 32 33 34 35 36

try:
    import cPickle as pickle
except ImportError:
    import pickle

37 38 39 40 41
try:
    import threading
except ImportError: # No threads, no problems
    threading = None

Robert Bradshaw's avatar
Robert Bradshaw committed
42 43 44 45 46 47 48 49 50 51 52 53 54
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
55 56
        def __contains__(self, key):
            return key in self._dict
Robert Bradshaw's avatar
Robert Bradshaw committed
57 58
        def __repr__(self):
            return repr(self._dict)
Stefan Behnel's avatar
Stefan Behnel committed
59 60
        def __nonzero__(self):
            return bool(self._dict)
Robert Bradshaw's avatar
Robert Bradshaw committed
61

Stefan Behnel's avatar
Stefan Behnel committed
62 63 64 65 66
try:
    basestring
except NameError:
    basestring = str

67
WITH_CYTHON = True
68
CY3_DIR = None
69 70

from distutils.command.build_ext import build_ext as _build_ext
71
from distutils import sysconfig
72

73

74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
def get_distutils_distro(_cache=[]):
    if _cache:
        return _cache[0]
    # late import to accomodate for setuptools override
    from distutils.dist import Distribution
    distutils_distro = Distribution()

    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)
    _cache.append(distutils_distro)
    return distutils_distro
Robert Bradshaw's avatar
Robert Bradshaw committed
94 95


96
EXT_DEP_MODULES = {
97 98 99 100 101
    'tag:numpy':    'numpy',
    'tag:asyncio':  'asyncio',
    'tag:pstats':   'pstats',
    'tag:posix':    'posix',
    'tag:array':    'array',
102
    'tag:coverage': 'Cython.Coverage',
103 104 105
    'Coverage':     'Cython.Coverage',
    'tag:ipython':  'IPython',
    'tag:jedi':     'jedi',
106 107
}

108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
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

125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
def def_to_cdef(source):
    '''
    Converts the module-level def methods into cdef methods, i.e.

        @decorator
        def foo([args]):
            """
            [tests]
            """
            [body]

    becomes

        def foo([args]):
            """
            [tests]
            """
            return foo_c([args])

        cdef foo_c([args]):
            [body]
    '''
    output = []
    skip = False
    def_node = re.compile(r'def (\w+)\(([^()*]*)\):').match
    lines = iter(source.split('\n'))
    for line in lines:
        if not line.strip():
            output.append(line)
            continue

        if skip:
            if line[0] != ' ':
                skip = False
            else:
                continue

        if line[0] == '@':
            skip = True
            continue

        m = def_node(line)
        if m:
            name = m.group(1)
            args = m.group(2)
            if args:
                args_no_types = ", ".join(arg.split()[-1] for arg in args.split(','))
            else:
                args_no_types = ""
            output.append("def %s(%s):" % (name, args_no_types))
175
            line = next(lines)
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
            if '"""' in line:
                has_docstring = True
                output.append(line)
                for line in lines:
                    output.append(line)
                    if '"""' in line:
                        break
            else:
                has_docstring = False
            output.append("    return %s_c(%s)" % (name, args_no_types))
            output.append('')
            output.append("cdef %s_c(%s):" % (name, args))
            if not has_docstring:
                output.append(line)

        else:
            output.append(line)

    return '\n'.join(output)

196 197 198 199
def update_linetrace_extension(ext):
    ext.define_macros.append(('CYTHON_TRACE', 1))
    return ext

200
def update_numpy_extension(ext):
201
    import numpy
202 203
    from numpy.distutils.misc_util import get_info

204
    ext.include_dirs.append(numpy.get_include())
205

206 207 208 209 210
    # 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)

211
def update_openmp_extension(ext):
212
    ext.openmp = True
213 214 215 216 217 218 219 220 221 222 223 224 225
    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
226 227
    elif sys.platform == 'win32':
        return ext
228 229 230 231 232 233 234 235 236 237 238 239 240 241

    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')
242 243

    if not cc:
244 245 246
        if sys.platform == 'win32':
            return '/openmp', ''
        return None
247

248 249 250
    # For some reason, cc can be e.g. 'gcc -pthread'
    cc = cc.split()[0]

251 252 253 254
    # Force english output
    env = os.environ.copy()
    env['LC_MESSAGES'] = 'C'

255 256
    matcher = re.compile(r"gcc version (\d+\.\d+)").search
    try:
257
        p = subprocess.Popen([cc, "-v"], stderr=subprocess.PIPE, env=env)
Robert Bradshaw's avatar
Robert Bradshaw committed
258 259 260 261 262 263
    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()
264

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

267 268 269 270
    gcc_version = matcher(output)
    if not gcc_version:
        return None # not gcc - FIXME: do something about other compilers

271 272 273 274
    # gcc defines "__int128_t", assume that at least all 64 bit architectures have it
    global COMPILER_HAS_INT128
    COMPILER_HAS_INT128 = getattr(sys, 'maxsize', getattr(sys, 'maxint', 0)) > 2**60

275
    compiler_version = gcc_version.group(1)
276 277 278
    if compiler_version and compiler_version.split('.') >= ['4', '2']:
        return '-fopenmp', '-fopenmp'

279 280 281 282
try:
    locale.setlocale(locale.LC_ALL, '')
except locale.Error:
    pass
283

284 285
COMPILER = None
COMPILER_HAS_INT128 = False
286 287 288 289 290 291
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()

292 293
EXT_EXTRAS = {
    'tag:numpy' : update_numpy_extension,
294
    'tag:openmp': update_openmp_extension,
295
    'tag:trace' : update_linetrace_extension,
296
}
297

298 299 300 301 302

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


Robert Bradshaw's avatar
Robert Bradshaw committed
303
# TODO: use tags
304
VER_DEP_MODULES = {
305
    # tests are excluded if 'CurrentPythonVersion OP VersionTuple', i.e.
Stefan Behnel's avatar
Stefan Behnel committed
306
    # (2,4) : (operator.lt, ...) excludes ... when PyVer < 2.4.x
307
    (2,7) : (operator.lt, lambda x: x in ['run.withstat_py27', # multi context with statement
Stefan Behnel's avatar
Stefan Behnel committed
308
                                          'run.yield_inside_lambda',
309
                                          'run.test_dictviews',
310
                                          'run.pyclass_special_methods',
311
                                          'run.set_literals',
Stefan Behnel's avatar
Stefan Behnel committed
312
                                          ]),
313 314 315 316
    # 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...
317 318 319
    (2,999): (operator.lt, lambda x: x in ['run.special_methods_T561_py3',
                                           'run.test_raisefrom',
                                           ]),
320
    (3,): (operator.ge, lambda x: x in ['run.non_future_division',
Stefan Behnel's avatar
Stefan Behnel committed
321
                                        'compile.extsetslice',
322
                                        'compile.extdelslice',
323 324 325 326
                                        'run.special_methods_T561_py2'
                                        ]),
    (3,1): (_is_py3_before_32, lambda x: x in ['run.pyclass_special_methods',
                                               ]),
327
    (3,3) : (operator.lt, lambda x: x in ['build.package_compilation',
328
                                          'run.yield_from_py33',
329
                                          ]),
330 331
    (3,4): (operator.lt, lambda x: x in ['run.py34_signature',
                                         ]),
332 333
    (3,5): (operator.lt, lambda x: x in ['run.py35_pep492_interop',
                                         ]),
334 335
}

336
INCLUDE_DIRS = [ d for d in os.getenv('INCLUDE', '').split(os.pathsep) if d ]
337
CFLAGS = os.getenv('CFLAGS', '').split()
338
CCACHE = os.getenv('CYTHON_RUNTESTS_CCACHE', '').split()
339
TEST_SUPPORT_DIR = 'testsupport'
340

341
BACKENDS = ['c', 'cpp']
342

343 344 345
UTF8_BOM_BYTES = r'\xef\xbb\xbf'.encode('ISO-8859-1').decode('unicode_escape')


346 347 348 349 350 351 352 353 354 355
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

356

Robert Bradshaw's avatar
Robert Bradshaw committed
357
@memoize
Robert Bradshaw's avatar
Robert Bradshaw committed
358 359
def parse_tags(filepath):
    tags = defaultdict(list)
360
    parse_tag = re.compile(r'#\s*(\w+)\s*:(.*)$').match
361
    f = io_open(filepath, encoding='ISO-8859-1', errors='ignore')
362 363
    try:
        for line in f:
364 365
            # ignore BOM-like bytes and whitespace
            line = line.lstrip(UTF8_BOM_BYTES).strip()
366
            if not line:
367 368 369 370
                if tags:
                    break  # assume all tags are in one block
                else:
                    continue
371 372
            if line[0] != '#':
                break
373 374 375 376 377 378 379 380
            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)
381
                if tag not in ('mode', 'tag', 'ticket', 'cython', 'distutils', 'preparse'):
382 383 384 385 386
                    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
387 388
    finally:
        f.close()
Robert Bradshaw's avatar
Robert Bradshaw committed
389 390
    return tags

391

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

Stefan Behnel's avatar
Stefan Behnel committed
394

395 396
@memoize
def _list_pyregr_data_files(test_directory):
397 398 399 400
    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)]
401 402


403 404 405 406 407 408 409 410 411 412 413 414 415 416
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(), ['*'])


417 418
class build_ext(_build_ext):
    def build_extension(self, ext):
419 420 421 422 423 424
        try:
            try: # Py2.7+ & Py3.2+
                compiler_obj = self.compiler_obj
            except AttributeError:
                compiler_obj = self.compiler
            if ext.language == 'c++':
425
                compiler_obj.compiler_so.remove('-Wstrict-prototypes')
426 427
            if CCACHE:
                compiler_obj.compiler_so = CCACHE + compiler_obj.compiler_so
428 429
            if getattr(ext, 'openmp', None) and compiler_obj.compiler_type == 'msvc':
                ext.extra_compile_args.append('/openmp')
430 431
        except Exception:
            pass
432
        _build_ext.build_extension(self, ext)
433 434

class ErrorWriter(object):
435
    match_error = re.compile('(warning:)?(?:.*:)?\s*([-0-9]+)\s*:\s*([-0-9]+)\s*:\s*(.*)').match
436 437 438 439
    def __init__(self):
        self.output = []
        self.write = self.output.append

440
    def _collect(self, collect_errors, collect_warnings):
441
        s = ''.join(self.output)
442
        result = []
443 444 445
        for line in s.split('\n'):
            match = self.match_error(line)
            if match:
446 447 448
                is_warning, line, column, message = match.groups()
                if (is_warning and collect_warnings) or \
                        (not is_warning and collect_errors):
449 450
                    result.append( (int(line), int(column), message.strip()) )
        result.sort()
451
        return [ "%d:%d: %s" % values for values in result ]
452 453 454 455 456 457 458 459 460

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

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

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

462
class TestBuilder(object):
463
    def __init__(self, rootdir, workdir, selectors, exclude_selectors, annotate,
464
                 cleanup_workdir, cleanup_sharedlibs, cleanup_failures,
465 466
                 with_pyregr, cython_only, languages, test_bugs, fork, language_level,
                 common_utility_dir):
467 468
        self.rootdir = rootdir
        self.workdir = workdir
469
        self.selectors = selectors
470
        self.exclude_selectors = exclude_selectors
471
        self.annotate = annotate
472
        self.cleanup_workdir = cleanup_workdir
473
        self.cleanup_sharedlibs = cleanup_sharedlibs
474
        self.cleanup_failures = cleanup_failures
475
        self.with_pyregr = with_pyregr
476 477
        self.cython_only = cython_only
        self.languages = languages
478
        self.test_bugs = test_bugs
479
        self.fork = fork
480
        self.language_level = language_level
481
        self.common_utility_dir = common_utility_dir
482 483 484

    def build_suite(self):
        suite = unittest.TestSuite()
485 486 487
        filenames = os.listdir(self.rootdir)
        filenames.sort()
        for filename in filenames:
488
            path = os.path.join(self.rootdir, filename)
489
            if os.path.isdir(path) and filename != TEST_SUPPORT_DIR:
490 491
                if filename == 'pyregr' and not self.with_pyregr:
                    continue
492 493
                if filename == 'broken' and not self.test_bugs:
                    continue
494
                suite.addTest(
495
                    self.handle_directory(path, filename))
496 497
        if sys.platform not in ['win32']:
            # Non-Windows makefile.
498 499
            if [1 for selector in self.selectors if selector("embedded")] \
                and not [1 for selector in self.exclude_selectors if selector("embedded")]:
500
                suite.addTest(unittest.makeSuite(EmbedTest))
501 502
        return suite

503
    def handle_directory(self, path, context):
504 505 506 507
        workdir = os.path.join(self.workdir, context)
        if not os.path.exists(workdir):
            os.makedirs(workdir)

508
        suite = unittest.TestSuite()
Robert Bradshaw's avatar
Robert Bradshaw committed
509
        filenames = list_unchanging_dir(path)
510 511
        filenames.sort()
        for filename in filenames:
512 513 514
            filepath = os.path.join(path, filename)
            module, ext = os.path.splitext(filename)
            if ext not in ('.py', '.pyx', '.srctree'):
515
                continue
516 517
            if filename.startswith('.'):
                continue # certain emacs backup files
518 519 520 521
            if context == 'pyregr':
                tags = defaultdict(list)
            else:
                tags = parse_tags(filepath)
Robert Bradshaw's avatar
Robert Bradshaw committed
522
            fqmodule = "%s.%s" % (context, module)
523
            if not [ 1 for match in self.selectors
Robert Bradshaw's avatar
Robert Bradshaw committed
524
                     if match(fqmodule, tags) ]:
525
                continue
526
            if self.exclude_selectors:
527
                if [1 for match in self.exclude_selectors
Robert Bradshaw's avatar
Robert Bradshaw committed
528
                        if match(fqmodule, tags)]:
529
                    continue
530 531 532 533 534 535 536 537

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

            if ext == '.srctree':
538 539
                if 'cpp' not in tags['tag'] or 'cpp' in self.languages:
                    suite.addTest(EndToEndTest(filepath, workdir, self.cleanup_workdir))
540 541 542 543 544 545
                continue

            # Choose the test suite.
            if mode == 'pyregr':
                if not filename.startswith('test_'):
                    continue
546
                test_class = CythonPyregrTestCase
547
            elif mode == 'run':
548
                if module.startswith("test_"):
549
                    test_class = CythonUnitTestCase
550
                else:
551
                    test_class = CythonRunTestCase
552
            else:
553
                test_class = CythonCompileTestCase
554

555
            for test in self.build_tests(test_class, path, workdir,
Robert Bradshaw's avatar
Robert Bradshaw committed
556
                                         module, mode == 'error', tags):
557
                suite.addTest(test)
558
            if mode == 'run' and ext == '.py' and not self.cython_only and not filename.startswith('test_'):
559 560
                # additionally test file in real Python
                suite.addTest(PureDoctestTestCase(module, os.path.join(path, filename)))
561

562 563
        return suite

Robert Bradshaw's avatar
Robert Bradshaw committed
564
    def build_tests(self, test_class, path, workdir, module, expect_errors, tags):
Robert Bradshaw's avatar
Robert Bradshaw committed
565
        if 'werror' in tags['tag']:
Vitja Makarov's avatar
Vitja Makarov committed
566 567 568 569
            warning_errors = True
        else:
            warning_errors = False

570
        if expect_errors:
Robert Bradshaw's avatar
Robert Bradshaw committed
571
            if 'cpp' in tags['tag'] and 'cpp' in self.languages:
Robert Bradshaw's avatar
Robert Bradshaw committed
572 573 574
                languages = ['cpp']
            else:
                languages = self.languages[:1]
575 576
        else:
            languages = self.languages
Robert Bradshaw's avatar
Robert Bradshaw committed
577

Robert Bradshaw's avatar
Robert Bradshaw committed
578
        if 'cpp' in tags['tag'] and 'c' in languages:
579 580
            languages = list(languages)
            languages.remove('c')
Robert Bradshaw's avatar
Robert Bradshaw committed
581 582 583
        elif 'no-cpp' in tags['tag'] and 'cpp' in self.languages:
            languages = list(languages)
            languages.remove('cpp')
584 585

        preparse_list = tags.get('preparse', ['id'])
586
        tests = [ self.build_test(test_class, path, workdir, module, tags,
587 588 589
                                  language, expect_errors, warning_errors, preparse)
                  for language in languages
                  for preparse in preparse_list ]
590 591
        return tests

592
    def build_test(self, test_class, path, workdir, module, tags,
593
                   language, expect_errors, warning_errors, preparse):
594 595 596 597
        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)
598 599
        if preparse != 'id':
            workdir += '_%s' % str(preparse)
600
        return test_class(path, workdir, module, tags,
601
                          language=language,
602
                          preparse=preparse,
603 604 605 606
                          expect_errors=expect_errors,
                          annotate=self.annotate,
                          cleanup_workdir=self.cleanup_workdir,
                          cleanup_sharedlibs=self.cleanup_sharedlibs,
607
                          cleanup_failures=self.cleanup_failures,
608
                          cython_only=self.cython_only,
609
                          fork=self.fork,
Vitja Makarov's avatar
Vitja Makarov committed
610
                          language_level=self.language_level,
611 612
                          warning_errors=warning_errors,
                          common_utility_dir=self.common_utility_dir)
613

614
class CythonCompileTestCase(unittest.TestCase):
615
    def __init__(self, test_directory, workdir, module, tags, language='c', preparse='id',
616
                 expect_errors=False, annotate=False, cleanup_workdir=True,
617
                 cleanup_sharedlibs=True, cleanup_failures=True, cython_only=False,
618 619
                 fork=True, language_level=2, warning_errors=False,
                 common_utility_dir=None):
620
        self.test_directory = test_directory
621
        self.tags = tags
622 623
        self.workdir = workdir
        self.module = module
624
        self.language = language
625 626
        self.preparse = preparse
        self.name = module if self.preparse == "id" else "%s_%s" % (module, preparse)
627
        self.expect_errors = expect_errors
628
        self.annotate = annotate
629
        self.cleanup_workdir = cleanup_workdir
630
        self.cleanup_sharedlibs = cleanup_sharedlibs
631
        self.cleanup_failures = cleanup_failures
632
        self.cython_only = cython_only
633
        self.fork = fork
634
        self.language_level = language_level
Vitja Makarov's avatar
Vitja Makarov committed
635
        self.warning_errors = warning_errors
636
        self.common_utility_dir = common_utility_dir
637 638 639
        unittest.TestCase.__init__(self)

    def shortDescription(self):
640
        return "compiling (%s) %s" % (self.language, self.name)
641

Stefan Behnel's avatar
Stefan Behnel committed
642
    def setUp(self):
Vitja Makarov's avatar
Vitja Makarov committed
643
        from Cython.Compiler import Options
644 645 646 647
        self._saved_options = [
            (name, getattr(Options, name))
            for name in ('warning_errors', 'clear_to_none', 'error_on_unknown_names', 'error_on_uninitialized')
        ]
648
        self._saved_default_directives = list(Options.get_directive_defaults().items())
Vitja Makarov's avatar
Vitja Makarov committed
649
        Options.warning_errors = self.warning_errors
650
        if sys.version_info >= (3, 4):
651
            Options._directive_defaults['autotestdict'] = False
Vitja Makarov's avatar
Vitja Makarov committed
652

653 654
        if not os.path.exists(self.workdir):
            os.makedirs(self.workdir)
Stefan Behnel's avatar
Stefan Behnel committed
655 656 657
        if self.workdir not in sys.path:
            sys.path.insert(0, self.workdir)

658
    def tearDown(self):
Vitja Makarov's avatar
Vitja Makarov committed
659
        from Cython.Compiler import Options
660 661
        for name, value in self._saved_options:
            setattr(Options, name, value)
662
        Options._directive_defaults = dict(self._saved_default_directives)
663
        unpatch_inspect_isfunction()
Vitja Makarov's avatar
Vitja Makarov committed
664

Stefan Behnel's avatar
Stefan Behnel committed
665 666 667 668 669 670 671 672
        try:
            sys.path.remove(self.workdir)
        except ValueError:
            pass
        try:
            del sys.modules[self.module]
        except KeyError:
            pass
673 674 675
        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
676
        if os.path.exists(self.workdir):
677 678 679 680 681 682
            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
683 684
                                rmfile[-4:] == ".cpp" or
                                rmfile.endswith(".html") and rmfile.startswith(self.module)):
685 686
                            continue
                    if not cleanup_lib_files and (rmfile.endswith(".so") or rmfile.endswith(".dll")):
687
                        continue
688 689 690 691 692 693 694 695
                    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
696

697
    def runTest(self):
698
        self.success = False
699
        self.runCompileTest()
700
        self.success = True
701 702

    def runCompileTest(self):
703 704 705
        return self.compile(
            self.test_directory, self.module, self.workdir,
            self.test_directory, self.expect_errors, self.annotate)
706

707 708 709 710 711
    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
712 713 714
    def build_target_filename(self, module_name):
        target = '%s.%s' % (module_name, self.language)
        return target
715

Robert Bradshaw's avatar
Robert Bradshaw committed
716
    def related_files(self, test_directory, module_name):
717
        is_related = re.compile('%s_.*[.].*' % module_name).match
Robert Bradshaw's avatar
Robert Bradshaw committed
718
        return [filename for filename in list_unchanging_dir(test_directory)
719
                if is_related(filename)]
Robert Bradshaw's avatar
Robert Bradshaw committed
720 721

    def copy_files(self, test_directory, target_directory, file_list):
722 723 724
        if self.preparse and self.preparse != 'id':
            preparse_func = globals()[self.preparse]
            def copy(src, dest):
725
                open(dest, 'w').write(preparse_func(open(src).read()))
726 727 728 729 730 731
        else:
            # use symlink on Unix, copy on Windows
            try:
                copy = os.symlink
            except AttributeError:
                copy = shutil.copy
732 733

        join = os.path.join
Robert Bradshaw's avatar
Robert Bradshaw committed
734
        for filename in file_list:
735
            file_path = join(test_directory, filename)
736
            if os.path.exists(file_path):
737
                copy(file_path, join(target_directory, filename))
738

Robert Bradshaw's avatar
Robert Bradshaw committed
739 740 741
    def source_files(self, workdir, module_name, file_list):
        return ([self.build_target_filename(module_name)] +
            [filename for filename in file_list
742
             if not os.path.isfile(os.path.join(workdir, filename))])
743 744

    def split_source_and_output(self, test_directory, module, workdir):
745
        source_file = self.find_module_source_file(os.path.join(test_directory, module) + '.pyx')
746
        source_and_output = io_open(source_file, 'rU', encoding='ISO-8859-1')
747
        try:
748 749
            out = io_open(os.path.join(workdir, module + os.path.splitext(source_file)[1]),
                              'w', encoding='ISO-8859-1')
750 751 752 753 754 755 756 757
            for line in source_and_output:
                if line.startswith("_ERRORS"):
                    out.close()
                    out = ErrorWriter()
                else:
                    out.write(line)
        finally:
            source_and_output.close()
758

759 760 761
        try:
            geterrors = out.geterrors
        except AttributeError:
762
            out.close()
763
            return []
764 765 766
        else:
            return geterrors()

767 768
    def run_cython(self, test_directory, module, targetdir, incdir, annotate,
                   extra_compile_options=None):
769
        include_dirs = INCLUDE_DIRS + [os.path.join(test_directory, '..', TEST_SUPPORT_DIR)]
770 771
        if incdir:
            include_dirs.append(incdir)
772
        source = self.find_module_source_file(
773
            os.path.join(test_directory, module + '.pyx'))
774 775 776 777 778 779
        if self.preparse == 'id':
            source = self.find_module_source_file(
                os.path.join(test_directory, module + '.pyx'))
        else:
            self.copy_files(test_directory, targetdir, [module + '.pyx'])
            source = os.path.join(targetdir, module + '.pyx')
Stefan Behnel's avatar
Stefan Behnel committed
780
        target = os.path.join(targetdir, self.build_target_filename(module))
781

782 783
        if extra_compile_options is None:
            extra_compile_options = {}
784

785 786 787 788
        if 'allow_unknown_names' in self.tags['tag']:
            from Cython.Compiler import Options
            Options.error_on_unknown_names = False

789 790 791 792 793 794
        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
795
        common_utility_include_dir = self.common_utility_dir
796

797
        options = CompilationOptions(
798
            default_options,
799 800
            include_path = include_dirs,
            output_file = target,
801
            annotate = annotate,
802 803
            use_listing_file = False,
            cplus = self.language == 'cpp',
804
            language_level = self.language_level,
805
            generate_pxi = False,
806
            evaluate_tree_assertions = True,
807
            common_utility_include_dir = common_utility_include_dir,
808
            **extra_compile_options
809
            )
810 811 812
        cython_compile(source, options=options,
                       full_module_name=module)

813
    def run_distutils(self, test_directory, module, workdir, incdir,
814
                      extra_extension_args=None):
815 816 817
        cwd = os.getcwd()
        os.chdir(workdir)
        try:
818
            build_extension = build_ext(get_distutils_distro())
819 820 821 822
            build_extension.include_dirs = INCLUDE_DIRS[:]
            if incdir:
                build_extension.include_dirs.append(incdir)
            build_extension.finalize_options()
823 824
            if COMPILER:
                build_extension.compiler = COMPILER
825

826
            ext_compile_flags = CFLAGS[:]
827 828 829 830
            compiler = COMPILER or sysconfig.get_config_var('CC')

            if self.language == 'c' and compiler == 'gcc':
                ext_compile_flags.extend(['-std=c89', '-pedantic'])
831 832
            if  build_extension.compiler == 'mingw32':
                ext_compile_flags.append('-Wno-format')
833 834
            if extra_extension_args is None:
                extra_extension_args = {}
835

Robert Bradshaw's avatar
Robert Bradshaw committed
836 837
            related_files = self.related_files(test_directory, module)
            self.copy_files(test_directory, workdir, related_files)
838 839

            from distutils.core import Extension
840 841
            extension = Extension(
                module,
842 843
                sources=self.source_files(workdir, module, related_files),
                extra_compile_args=ext_compile_flags,
844
                **extra_extension_args
845
                )
846 847 848 849 850

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

851 852
            if 'distutils' in self.tags:
                from Cython.Build.Dependencies import DistutilsInfo
853
                from Cython.Utils import open_source_file
854
                pyx_path = os.path.join(self.test_directory, self.module + ".pyx")
855 856
                with open_source_file(pyx_path) as f:
                    DistutilsInfo(f).apply(extension)
857

Stefan Behnel's avatar
Stefan Behnel committed
858
            for matcher, fixer in list(EXT_EXTRAS.items()):
859
                if isinstance(matcher, str):
Stefan Behnel's avatar
Stefan Behnel committed
860
                    # lazy init
861 862 863
                    del EXT_EXTRAS[matcher]
                    matcher = string_selector(matcher)
                    EXT_EXTRAS[matcher] = fixer
864
                if matcher(module, self.tags):
865 866 867 868
                    newext = fixer(extension)
                    if newext is EXCLUDE_EXT:
                        return
                    extension = newext or extension
869 870
            if self.language == 'cpp':
                extension.language = 'c++'
871 872 873 874 875 876
            build_extension.extensions = [extension]
            build_extension.build_temp = workdir
            build_extension.build_lib  = workdir
            build_extension.run()
        finally:
            os.chdir(cwd)
877

878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894
        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)
895

896
    def compile(self, test_directory, module, workdir, incdir,
897
                expect_errors, annotate):
898 899
        expected_errors = errors = ()
        if expect_errors:
900
            expected_errors = self.split_source_and_output(
901 902
                test_directory, module, workdir)
            test_directory = workdir
903

904 905 906 907
        if WITH_CYTHON:
            old_stderr = sys.stderr
            try:
                sys.stderr = ErrorWriter()
908
                self.run_cython(test_directory, module, workdir, incdir, annotate)
909
                errors = sys.stderr.geterrors()
910 911
            finally:
                sys.stderr = old_stderr
912

913
        tostderr = sys.__stderr__.write
914
        if 'cerror' in self.tags['tag']:
915
            if errors:
916 917 918 919
                tostderr("\n=== Expected C compile error ===\n")
                tostderr("\n=== Got Cython errors: ===\n")
                tostderr('\n'.join(errors))
                tostderr('\n\n')
920 921
                raise RuntimeError('should have generated extension code')
        elif errors or expected_errors:
922 923 924 925 926 927 928 929 930 931
            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:
932 933 934 935 936
                tostderr("\n=== Expected errors: ===\n")
                tostderr('\n'.join(expected_errors))
                tostderr("\n\n=== Got errors: ===\n")
                tostderr('\n'.join(errors))
                tostderr('\n\n')
937
                raise
938 939
            return None

940 941
        so_path = None
        if not self.cython_only:
942
            from Cython.Utils import captured_fd, print_bytes
943
            from distutils.errors import CompileError, LinkError
944 945
            show_output = True
            get_stderr = get_stdout = None
946
            try:
947 948 949
                with captured_fd(1) as get_stdout:
                    with captured_fd(2) as get_stderr:
                        so_path = self.run_distutils(test_directory, module, workdir, incdir)
950
            except Exception as exc:
951
                if ('cerror' in self.tags['tag'] and
952
                    ((get_stderr and get_stderr()) or
953
                     isinstance(exc, (CompileError, LinkError)))):
954
                    show_output = False  # expected C compiler failure
955 956 957
                else:
                    raise
            else:
958
                if 'cerror' in self.tags['tag']:
959
                    raise RuntimeError('should have failed C compile')
960 961 962 963
            finally:
                if show_output:
                    stdout = get_stdout and get_stdout().strip()
                    if stdout:
964
                        tostderr("\n=== C/C++ compiler output: ===\n")
965
                        print_bytes(stdout, end=None, file=sys.__stderr__)
966 967
                    stderr = get_stderr and get_stderr().strip()
                    if stderr:
968
                        tostderr("\n=== C/C++ compiler error output: ===\n")
969
                        print_bytes(stderr, end=None, file=sys.__stderr__)
970
                    if stdout or stderr:
971
                        tostderr("\n==============================\n")
972
        return so_path
973

974

975
class CythonRunTestCase(CythonCompileTestCase):
976 977 978 979 980
    def setUp(self):
        CythonCompileTestCase.setUp(self)
        from Cython.Compiler import Options
        Options.clear_to_none = False

981
    def shortDescription(self):
Stefan Behnel's avatar
Stefan Behnel committed
982 983 984
        if self.cython_only:
            return CythonCompileTestCase.shortDescription(self)
        else:
985
            return "compiling (%s) and running %s" % (self.language, self.name)
986 987

    def run(self, result=None):
988 989
        if result is None:
            result = self.defaultTestResult()
Stefan Behnel's avatar
Stefan Behnel committed
990
        result.startTest(self)
991
        try:
Stefan Behnel's avatar
Stefan Behnel committed
992
            self.setUp()
993
            try:
994
                self.success = False
995
                ext_so_path = self.runCompileTest()
996
                failures, errors = len(result.failures), len(result.errors)
997
                if not self.cython_only and ext_so_path is not None:
998
                    self.run_tests(result, ext_so_path)
999 1000 1001
                if failures == len(result.failures) and errors == len(result.errors):
                    # No new errors...
                    self.success = True
1002 1003
            finally:
                check_thread_termination()
1004 1005 1006
        except Exception:
            result.addError(self, sys.exc_info())
            result.stopTest(self)
1007 1008 1009 1010
        try:
            self.tearDown()
        except Exception:
            pass
1011

1012
    def run_tests(self, result, ext_so_path):
Stefan Behnel's avatar
Stefan Behnel committed
1013
        self.run_doctests(self.module, result, ext_so_path)
1014

Stefan Behnel's avatar
Stefan Behnel committed
1015
    def run_doctests(self, module_or_name, result, ext_so_path):
1016
        def run_test(result):
Stefan Behnel's avatar
Stefan Behnel committed
1017 1018 1019 1020
            if isinstance(module_or_name, basestring):
                module = import_ext(module_or_name, ext_so_path)
            else:
                module = module_or_name
1021
            tests = doctest.DocTestSuite(module)
1022 1023 1024 1025
            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
1026 1027
    if not fork or sys.version_info[0] >= 3 or not hasattr(os, 'fork'):
        run_func(result)
1028 1029
        sys.stdout.flush()
        sys.stderr.flush()
1030 1031 1032 1033 1034 1035 1036 1037 1038
        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
1039
        output = None
1040
        try:
1041
            try:
1042
                tests = partial_result = None
1043
                try:
1044 1045
                    partial_result = PartialTestResult(result)
                    run_func(partial_result)
1046 1047
                    sys.stdout.flush()
                    sys.stderr.flush()
1048 1049 1050
                    gc.collect()
                except Exception:
                    result_code = 1
1051 1052 1053 1054 1055 1056 1057 1058
                    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())
1059 1060 1061 1062
                output = open(result_file, 'wb')
                pickle.dump(partial_result.data(), output)
            except:
                traceback.print_exc()
1063
        finally:
1064
            try: sys.stderr.flush()
1065
            except: pass
1066 1067 1068 1069 1070 1071 1072
            try: sys.stdout.flush()
            except: pass
            try:
                if output is not None:
                    output.close()
            except:
                pass
1073 1074 1075 1076
            os._exit(result_code)

    try:
        cid, result_code = os.waitpid(child_id, 0)
1077
        module_name = test_name.split()[-1]
1078 1079 1080 1081 1082 1083
        # 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))
1084
        result_code >>= 8
1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096
        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
1097

1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122
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]
1123
                check_thread_termination()
1124 1125 1126 1127 1128 1129 1130
        except Exception:
            result.addError(self, sys.exc_info())
            result.stopTest(self)
        try:
            self.tearDown()
        except Exception:
            pass
1131

1132 1133 1134 1135
is_private_field = re.compile('^_[^_]').match

class _FakeClass(object):
    def __init__(self, **kwargs):
1136
        self._shortDescription = kwargs.get('module_name')
1137
        self.__dict__.update(kwargs)
1138 1139
    def shortDescription(self):
        return self._shortDescription
1140

1141 1142 1143 1144 1145 1146
try: # Py2.7+ and Py3.2+
    from unittest.runner import _TextTestResult
except ImportError:
    from unittest import _TextTestResult

class PartialTestResult(_TextTestResult):
1147
    def __init__(self, base_result):
1148
        _TextTestResult.__init__(
1149 1150 1151
            self, self._StringIO(), True,
            base_result.dots + base_result.showAll*2)

1152 1153 1154 1155 1156 1157
    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
1158
                elif attr_name != '_shortDescription':
1159 1160
                    setattr(test_case, attr_name, None)

1161
    def data(self):
1162 1163
        self.strip_error_results(self.failures)
        self.strip_error_results(self.errors)
1164 1165 1166 1167 1168 1169 1170
        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
1171
        failures, errors, tests_run, output = data
1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184
        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)


1185
class CythonUnitTestCase(CythonRunTestCase):
1186
    def shortDescription(self):
1187
        return "compiling (%s) tests in %s" % (self.language, self.name)
1188

1189 1190 1191
    def run_tests(self, result, ext_so_path):
        module = import_ext(self.module, ext_so_path)
        unittest.defaultTestLoader.loadTestsFromModule(module).run(result)
1192 1193 1194


class CythonPyregrTestCase(CythonRunTestCase):
1195 1196 1197 1198
    def setUp(self):
        CythonRunTestCase.setUp(self)
        from Cython.Compiler import Options
        Options.error_on_unknown_names = False
1199
        Options.error_on_uninitialized = False
1200
        Options._directive_defaults.update(dict(
1201 1202
            binding=True, always_allow_keywords=True,
            set_initial_path="SOURCEFILE"))
1203
        patch_inspect_isfunction()
1204

1205 1206 1207
    def related_files(self, test_directory, module_name):
        return _list_pyregr_data_files(test_directory)

1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224
    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
1225
        self.run_doctests(module, result, None)
1226

1227
    def run_tests(self, result, ext_so_path):
1228
        try:
1229
            from test import support
Vitja Makarov's avatar
Vitja Makarov committed
1230 1231
        except ImportError: # Python2.x
            from test import test_support as support
1232

1233 1234 1235 1236 1237
        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)
1238

1239
            backup = (support.run_unittest, support.run_doctest)
1240 1241
            support.run_unittest = run_unittest
            support.run_doctest = run_doctest
1242

1243
            try:
1244 1245
                try:
                    sys.stdout.flush() # helps in case of crashes
1246
                    module = import_ext(self.module, ext_so_path)
1247 1248
                    sys.stdout.flush() # helps in case of crashes
                    if hasattr(module, 'test_main'):
1249 1250 1251 1252 1253 1254 1255 1256 1257
                        # help 'doctest.DocFileTest' find the module path through frame inspection
                        fake_caller_module_globals = {
                            'module': module,
                            '__name__': module.__name__,
                        }
                        call_tests = eval(
                            'lambda: module.test_main()',
                            fake_caller_module_globals, fake_caller_module_globals)
                        call_tests()
1258 1259 1260 1261 1262
                        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
1263 1264

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

Stefan Behnel's avatar
Stefan Behnel committed
1266

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

Stefan Behnel's avatar
Stefan Behnel committed
1269

1270
def collect_unittests(path, module_prefix, suite, selectors, exclude_selectors):
1271 1272 1273 1274 1275 1276 1277
    def file_matches(filename):
        return filename.startswith("Test") and filename.endswith(".py")

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

    loader = unittest.TestLoader()
1278

1279 1280 1281
    if include_debugger:
        skipped_dirs = []
    else:
1282
        skipped_dirs = ['Cython' + os.path.sep + 'Debugger' + os.path.sep]
1283

1284
    for dirpath, dirnames, filenames in os.walk(path):
1285 1286 1287 1288 1289 1290 1291 1292 1293
        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
1294 1295 1296 1297 1298
        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")]
1299
                    modulename = module_prefix + filepath[len(path)+1:].replace(os.path.sep, '.')
Stefan Behnel's avatar
Stefan Behnel committed
1300
                    if not any(1 for match in selectors if match(modulename)):
1301
                        continue
Stefan Behnel's avatar
Stefan Behnel committed
1302
                    if any(1 for match in exclude_selectors if match(modulename)):
1303
                        continue
1304 1305 1306
                    module = __import__(modulename)
                    for x in modulename.split('.')[1:]:
                        module = getattr(module, x)
Robert Bradshaw's avatar
Robert Bradshaw committed
1307
                    suite.addTests([loader.loadTestsFromModule(module)])
1308

1309

1310
def collect_doctests(path, module_prefix, suite, selectors, exclude_selectors):
1311
    def package_matches(dirname):
1312 1313
        if dirname == 'Debugger' and not include_debugger:
            return False
1314
        return dirname not in ("Mac", "Distutils", "Plex", "Tempita")
1315
    def file_matches(filename):
Mark Florisson's avatar
Mark Florisson committed
1316
        filename, ext = os.path.splitext(filename)
1317
        blacklist = ['libcython', 'libpython', 'test_libcython_in_gdb',
1318
                     'TestLibCython']
Mark Florisson's avatar
Mark Florisson committed
1319 1320 1321 1322 1323
        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
1324
    import doctest
1325
    for dirpath, dirnames, filenames in os.walk(path):
Robert Bradshaw's avatar
Robert Bradshaw committed
1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337
        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
1338 1339
                if [ 1 for match in exclude_selectors if match(modulename) ]:
                    continue
Robert Bradshaw's avatar
Robert Bradshaw committed
1340 1341 1342
                if 'in_gdb' in modulename:
                    # These should only be imported from gdb.
                    continue
Robert Bradshaw's avatar
Robert Bradshaw committed
1343 1344 1345 1346 1347 1348 1349 1350
                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
1351

1352 1353 1354 1355 1356 1357

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
1358
    cython_root = os.path.dirname(os.path.abspath(__file__))
1359

1360
    def __init__(self, treefile, workdir, cleanup_workdir=True):
1361
        self.name = os.path.splitext(os.path.basename(treefile))[0]
1362
        self.treefile = treefile
1363
        self.workdir = os.path.join(workdir, self.name)
1364
        self.cleanup_workdir = cleanup_workdir
1365 1366 1367
        cython_syspath = [self.cython_root]
        for path in sys.path:
            if path.startswith(self.cython_root) and path not in cython_syspath:
1368 1369
                # Py3 installation and refnanny build prepend their
                # fixed paths to sys.path => prefer that over the
1370 1371 1372
                # generic one (cython_root itself goes last)
                cython_syspath.append(path)
        self.cython_syspath = os.pathsep.join(cython_syspath[::-1])
1373 1374 1375
        unittest.TestCase.__init__(self)

    def shortDescription(self):
1376
        return "End-to-end %s" % self.name
1377 1378 1379

    def setUp(self):
        from Cython.TestUtils import unpack_source_tree
1380
        _, self.commands = unpack_source_tree(self.treefile, self.workdir)
1381 1382 1383 1384 1385 1386 1387
        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:
1388 1389 1390 1391 1392 1393 1394
            for trial in range(5):
                try:
                    shutil.rmtree(self.workdir)
                except OSError:
                    time.sleep(0.1)
                else:
                    break
1395
        os.chdir(self.old_dir)
1396

1397 1398 1399 1400 1401 1402
    def _try_decode(self, content):
        try:
            return content.decode()
        except UnicodeDecodeError:
            return content.decode('iso-8859-1')

1403
    def runTest(self):
1404
        self.success = False
1405
        commands = (self.commands
Robert Bradshaw's avatar
Robert Bradshaw committed
1406
            .replace("CYTHON", "PYTHON %s" % os.path.join(self.cython_root, 'cython.py'))
1407
            .replace("PYTHON", sys.executable))
1408 1409
        old_path = os.environ.get('PYTHONPATH')
        os.environ['PYTHONPATH'] = self.cython_syspath + os.pathsep + (old_path or '')
1410
        try:
1411 1412
            for command in filter(None, commands.splitlines()):
                p = subprocess.Popen(command,
Robert Bradshaw's avatar
Robert Bradshaw committed
1413 1414 1415 1416 1417 1418 1419
                                     stderr=subprocess.PIPE,
                                     stdout=subprocess.PIPE,
                                     shell=True)
                out, err = p.communicate()
                res = p.returncode
                if res != 0:
                    print(command)
1420 1421
                    print(self._try_decode(out))
                    print(self._try_decode(err))
Robert Bradshaw's avatar
Robert Bradshaw committed
1422
                self.assertEqual(0, res, "non-zero exit status")
1423
        finally:
1424 1425 1426 1427
            if old_path:
                os.environ['PYTHONPATH'] = old_path
            else:
                del os.environ['PYTHONPATH']
1428
        self.success = True
1429 1430


1431 1432 1433 1434
# TODO: Support cython_freeze needed here as well.
# TODO: Windows support.

class EmbedTest(unittest.TestCase):
1435

1436
    working_dir = "Demos/embed"
1437

1438 1439 1440
    def setUp(self):
        self.old_dir = os.getcwd()
        os.chdir(self.working_dir)
1441
        os.system(
1442
            "make PYTHON='%s' clean > /dev/null" % sys.executable)
1443

1444 1445
    def tearDown(self):
        try:
1446 1447
            os.system(
                "make PYTHON='%s' clean > /dev/null" % sys.executable)
1448 1449 1450
        except:
            pass
        os.chdir(self.old_dir)
1451

1452
    def test_embed(self):
1453
        from distutils import sysconfig
1454
        libname = sysconfig.get_config_var('LIBRARY')
1455
        libdir = sysconfig.get_config_var('LIBDIR')
1456 1457 1458 1459 1460 1461 1462
        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')
1463
        cython = 'cython.py'
Stefan Behnel's avatar
Stefan Behnel committed
1464
        if sys.version_info[0] >=3 and CY3_DIR:
1465 1466
            cython = os.path.join(CY3_DIR, cython)
        cython = os.path.abspath(os.path.join('..', '..', cython))
1467
        self.assert_(os.system(
1468
            "make PYTHON='%s' CYTHON='%s' LIBDIR1='%s' test > make.output" % (sys.executable, cython, libdir)) == 0)
1469 1470 1471 1472
        try:
            os.remove('make.output')
        except OSError:
            pass
1473

1474 1475
class MissingDependencyExcluder:
    def __init__(self, deps):
1476
        # deps: { matcher func : module name }
1477
        self.exclude_matchers = []
1478
        for matcher, mod in deps.items():
1479 1480 1481
            try:
                __import__(mod)
            except ImportError:
Robert Bradshaw's avatar
Robert Bradshaw committed
1482
                self.exclude_matchers.append(string_selector(matcher))
1483
        self.tests_missing_deps = []
Robert Bradshaw's avatar
Robert Bradshaw committed
1484
    def __call__(self, testname, tags=None):
1485
        for matcher in self.exclude_matchers:
Robert Bradshaw's avatar
Robert Bradshaw committed
1486
            if matcher(testname, tags):
1487 1488 1489 1490
                self.tests_missing_deps.append(testname)
                return True
        return False

1491 1492 1493 1494 1495
class VersionDependencyExcluder:
    def __init__(self, deps):
        # deps: { version : matcher func }
        from sys import version_info
        self.exclude_matchers = []
1496 1497
        for ver, (compare, matcher) in deps.items():
            if compare(version_info, ver):
1498 1499
                self.exclude_matchers.append(matcher)
        self.tests_missing_deps = []
Robert Bradshaw's avatar
Robert Bradshaw committed
1500
    def __call__(self, testname, tags=None):
1501 1502 1503 1504 1505 1506
        for matcher in self.exclude_matchers:
            if matcher(testname):
                self.tests_missing_deps.append(testname)
                return True
        return False

1507

1508 1509
class FileListExcluder:

1510 1511
    def __init__(self, list_file, verbose=False):
        self.verbose = verbose
1512
        self.excludes = {}
1513 1514 1515
        self._list_file = os.path.relpath(list_file)
        with open(list_file) as f:
            for line in f:
1516 1517 1518
                line = line.strip()
                if line and line[0] != '#':
                    self.excludes[line.split()[0]] = True
1519

Robert Bradshaw's avatar
Robert Bradshaw committed
1520
    def __call__(self, testname, tags=None):
1521 1522
        exclude = (testname in self.excludes
                   or testname.split('.')[-1] in self.excludes)
1523
        if exclude and self.verbose:
1524 1525 1526
            print("Excluding %s because it's listed in %s"
                  % (testname, self._list_file))
        return exclude
1527

1528

Robert Bradshaw's avatar
Robert Bradshaw committed
1529 1530 1531 1532 1533
class TagsSelector:

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

Robert Bradshaw's avatar
Robert Bradshaw committed
1535 1536 1537 1538 1539 1540 1541
    def __call__(self, testname, tags=None):
        if tags is None:
            return False
        else:
            return self.value in tags[self.tag]

class RegExSelector:
1542

Robert Bradshaw's avatar
Robert Bradshaw committed
1543
    def __init__(self, pattern_string):
1544 1545 1546 1547 1548
        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
1549 1550 1551 1552 1553 1554 1555 1556 1557 1558

    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:])
1559 1560 1561

class ShardExcludeSelector:
    # This is an exclude selector so it can override the (include) selectors.
1562 1563
    # It may not provide uniform distribution (in time or count), but is a
    # determanistic partition of the tests which is important.
1564 1565 1566 1567 1568 1569
    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
1570

Robert Bradshaw's avatar
Robert Bradshaw committed
1571

1572 1573 1574 1575
def refactor_for_py3(distdir, cy3_dir):
    # need to convert Cython sources first
    import lib2to3.refactor
    from distutils.util import copydir_run_2to3
1576 1577
    with open('2to3-fixers.txt') as f:
        fixers = [line.strip() for line in f if line.strip()]
1578 1579 1580
    if not os.path.exists(cy3_dir):
        os.makedirs(cy3_dir)
    import distutils.log as dlog
1581
    dlog.set_threshold(dlog.INFO)
1582 1583 1584 1585 1586 1587
    copydir_run_2to3(distdir, cy3_dir, fixer_names=fixers,
                     template = '''
                     global-exclude *
                     graft Cython
                     recursive-exclude Cython *
                     recursive-include Cython *.py *.pyx *.pxd
1588
                     recursive-include Cython/Debugger/Tests *
1589
                     recursive-include Cython/Utility *
1590
                     recursive-exclude pyximport test
1591
                     include Tools/*.py
1592
                     include pyximport/*.py
1593
                     include runtests.py
1594
                     include cython.py
1595
                     include cythonize.py
1596 1597 1598
                     ''')
    sys.path.insert(0, cy3_dir)

1599

1600 1601
class PendingThreadsError(RuntimeError):
    pass
1602

1603 1604 1605
threads_seen = []

def check_thread_termination(ignore_seen=True):
1606 1607 1608 1609 1610 1611 1612 1613 1614
    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():
1615 1616 1617
            if not ignore_seen:
                blocking_threads.append(t)
                continue
1618 1619 1620 1621 1622 1623
            for seen in threads_seen:
                if t is seen:
                    break
            else:
                threads_seen.append(t)
                blocking_threads.append(t)
1624 1625 1626 1627 1628
    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))
1629
    raise PendingThreadsError("left-over threads found after running test")
1630

1631 1632
def subprocess_output(cmd):
    try:
Mark Florisson's avatar
Mark Florisson committed
1633 1634
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        return p.communicate()[0].decode('UTF-8')
1635 1636 1637 1638 1639 1640 1641 1642
    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
1643
        old_dir = os.getcwd()
1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656
        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

1657 1658 1659 1660 1661 1662 1663 1664
_orig_stdout, _orig_stderr = sys.stdout, sys.stderr
def flush_and_terminate(status):
    try:
        _orig_stdout.flush()
        _orig_stderr.flush()
    finally:
        os._exit(status)

1665
def main():
1666

1667
    global DISTDIR, WITH_CYTHON
1668 1669
    DISTDIR = os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]))

1670 1671 1672
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option("--no-cleanup", dest="cleanup_workdir",
Stefan Behnel's avatar
Stefan Behnel committed
1673 1674
                      action="store_false", default=True,
                      help="do not delete the generated C files (allows passing --no-cython on next run)")
1675 1676 1677
    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)")
1678 1679 1680
    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
1681 1682 1683
    parser.add_option("--no-cython", dest="with_cython",
                      action="store_false", default=True,
                      help="do not run the Cython compiler, only the C compiler")
1684 1685
    parser.add_option("--compiler", dest="compiler", default=None,
                      help="C compiler type")
1686 1687 1688
    backend_list = ','.join(BACKENDS)
    parser.add_option("--backends", dest="backends", default=backend_list,
                      help="select backends to test (default: %s)" % backend_list)
1689 1690
    parser.add_option("--no-c", dest="use_c",
                      action="store_false", default=True,
1691
                      help="do not test C compilation backend")
1692 1693
    parser.add_option("--no-cpp", dest="use_cpp",
                      action="store_false", default=True,
1694
                      help="do not test C++ compilation backend")
1695 1696 1697
    parser.add_option("--no-unit", dest="unittests",
                      action="store_false", default=True,
                      help="do not run the unit tests")
1698 1699 1700
    parser.add_option("--no-doctest", dest="doctests",
                      action="store_false", default=True,
                      help="do not run the doctests")
1701 1702 1703
    parser.add_option("--no-file", dest="filetests",
                      action="store_false", default=True,
                      help="do not run the file based tests")
1704 1705
    parser.add_option("--no-pyregr", dest="pyregr",
                      action="store_false", default=True,
1706
                      help="do not run the regression tests of CPython in tests/pyregr/")
1707
    parser.add_option("--cython-only", dest="cython_only",
1708 1709
                      action="store_true", default=False,
                      help="only compile pyx to c, do not run C compiler or run the tests")
1710
    parser.add_option("--no-refnanny", dest="with_refnanny",
1711
                      action="store_false", default=True,
1712
                      help="do not regression test reference counting")
1713 1714 1715
    parser.add_option("--no-fork", dest="fork",
                      action="store_false", default=True,
                      help="do not fork to run tests")
1716 1717 1718
    parser.add_option("--sys-pyregr", dest="system_pyregr",
                      action="store_true", default=False,
                      help="run the regression tests of the CPython installation")
1719 1720 1721
    parser.add_option("-x", "--exclude", dest="exclude",
                      action="append", metavar="PATTERN",
                      help="exclude tests matching the PATTERN")
1722
    parser.add_option("-j", "--shard_count", dest="shard_count", metavar="N",
1723 1724 1725 1726 1727
                      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")
1728
    parser.add_option("-C", "--coverage", dest="coverage",
Stefan Behnel's avatar
Stefan Behnel committed
1729 1730
                      action="store_true", default=False,
                      help="collect source coverage data for the Compiler")
1731 1732 1733
    parser.add_option("--coverage-xml", dest="coverage_xml",
                      action="store_true", default=False,
                      help="collect source coverage data for the Compiler in XML format")
1734 1735 1736
    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
1737
    parser.add_option("-A", "--annotate", dest="annotate_source",
1738
                      action="store_true", default=True,
Stefan Behnel's avatar
Stefan Behnel committed
1739
                      help="generate annotated HTML versions of the test source files")
1740 1741 1742
    parser.add_option("--no-annotate", dest="annotate_source",
                      action="store_false",
                      help="do not generate annotated HTML versions of the test source files")
1743
    parser.add_option("-v", "--verbose", dest="verbosity",
Stefan Behnel's avatar
Stefan Behnel committed
1744 1745
                      action="count", default=0,
                      help="display test progress, pass twice to print test names")
1746 1747
    parser.add_option("-T", "--ticket", dest="tickets",
                      action="append",
1748
                      help="a bug ticket number to run the respective test in 'tests/*'")
1749 1750 1751
    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)'")
1752 1753
    parser.add_option("--xml-output", dest="xml_output_dir", metavar="DIR",
                      help="write test results in XML to directory DIR")
1754 1755 1756
    parser.add_option("--exit-ok", dest="exit_ok", default=False,
                      action="store_true",
                      help="exit without error code even on test failures")
1757 1758 1759 1760
    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")
1761 1762
    parser.add_option("--cython-dir", dest="cython_dir", default=os.getcwd(),
                      help="Cython installation directory (default: use local source version)")
1763 1764
    parser.add_option("--debug", dest="for_debugging", default=False, action="store_true",
                      help="configure for easier use with a debugger (e.g. gdb)")
1765 1766
    parser.add_option("--pyximport-py", dest="pyximport_py", default=False, action="store_true",
                      help="use pyximport to automatically compile imported .pyx and .py files")
1767 1768
    parser.add_option("--watermark", dest="watermark", default=None,
                      help="deterministic generated by string")
1769
    parser.add_option("--use_common_utility_dir", default=False, action="store_true")
1770
    parser.add_option("--use_formal_grammar", default=False, action="store_true")
1771 1772 1773

    options, cmd_args = parser.parse_args()

1774
    WORKDIR = os.path.abspath(options.work_dir)
1775

1776 1777 1778
    if options.with_cython and sys.version_info[0] >= 3:
        sys.path.insert(0, options.cython_dir)
        if sys.version_info[:2] == (3, 2):
1779 1780 1781 1782
            try:
                # try if Cython is installed in a Py3 version
                import Cython.Compiler.Main
            except Exception:
Stefan Behnel's avatar
Stefan Behnel committed
1783 1784
                # back out anything the import process loaded, then
                # 2to3 the Cython sources to make them re-importable
1785
                cy_modules = [ name for name in sys.modules
Stefan Behnel's avatar
Stefan Behnel committed
1786
                               if name == 'Cython' or name.startswith('Cython.') ]
1787 1788 1789
                for name in cy_modules:
                    del sys.modules[name]
                # hasn't been refactored yet - do it now
1790 1791
                global CY3_DIR
                CY3_DIR = cy3_dir = os.path.join(WORKDIR, 'Cy3')
Stefan Behnel's avatar
Stefan Behnel committed
1792
                refactor_for_py3(DISTDIR, cy3_dir)
1793

1794 1795 1796 1797
    if options.watermark:
        import Cython.Compiler.Version
        Cython.Compiler.Version.watermark = options.watermark

1798
    WITH_CYTHON = options.with_cython
1799

1800
    coverage = None
1801
    if options.coverage or options.coverage_xml or options.coverage_html:
1802
        if options.shard_count <= 1 and options.shard_num < 0:
1803 1804 1805
            if not WITH_CYTHON:
                options.coverage = options.coverage_xml = options.coverage_html = False
            else:
1806
                print("Enabling coverage analysis")
1807
                from coverage import coverage as _coverage
1808
                coverage = _coverage(branch=True, omit=['Test*'])
1809 1810
                coverage.erase()
                coverage.start()
1811

1812 1813 1814
    if options.xml_output_dir:
        shutil.rmtree(options.xml_output_dir, ignore_errors=True)

1815
    if WITH_CYTHON:
1816
        global CompilationOptions, pyrex_default_options, cython_compile
1817 1818 1819 1820
        from Cython.Compiler.Main import \
            CompilationOptions, \
            default_options as pyrex_default_options, \
            compile as cython_compile
1821 1822
        from Cython.Compiler import Errors
        Errors.LEVEL = 0 # show all warnings
Stefan Behnel's avatar
Stefan Behnel committed
1823
        from Cython.Compiler import Options
1824
        Options.generate_cleanup_code = 3   # complete cleanup code
Stefan Behnel's avatar
Stefan Behnel committed
1825 1826
        from Cython.Compiler import DebugFlags
        DebugFlags.debug_temp_code_comments = 1
1827
        pyrex_default_options['formal_grammar'] = options.use_formal_grammar
1828

1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841
    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
1842
            print("Errors for shards %s" % ", ".join([str(e) for e in errors]))
1843 1844 1845 1846
            return_code = 1
        else:
            return_code = 0
    else:
1847
        _, return_code = runtests(options, cmd_args, coverage)
1848 1849 1850 1851 1852 1853 1854
    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
1855 1856
    else:
        sys.exit(return_code)
1857 1858 1859 1860 1861 1862 1863


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

1864
def runtests(options, cmd_args, coverage=None):
1865

1866 1867 1868 1869
    WITH_CYTHON = options.with_cython
    ROOTDIR = os.path.abspath(options.root_dir)
    WORKDIR = os.path.abspath(options.work_dir)

1870
    xml_output_dir = options.xml_output_dir
1871 1872
    if options.shard_num > -1:
        WORKDIR = os.path.join(WORKDIR, str(options.shard_num))
1873 1874
        if xml_output_dir:
            xml_output_dir = os.path.join(xml_output_dir, 'shard-%03d' % options.shard_num)
1875

1876
    # RUN ALL TESTS!
1877
    UNITTEST_MODULE = "Cython"
1878
    UNITTEST_ROOT = os.path.join(os.path.dirname(__file__), UNITTEST_MODULE)
1879 1880
    if WITH_CYTHON:
        if os.path.exists(WORKDIR):
1881
            for path in os.listdir(WORKDIR):
1882
                if path in ("support", "Cy3"): continue
1883
                shutil.rmtree(os.path.join(WORKDIR, path), ignore_errors=True)
1884 1885
    if not os.path.exists(WORKDIR):
        os.makedirs(WORKDIR)
1886

1887 1888 1889 1890 1891 1892 1893
    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")
1894

1895 1896 1897 1898
    if options.for_debugging:
        options.cleanup_workdir = False
        options.cleanup_sharedlibs = False
        options.fork = False
1899
        if WITH_CYTHON and include_debugger:
1900 1901 1902
            from Cython.Compiler.Main import default_options as compiler_default_options
            compiler_default_options['gdb_debug'] = True
            compiler_default_options['output_dir'] = os.getcwd()
1903

1904 1905 1906 1907 1908
    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"))
1909
        sys.path.insert(0, os.path.split(libpath)[0])
1910
        CFLAGS.append("-DCYTHON_REFNANNY=1")
1911

1912
    if xml_output_dir and options.fork:
Stefan Behnel's avatar
Stefan Behnel committed
1913 1914 1915 1916
        # doesn't currently work together
        sys.stderr.write("Disabling forked testing to support XML test output\n")
        options.fork = False

1917 1918 1919
    if WITH_CYTHON and options.language_level == 3:
        sys.stderr.write("Using Cython language level 3.\n")

1920
    test_bugs = False
Stefan Behnel's avatar
Stefan Behnel committed
1921 1922 1923
    if options.tickets:
        for ticket_number in options.tickets:
            test_bugs = True
1924
            cmd_args.append('ticket:%s' % ticket_number)
1925 1926 1927 1928
    if not test_bugs:
        for selector in cmd_args:
            if selector.startswith('bugs'):
                test_bugs = True
1929

Robert Bradshaw's avatar
Robert Bradshaw committed
1930
    selectors = [ string_selector(r) for r in cmd_args ]
1931
    verbose_excludes = selectors or options.verbosity >= 2
1932
    if not selectors:
Robert Bradshaw's avatar
Robert Bradshaw committed
1933
        selectors = [ lambda x, tags=None: True ]
1934

1935
    # Check which external modules are not present and exclude tests
1936 1937
    # which depends on them (by prefix)

1938 1939
    missing_dep_excluder = MissingDependencyExcluder(EXT_DEP_MODULES)
    version_dep_excluder = VersionDependencyExcluder(VER_DEP_MODULES)
Robert Bradshaw's avatar
Robert Bradshaw committed
1940
    exclude_selectors = [missing_dep_excluder, version_dep_excluder] # want to print msg at exit
1941

1942
    try:
1943 1944 1945 1946
        import IPython.core.release
        if list(IPython.core.release._ver) < [1, 0, 0]:
            raise ImportError
    except (ImportError, AttributeError, TypeError):
1947
        exclude_selectors.append(RegExSelector('IPython'))
1948

1949 1950
    try:
        import jedi
1951
        if not ([0, 9] <= list(map(int, re.findall('[0-9]+', jedi.__version__ or '0')))):
1952
            raise ImportError
Stefan Behnel's avatar
Stefan Behnel committed
1953
    except (ImportError, AttributeError, TypeError):
1954 1955
        exclude_selectors.append(RegExSelector('Jedi'))

1956
    if options.exclude:
Robert Bradshaw's avatar
Robert Bradshaw committed
1957
        exclude_selectors += [ string_selector(r) for r in options.exclude ]
1958

1959 1960 1961
    if not COMPILER_HAS_INT128 or not IS_CPYTHON:
        exclude_selectors += [RegExSelector('int128')]

1962 1963 1964
    if options.shard_num > -1:
        exclude_selectors.append(ShardExcludeSelector(options.shard_num, options.shard_count))

1965
    if not test_bugs:
1966 1967
        exclude_selectors += [
            FileListExcluder(os.path.join(ROOTDIR, bugs_file_name), verbose=verbose_excludes)
1968 1969
            for bugs_file_name in ['bugs.txt'] + (['pypy_bugs.txt'] if IS_PYPY else []) +
            (['windows_bugs.txt'] if sys.platform == 'win32' else [])
1970
        ]
1971

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

1975 1976 1977
    global COMPILER
    if options.compiler:
        COMPILER = options.compiler
1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990

    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)
1991 1992
    if options.shard_num <= 0:
        sys.stderr.write("Backends: %s\n" % ','.join(backends))
1993 1994
    languages = backends

1995 1996 1997 1998 1999 2000 2001
    if options.use_common_utility_dir:
        common_utility_dir = os.path.join(WORKDIR, 'utility_code')
        if not os.path.exists(common_utility_dir):
            os.makedirs(common_utility_dir)
    else:
        common_utility_dir = None

2002
    sys.stderr.write("\n")
2003

2004 2005 2006
    test_suite = unittest.TestSuite()

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

2009
    if options.doctests:
2010
        collect_doctests(UNITTEST_ROOT, UNITTEST_MODULE + ".", test_suite, selectors, exclude_selectors)
2011

2012
    if options.filetests and languages:
2013
        filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors,
2014
                                options.annotate_source, options.cleanup_workdir,
2015 2016
                                options.cleanup_sharedlibs, options.cleanup_failures,
                                options.pyregr,
2017
                                options.cython_only, languages, test_bugs,
2018 2019
                                options.fork, options.language_level,
                                common_utility_dir)
2020 2021
        test_suite.addTest(filetests.build_suite())

2022
    if options.system_pyregr and languages:
2023
        sys_pyregr_dir = os.path.join(sys.prefix, 'lib', 'python'+sys.version[:3], 'test')
2024 2025
        if not os.path.isdir(sys_pyregr_dir):
            sys_pyregr_dir = os.path.join(os.path.dirname(sys.executable), 'Lib', 'test')  # source build
2026 2027 2028
        if os.path.isdir(sys_pyregr_dir):
            filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors,
                                    options.annotate_source, options.cleanup_workdir,
2029 2030
                                    options.cleanup_sharedlibs, options.cleanup_failures,
                                    True,
2031
                                    options.cython_only, languages, test_bugs,
2032 2033
                                    options.fork, sys.version_info[0],
                                    common_utility_dir)
2034 2035
            sys.stderr.write("Including CPython regression tests in %s\n" % sys_pyregr_dir)
            test_suite.addTest(filetests.handle_directory(sys_pyregr_dir, 'pyregr'))
2036

2037
    if xml_output_dir:
2038
        from Cython.Tests.xmlrunner import XMLTestRunner
2039 2040 2041 2042
        if not os.path.exists(xml_output_dir):
            try: os.makedirs(xml_output_dir)
            except OSError: pass  # concurrency issue?
        test_runner = XMLTestRunner(output=xml_output_dir,
Stefan Behnel's avatar
Stefan Behnel committed
2043
                                    verbose=options.verbosity > 0)
2044 2045 2046
    else:
        test_runner = unittest.TextTestRunner(verbosity=options.verbosity)

2047
    if options.pyximport_py:
2048
        from pyximport import pyximport
2049
        pyximport.install(pyimport=True, build_dir=os.path.join(WORKDIR, '_pyximport'),
2050
                          load_py_module_on_import_failure=True, inplace=True)
2051

2052
    result = test_runner.run(test_suite)
2053

2054 2055 2056
    if common_utility_dir and options.shard_num < 0 and options.cleanup_workdir:
        shutil.rmtree(common_utility_dir)

2057
    if coverage is not None:
2058
        coverage.stop()
2059 2060
        ignored_modules = set(
            'Cython.Compiler.' + name
2061 2062 2063
            for name in ('Version', 'DebugFlags', 'CmdLine')) | set(
            'Cython.' + name
            for name in ('Debugging',))
2064 2065 2066 2067 2068 2069 2070 2071 2072
        ignored_packages = ['Cython.Runtime', 'Cython.Tempita']
        modules = [
            module for name, module in sys.modules.items()
            if module is not None and
            name.startswith('Cython.') and
            '.Tests' not in name and
            name not in ignored_modules and
            not any(name.startswith(package) for package in ignored_packages)
        ]
2073 2074 2075
        if options.coverage:
            coverage.report(modules, show_missing=0)
        if options.coverage_xml:
2076
            coverage.xml_report(modules, outfile="coverage-report.xml")
2077 2078
        if options.coverage_html:
            coverage.html_report(modules, directory="coverage-report-html")
2079 2080 2081 2082 2083

    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)
2084

2085
    if options.with_refnanny:
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
2086
        import refnanny
2087
        sys.stderr.write("\n".join([repr(x) for x in refnanny.reflog]))
2088

2089
    if options.exit_ok:
2090
        return options.shard_num, 0
2091
    else:
2092
        return options.shard_num, not result.wasSuccessful()
2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103


if __name__ == '__main__':
    try:
        main()
    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
2104
            flush_and_terminate(1)
2105
        sys.exit(1)