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

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

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

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

try:
    import cPickle as pickle
except ImportError:
    import pickle

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

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

Robert Bradshaw's avatar
Robert Bradshaw committed
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
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
        def __repr__(self):
            return repr(self._dict)

62
WITH_CYTHON = True
63
CY3_DIR = None
64 65 66 67

from distutils.dist import Distribution
from distutils.core import Extension
from distutils.command.build_ext import build_ext as _build_ext
Mark Florisson's avatar
Mark Florisson committed
68
from distutils import sysconfig
69 70
distutils_distro = Distribution()

Robert Bradshaw's avatar
Robert Bradshaw committed
71 72 73 74 75 76 77 78 79 80 81 82
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)

83
EXT_DEP_MODULES = {
84 85 86
    'tag:numpy' : 'numpy',
    'tag:pstats': 'pstats',
    'tag:posix' : 'posix',
87
    'tag:array' : 'array',
88 89
}

90
def update_numpy_extension(ext):
91
    import numpy
92
    ext.include_dirs.append(numpy.get_include())
93

Mark Florisson's avatar
Mark Florisson committed
94
def update_openmp_extension(ext):
95
    ext.openmp = True
Mark Florisson's avatar
Mark Florisson committed
96 97 98 99 100 101 102 103 104 105 106 107 108
    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
109 110
    elif sys.platform == 'win32':
        return ext
Mark Florisson's avatar
Mark Florisson committed
111 112 113 114 115 116 117 118 119 120 121 122 123 124

    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')
125 126

    if not cc:
127 128 129
        if sys.platform == 'win32':
            return '/openmp', ''
        return None
Mark Florisson's avatar
Mark Florisson committed
130

131 132 133
    # For some reason, cc can be e.g. 'gcc -pthread'
    cc = cc.split()[0]

134 135 136 137
    # Force english output
    env = os.environ.copy()
    env['LC_MESSAGES'] = 'C'

Mark Florisson's avatar
Mark Florisson committed
138 139
    matcher = re.compile(r"gcc version (\d+\.\d+)").search
    try:
140
        p = subprocess.Popen([cc, "-v"], stderr=subprocess.PIPE, env=env)
Robert Bradshaw's avatar
Robert Bradshaw committed
141 142 143 144 145 146
    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()
147

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

150 151 152 153 154
    gcc_version = matcher(output)
    if not gcc_version:
        return None # not gcc - FIXME: do something about other compilers

    compiler_version = gcc_version.group(1)
Mark Florisson's avatar
Mark Florisson committed
155 156 157
    if compiler_version and compiler_version.split('.') >= ['4', '2']:
        return '-fopenmp', '-fopenmp'

158 159 160 161
try:
    locale.setlocale(locale.LC_ALL, '')
except locale.Error:
    pass
162

Mark Florisson's avatar
Mark Florisson committed
163 164 165 166 167 168
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()

169 170
EXT_EXTRAS = {
    'tag:numpy' : update_numpy_extension,
Mark Florisson's avatar
Mark Florisson committed
171
    'tag:openmp': update_openmp_extension,
172
}
173

Robert Bradshaw's avatar
Robert Bradshaw committed
174
# TODO: use tags
175
VER_DEP_MODULES = {
176
    # tests are excluded if 'CurrentPythonVersion OP VersionTuple', i.e.
Stefan Behnel's avatar
Stefan Behnel committed
177 178
    # (2,4) : (operator.lt, ...) excludes ... when PyVer < 2.4.x
    (2,4) : (operator.lt, lambda x: x in ['run.extern_builtins_T258',
179 180
                                          'run.builtin_sorted',
                                          'run.reversed_iteration',
Stefan Behnel's avatar
Stefan Behnel committed
181
                                          ]),
182 183
    (2,5) : (operator.lt, lambda x: x in ['run.any',
                                          'run.all',
184
                                          'run.yield_from_pep380',  # GeneratorExit
185
                                          'run.generator_frame_cycle', # yield in try-finally
Haoyu Bai's avatar
Haoyu Bai committed
186
                                          'run.relativeimport_T542',
187
                                          'run.relativeimport_star_T542',
188
                                          'run.initial_file_path',  # relative import
189
                                          ]),
190
    (2,6) : (operator.lt, lambda x: x in ['run.print_function',
191
                                          'run.language_level', # print function
192
                                          'run.cython3',
Stefan Behnel's avatar
Stefan Behnel committed
193
                                          'run.property_decorator_T593', # prop.setter etc.
Stefan Behnel's avatar
comment  
Stefan Behnel committed
194
                                          'run.generators_py', # generators, with statement
195
                                          'run.pure_py', # decorators, with statement
196
                                          'run.purecdef',
197
                                          'run.struct_conversion',
Stefan Behnel's avatar
Stefan Behnel committed
198
                                          # memory views require buffer protocol
199 200 201 202 203
                                          'memoryview.cythonarray',
                                          'memoryview.memslice',
                                          'memoryview.numpy_memoryview',
                                          'memoryview.memoryviewattrs',
                                          'memoryview.memoryview',
204
                                          ]),
Stefan Behnel's avatar
Stefan Behnel committed
205
    (2,7) : (operator.lt, lambda x: x in ['run.withstat_py', # multi context with statement
Stefan Behnel's avatar
Stefan Behnel committed
206
                                          'run.yield_inside_lambda',
Stefan Behnel's avatar
Stefan Behnel committed
207
                                          ]),
208 209 210 211
    # 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...
212 213 214
    (2,999): (operator.lt, lambda x: x in ['run.special_methods_T561_py3',
                                           'run.test_raisefrom',
                                           ]),
215
    (3,): (operator.ge, lambda x: x in ['run.non_future_division',
Stefan Behnel's avatar
Stefan Behnel committed
216
                                        'compile.extsetslice',
217 218
                                        'compile.extdelslice',
                                        'run.special_methods_T561_py2']),
219 220
}

221 222 223 224 225 226 227 228
# files that should not be converted to Python 3 code with 2to3
KEEP_2X_FILES = [
    os.path.join('Cython', 'Debugger', 'Tests', 'test_libcython_in_gdb.py'),
    os.path.join('Cython', 'Debugger', 'Tests', 'test_libpython_in_gdb.py'),
    os.path.join('Cython', 'Debugger', 'libcython.py'),
    os.path.join('Cython', 'Debugger', 'libpython.py'),
]

229
COMPILER = None
230
INCLUDE_DIRS = [ d for d in os.getenv('INCLUDE', '').split(os.pathsep) if d ]
231
CFLAGS = os.getenv('CFLAGS', '').split()
232
CCACHE = os.getenv('CYTHON_RUNTESTS_CCACHE', '').split()
233
TEST_SUPPORT_DIR = 'testsupport'
234

235
BACKENDS = ['c', 'cpp']
236

237 238 239 240 241 242 243 244 245 246
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

Robert Bradshaw's avatar
cleanup  
Robert Bradshaw committed
247
@memoize
Robert Bradshaw's avatar
Robert Bradshaw committed
248 249
def parse_tags(filepath):
    tags = defaultdict(list)
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
    f = io_open(filepath, encoding='ISO-8859-1', errors='replace')
    try:
        for line in f:
            line = line.strip()
            if not line:
                continue
            if line[0] != '#':
                break
            ix = line.find(':')
            if ix != -1:
                tag = line[1:ix].strip()
                values = line[ix+1:].split(',')
                tags[tag].extend([value.strip() for value in values])
    finally:
        f.close()
Robert Bradshaw's avatar
Robert Bradshaw committed
265 266
    return tags

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

Stefan Behnel's avatar
Stefan Behnel committed
269

270 271
class build_ext(_build_ext):
    def build_extension(self, ext):
272 273 274 275 276 277
        try:
            try: # Py2.7+ & Py3.2+
                compiler_obj = self.compiler_obj
            except AttributeError:
                compiler_obj = self.compiler
            if ext.language == 'c++':
278
                compiler_obj.compiler_so.remove('-Wstrict-prototypes')
279 280
            if CCACHE:
                compiler_obj.compiler_so = CCACHE + compiler_obj.compiler_so
281 282
            if getattr(ext, 'openmp', None) and compiler_obj.compiler_type == 'msvc':
                ext.extra_compile_args.append('/openmp')
283 284
        except Exception:
            pass
285
        _build_ext.build_extension(self, ext)
286 287

class ErrorWriter(object):
288
    match_error = re.compile('(warning:)?(?:.*:)?\s*([-0-9]+)\s*:\s*([-0-9]+)\s*:\s*(.*)').match
289 290 291 292
    def __init__(self):
        self.output = []
        self.write = self.output.append

293
    def _collect(self, collect_errors, collect_warnings):
294
        s = ''.join(self.output)
295
        result = []
296 297 298
        for line in s.split('\n'):
            match = self.match_error(line)
            if match:
299 300 301
                is_warning, line, column, message = match.groups()
                if (is_warning and collect_warnings) or \
                        (not is_warning and collect_errors):
302 303
                    result.append( (int(line), int(column), message.strip()) )
        result.sort()
Stefan Behnel's avatar
Stefan Behnel committed
304
        return [ "%d:%d: %s" % values for values in result ]
305 306 307 308 309 310 311 312 313

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

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

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

315
class TestBuilder(object):
316
    def __init__(self, rootdir, workdir, selectors, exclude_selectors, annotate,
317 318
                 cleanup_workdir, cleanup_sharedlibs, cleanup_failures,
                 with_pyregr, cython_only, languages, test_bugs, fork, language_level):
319 320
        self.rootdir = rootdir
        self.workdir = workdir
321
        self.selectors = selectors
322
        self.exclude_selectors = exclude_selectors
323
        self.annotate = annotate
324
        self.cleanup_workdir = cleanup_workdir
325
        self.cleanup_sharedlibs = cleanup_sharedlibs
326
        self.cleanup_failures = cleanup_failures
327
        self.with_pyregr = with_pyregr
Stefan Behnel's avatar
Stefan Behnel committed
328 329
        self.cython_only = cython_only
        self.languages = languages
330
        self.test_bugs = test_bugs
331
        self.fork = fork
332
        self.language_level = language_level
333 334 335

    def build_suite(self):
        suite = unittest.TestSuite()
336 337 338
        filenames = os.listdir(self.rootdir)
        filenames.sort()
        for filename in filenames:
339
            path = os.path.join(self.rootdir, filename)
340
            if os.path.isdir(path) and filename != TEST_SUPPORT_DIR:
341 342
                if filename == 'pyregr' and not self.with_pyregr:
                    continue
343 344
                if filename == 'broken' and not self.test_bugs:
                    continue
345
                suite.addTest(
346
                    self.handle_directory(path, filename))
347 348
        if sys.platform not in ['win32']:
            # Non-Windows makefile.
349 350
            if [1 for selector in self.selectors if selector("embedded")] \
                and not [1 for selector in self.exclude_selectors if selector("embedded")]:
351
                suite.addTest(unittest.makeSuite(EmbedTest))
352 353
        return suite

354
    def handle_directory(self, path, context):
355 356 357 358
        workdir = os.path.join(self.workdir, context)
        if not os.path.exists(workdir):
            os.makedirs(workdir)

359
        suite = unittest.TestSuite()
Robert Bradshaw's avatar
Robert Bradshaw committed
360
        filenames = list_unchanging_dir(path)
361 362
        filenames.sort()
        for filename in filenames:
363 364 365
            filepath = os.path.join(path, filename)
            module, ext = os.path.splitext(filename)
            if ext not in ('.py', '.pyx', '.srctree'):
366
                continue
367 368 369
            if filename.startswith('.'):
                continue # certain emacs backup files
            tags = parse_tags(filepath)
Robert Bradshaw's avatar
Robert Bradshaw committed
370
            fqmodule = "%s.%s" % (context, module)
371
            if not [ 1 for match in self.selectors
Robert Bradshaw's avatar
Robert Bradshaw committed
372
                     if match(fqmodule, tags) ]:
373
                continue
374
            if self.exclude_selectors:
Robert Bradshaw's avatar
Robert Bradshaw committed
375 376
                if [1 for match in self.exclude_selectors 
                        if match(fqmodule, tags)]:
377
                    continue
378 379 380 381 382 383 384 385

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

            if ext == '.srctree':
386 387
                if 'cpp' not in tags['tag'] or 'cpp' in self.languages:
                    suite.addTest(EndToEndTest(filepath, workdir, self.cleanup_workdir))
388 389 390 391 392 393
                continue

            # Choose the test suite.
            if mode == 'pyregr':
                if not filename.startswith('test_'):
                    continue
394
                test_class = CythonPyregrTestCase
395
            elif mode == 'run':
396
                if module.startswith("test_"):
Stefan Behnel's avatar
Stefan Behnel committed
397
                    test_class = CythonUnitTestCase
398
                else:
Stefan Behnel's avatar
Stefan Behnel committed
399
                    test_class = CythonRunTestCase
400
            else:
Stefan Behnel's avatar
Stefan Behnel committed
401
                test_class = CythonCompileTestCase
402

Stefan Behnel's avatar
Stefan Behnel committed
403
            for test in self.build_tests(test_class, path, workdir,
Robert Bradshaw's avatar
Robert Bradshaw committed
404
                                         module, mode == 'error', tags):
Stefan Behnel's avatar
Stefan Behnel committed
405
                suite.addTest(test)
Stefan Behnel's avatar
fix  
Stefan Behnel committed
406
            if mode == 'run' and ext == '.py' and not self.cython_only:
407 408
                # additionally test file in real Python
                suite.addTest(PureDoctestTestCase(module, os.path.join(path, filename)))
409
                
410 411
        return suite

Robert Bradshaw's avatar
Robert Bradshaw committed
412
    def build_tests(self, test_class, path, workdir, module, expect_errors, tags):
Robert Bradshaw's avatar
Robert Bradshaw committed
413
        if 'werror' in tags['tag']:
Vitja Makarov's avatar
Vitja Makarov committed
414 415 416 417
            warning_errors = True
        else:
            warning_errors = False

418
        if expect_errors:
Robert Bradshaw's avatar
Robert Bradshaw committed
419
            if 'cpp' in tags['tag'] and 'cpp' in self.languages:
Robert Bradshaw's avatar
Robert Bradshaw committed
420 421 422
                languages = ['cpp']
            else:
                languages = self.languages[:1]
423 424
        else:
            languages = self.languages
Robert Bradshaw's avatar
Robert Bradshaw committed
425
        if 'cpp' in tags['tag'] and 'c' in languages:
426 427
            languages = list(languages)
            languages.remove('c')
Stefan Behnel's avatar
Stefan Behnel committed
428
        tests = [ self.build_test(test_class, path, workdir, module,
Vitja Makarov's avatar
Vitja Makarov committed
429
                                  language, expect_errors, warning_errors)
430
                  for language in languages ]
Stefan Behnel's avatar
Stefan Behnel committed
431 432 433
        return tests

    def build_test(self, test_class, path, workdir, module,
Vitja Makarov's avatar
Vitja Makarov committed
434
                   language, expect_errors, warning_errors):
435 436 437 438
        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)
Stefan Behnel's avatar
Stefan Behnel committed
439 440 441 442 443 444
        return test_class(path, workdir, module,
                          language=language,
                          expect_errors=expect_errors,
                          annotate=self.annotate,
                          cleanup_workdir=self.cleanup_workdir,
                          cleanup_sharedlibs=self.cleanup_sharedlibs,
445
                          cleanup_failures=self.cleanup_failures,
446
                          cython_only=self.cython_only,
447
                          fork=self.fork,
Vitja Makarov's avatar
Vitja Makarov committed
448 449
                          language_level=self.language_level,
                          warning_errors=warning_errors)
Stefan Behnel's avatar
Stefan Behnel committed
450

451
class CythonCompileTestCase(unittest.TestCase):
452
    def __init__(self, test_directory, workdir, module, language='c',
453
                 expect_errors=False, annotate=False, cleanup_workdir=True,
454 455
                 cleanup_sharedlibs=True, cleanup_failures=True, cython_only=False,
                 fork=True, language_level=2, warning_errors=False):
456
        self.test_directory = test_directory
457 458
        self.workdir = workdir
        self.module = module
Stefan Behnel's avatar
Stefan Behnel committed
459
        self.language = language
460
        self.expect_errors = expect_errors
461
        self.annotate = annotate
462
        self.cleanup_workdir = cleanup_workdir
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
463
        self.cleanup_sharedlibs = cleanup_sharedlibs
464
        self.cleanup_failures = cleanup_failures
Stefan Behnel's avatar
Stefan Behnel committed
465
        self.cython_only = cython_only
466
        self.fork = fork
467
        self.language_level = language_level
Vitja Makarov's avatar
Vitja Makarov committed
468
        self.warning_errors = warning_errors
469 470 471
        unittest.TestCase.__init__(self)

    def shortDescription(self):
Stefan Behnel's avatar
Stefan Behnel committed
472
        return "compiling (%s) %s" % (self.language, self.module)
473

Stefan Behnel's avatar
Stefan Behnel committed
474
    def setUp(self):
Vitja Makarov's avatar
Vitja Makarov committed
475
        from Cython.Compiler import Options
476 477
        self._saved_options = [ (name, getattr(Options, name))
                                for name in ('warning_errors', 'error_on_unknown_names') ]
478
        self._saved_default_directives = Options.directive_defaults.items()
Vitja Makarov's avatar
Vitja Makarov committed
479 480
        Options.warning_errors = self.warning_errors

481 482
        if not os.path.exists(self.workdir):
            os.makedirs(self.workdir)
Stefan Behnel's avatar
Stefan Behnel committed
483 484 485
        if self.workdir not in sys.path:
            sys.path.insert(0, self.workdir)

486
    def tearDown(self):
Vitja Makarov's avatar
Vitja Makarov committed
487
        from Cython.Compiler import Options
488 489
        for name, value in self._saved_options:
            setattr(Options, name, value)
490
        Options.directive_defaults = dict(self._saved_default_directives)
Vitja Makarov's avatar
Vitja Makarov committed
491

Stefan Behnel's avatar
Stefan Behnel committed
492 493 494 495 496 497 498 499
        try:
            sys.path.remove(self.workdir)
        except ValueError:
            pass
        try:
            del sys.modules[self.module]
        except KeyError:
            pass
500 501 502
        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
503
        if os.path.exists(self.workdir):
504 505 506 507 508 509 510 511 512 513
            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
                            rmfile[-4:] == ".cpp" or
                            rmfile.endswith(".html")):
                            continue
                    if not cleanup_lib_files and (rmfile.endswith(".so") or rmfile.endswith(".dll")):
514
                        continue
515 516 517 518 519 520 521 522
                    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
523

524
    def runTest(self):
525
        self.success = False
526
        self.runCompileTest()
527
        self.success = True
528 529

    def runCompileTest(self):
530 531
        self.compile(self.test_directory, self.module, self.workdir,
                     self.test_directory, self.expect_errors, self.annotate)
532

533 534 535 536 537
    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
538 539 540
    def build_target_filename(self, module_name):
        target = '%s.%s' % (module_name, self.language)
        return target
Robert Bradshaw's avatar
Robert Bradshaw committed
541 542
    
    def related_files(self, test_directory, module_name):
543
        is_related = re.compile('%s_.*[.].*' % module_name).match
Robert Bradshaw's avatar
Robert Bradshaw committed
544 545 546 547 548 549 550
        return [filename for filename in list_unchanging_dir(test_directory)
            if is_related(filename)]

    def copy_files(self, test_directory, target_directory, file_list):
        for filename in file_list:
            shutil.copy(os.path.join(test_directory, filename),
                        target_directory)
551

Robert Bradshaw's avatar
Robert Bradshaw committed
552 553 554 555
    def source_files(self, workdir, module_name, file_list):
        return ([self.build_target_filename(module_name)] +
            [filename for filename in file_list
                if not os.path.isfile(os.path.join(workdir, filename))])
556 557

    def split_source_and_output(self, test_directory, module, workdir):
558
        source_file = self.find_module_source_file(os.path.join(test_directory, module) + '.pyx')
559
        source_and_output = io_open(source_file, 'rU', encoding='ISO-8859-1')
560
        try:
561 562
            out = io_open(os.path.join(workdir, module + os.path.splitext(source_file)[1]),
                              'w', encoding='ISO-8859-1')
563 564 565 566 567 568 569 570
            for line in source_and_output:
                if line.startswith("_ERRORS"):
                    out.close()
                    out = ErrorWriter()
                else:
                    out.write(line)
        finally:
            source_and_output.close()
571 572 573
        try:
            geterrors = out.geterrors
        except AttributeError:
574
            out.close()
575 576 577 578
            return []
        else:
            return geterrors()

579 580
    def run_cython(self, test_directory, module, targetdir, incdir, annotate,
                   extra_compile_options=None):
581
        include_dirs = INCLUDE_DIRS + [os.path.join(test_directory, '..', TEST_SUPPORT_DIR)]
582 583
        if incdir:
            include_dirs.append(incdir)
584
        source = self.find_module_source_file(
585
            os.path.join(test_directory, module + '.pyx'))
Stefan Behnel's avatar
Stefan Behnel committed
586
        target = os.path.join(targetdir, self.build_target_filename(module))
587

588 589
        if extra_compile_options is None:
            extra_compile_options = {}
590

591 592 593 594 595 596
        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
597

598
        options = CompilationOptions(
599
            default_options,
600 601
            include_path = include_dirs,
            output_file = target,
602
            annotate = annotate,
Stefan Behnel's avatar
Stefan Behnel committed
603 604
            use_listing_file = False,
            cplus = self.language == 'cpp',
605
            language_level = self.language_level,
606
            generate_pxi = False,
607
            evaluate_tree_assertions = True,
608
            **extra_compile_options
609
            )
610 611 612
        cython_compile(source, options=options,
                       full_module_name=module)

613
    def run_distutils(self, test_directory, module, workdir, incdir,
614
                      extra_extension_args=None):
615 616 617 618 619 620
        original_source = self.find_module_source_file(
            os.path.join(test_directory, module + '.pyx'))
        try:
            tags = parse_tags(original_source)
        except IOError:
            tags = {}
621 622 623 624 625 626 627 628
        cwd = os.getcwd()
        os.chdir(workdir)
        try:
            build_extension = build_ext(distutils_distro)
            build_extension.include_dirs = INCLUDE_DIRS[:]
            if incdir:
                build_extension.include_dirs.append(incdir)
            build_extension.finalize_options()
629 630
            if COMPILER:
                build_extension.compiler = COMPILER
631

632
            ext_compile_flags = CFLAGS[:]
633 634 635 636
            compiler = COMPILER or sysconfig.get_config_var('CC')

            if self.language == 'c' and compiler == 'gcc':
                ext_compile_flags.extend(['-std=c89', '-pedantic'])
637 638
            if  build_extension.compiler == 'mingw32':
                ext_compile_flags.append('-Wno-format')
639 640
            if extra_extension_args is None:
                extra_extension_args = {}
641

Robert Bradshaw's avatar
Robert Bradshaw committed
642 643
            related_files = self.related_files(test_directory, module)
            self.copy_files(test_directory, workdir, related_files)
644 645
            extension = Extension(
                module,
Robert Bradshaw's avatar
Robert Bradshaw committed
646
                sources = self.source_files(workdir, module, related_files),
647
                extra_compile_args = ext_compile_flags,
648
                **extra_extension_args
649
                )
Mark Florisson's avatar
Mark Florisson committed
650 651 652 653 654

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

655 656 657 658 659 660
            for matcher, fixer in EXT_EXTRAS.items():
                if isinstance(matcher, str):
                    del EXT_EXTRAS[matcher]
                    matcher = string_selector(matcher)
                    EXT_EXTRAS[matcher] = fixer
                if matcher(module, tags):
Mark Florisson's avatar
Mark Florisson committed
661 662 663 664
                    newext = fixer(extension)
                    if newext is EXCLUDE_EXT:
                        return
                    extension = newext or extension
665 666
            if self.language == 'cpp':
                extension.language = 'c++'
667 668 669 670 671 672
            build_extension.extensions = [extension]
            build_extension.build_temp = workdir
            build_extension.build_lib  = workdir
            build_extension.run()
        finally:
            os.chdir(cwd)
673

674
    def compile(self, test_directory, module, workdir, incdir,
675
                expect_errors, annotate):
676 677 678
        expected_errors = errors = ()
        if expect_errors:
            expected_errors = self.split_source_and_output(
679 680
                test_directory, module, workdir)
            test_directory = workdir
681

682 683 684 685
        if WITH_CYTHON:
            old_stderr = sys.stderr
            try:
                sys.stderr = ErrorWriter()
686
                self.run_cython(test_directory, module, workdir, incdir, annotate)
687 688 689
                errors = sys.stderr.geterrors()
            finally:
                sys.stderr = old_stderr
690 691

        if errors or expected_errors:
692 693 694 695 696 697 698 699 700 701 702 703
            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:
                print("\n=== Expected errors: ===")
                print('\n'.join(expected_errors))
Stefan Behnel's avatar
Py3 fix  
Stefan Behnel committed
704
                print("\n\n=== Got errors: ===")
705 706 707
                print('\n'.join(errors))
                print('\n')
                raise
708
        else:
Stefan Behnel's avatar
Stefan Behnel committed
709
            if not self.cython_only:
710
                self.run_distutils(test_directory, module, workdir, incdir)
711 712

class CythonRunTestCase(CythonCompileTestCase):
713
    def shortDescription(self):
Stefan Behnel's avatar
Stefan Behnel committed
714 715 716 717
        if self.cython_only:
            return CythonCompileTestCase.shortDescription(self)
        else:
            return "compiling (%s) and running %s" % (self.language, self.module)
718 719

    def run(self, result=None):
720 721
        if result is None:
            result = self.defaultTestResult()
Stefan Behnel's avatar
Stefan Behnel committed
722
        result.startTest(self)
723
        try:
Stefan Behnel's avatar
Stefan Behnel committed
724
            self.setUp()
725
            try:
726
                self.success = False
727
                self.runCompileTest()
728
                failures, errors = len(result.failures), len(result.errors)
729
                self.run_tests(result)
730 731 732
                if failures == len(result.failures) and errors == len(result.errors):
                    # No new errors...
                    self.success = True
733 734
            finally:
                check_thread_termination()
735 736 737
        except Exception:
            result.addError(self, sys.exc_info())
            result.stopTest(self)
738 739 740 741
        try:
            self.tearDown()
        except Exception:
            pass
742

743 744 745 746
    def run_tests(self, result):
        if not self.cython_only:
            self.run_doctests(self.module, result)

747
    def run_doctests(self, module_name, result):
748 749 750 751 752 753 754
        def run_test(result):
            tests = doctest.DocTestSuite(module_name)
            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
755 756
    if not fork or sys.version_info[0] >= 3 or not hasattr(os, 'fork'):
        run_func(result)
757 758 759
        gc.collect()
        return

760
    module_name = test_name.split()[-1]
761 762 763 764 765 766
    # 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
767
        try:
768 769
            try:
                tests = None
770
                try:
771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786
                    partial_result = PartialTestResult(result)
                    run_func(partial_result)
                    gc.collect()
                except Exception:
                    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())
                    result_code = 1
                output = open(result_file, 'wb')
                pickle.dump(partial_result.data(), output)
            except:
                traceback.print_exc()
787
        finally:
788
            try: output.close()
789
            except: pass
790 791 792 793
            os._exit(result_code)

    try:
        cid, result_code = os.waitpid(child_id, 0)
Mark Florisson's avatar
Mark Florisson committed
794
        module_name = test_name.split()[-1]
795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
        # 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))
        result_code = result_code >> 8
        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
814

815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839
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]
840
                check_thread_termination()
841 842 843 844 845 846 847
        except Exception:
            result.addError(self, sys.exc_info())
            result.stopTest(self)
        try:
            self.tearDown()
        except Exception:
            pass
848

849 850 851 852
is_private_field = re.compile('^_[^_]').match

class _FakeClass(object):
    def __init__(self, **kwargs):
853
        self._shortDescription = kwargs.get('module_name')
854
        self.__dict__.update(kwargs)
855 856
    def shortDescription(self):
        return self._shortDescription
857

858 859 860 861 862 863
try: # Py2.7+ and Py3.2+
    from unittest.runner import _TextTestResult
except ImportError:
    from unittest import _TextTestResult

class PartialTestResult(_TextTestResult):
864
    def __init__(self, base_result):
865
        _TextTestResult.__init__(
866 867 868
            self, self._StringIO(), True,
            base_result.dots + base_result.showAll*2)

869 870 871 872 873 874
    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
875
                elif attr_name != '_shortDescription':
876 877
                    setattr(test_case, attr_name, None)

878
    def data(self):
879 880
        self.strip_error_results(self.failures)
        self.strip_error_results(self.errors)
881 882 883 884 885 886 887
        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
888
        failures, errors, tests_run, output = data
889 890 891 892 893 894 895 896 897 898 899 900 901
        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)


902
class CythonUnitTestCase(CythonRunTestCase):
903
    def shortDescription(self):
Stefan Behnel's avatar
Stefan Behnel committed
904
        return "compiling (%s) tests in %s" % (self.language, self.module)
905

906 907 908 909 910
    def run_tests(self, result):
        unittest.defaultTestLoader.loadTestsFromName(self.module).run(result)


class CythonPyregrTestCase(CythonRunTestCase):
911 912 913 914
    def setUp(self):
        CythonRunTestCase.setUp(self)
        from Cython.Compiler import Options
        Options.error_on_unknown_names = False
915 916 917
        Options.directive_defaults.update(dict(
            binding=True, always_allow_keywords=True,
            set_initial_path="SOURCEFILE"))
918

919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938
    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):
        self.run_doctests(module, result)

    def run_tests(self, result):
939
        try:
940
            from test import support
Vitja Makarov's avatar
Vitja Makarov committed
941 942
        except ImportError: # Python2.x
            from test import test_support as support
943

944 945 946 947 948
        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)
949

950 951
            support.run_unittest = run_unittest
            support.run_doctest = run_doctest
952

953 954 955 956 957 958 959 960
            try:
                module = __import__(self.module)
                if hasattr(module, 'test_main'):
                    module.test_main()
            except (unittest.SkipTest, support.ResourceDenied):
                result.addSkip(self, 'ok')

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

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

964
def collect_unittests(path, module_prefix, suite, selectors, exclude_selectors):
965 966 967 968 969 970 971
    def file_matches(filename):
        return filename.startswith("Test") and filename.endswith(".py")

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

    loader = unittest.TestLoader()
972

973 974 975
    if include_debugger:
        skipped_dirs = []
    else:
976
        skipped_dirs = ['Cython' + os.path.sep + 'Debugger' + os.path.sep]
977

978
    for dirpath, dirnames, filenames in os.walk(path):
979 980 981 982 983 984 985 986 987
        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
988 989 990 991 992
        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")]
993
                    modulename = module_prefix + filepath[len(path)+1:].replace(os.path.sep, '.')
994 995
                    if not [ 1 for match in selectors if match(modulename) ]:
                        continue
996 997
                    if [ 1 for match in exclude_selectors if match(modulename) ]:
                        continue
998 999 1000
                    module = __import__(modulename)
                    for x in modulename.split('.')[1:]:
                        module = getattr(module, x)
Robert Bradshaw's avatar
Robert Bradshaw committed
1001
                    suite.addTests([loader.loadTestsFromModule(module)])
1002

1003 1004


1005
def collect_doctests(path, module_prefix, suite, selectors, exclude_selectors):
1006
    def package_matches(dirname):
1007 1008
        if dirname == 'Debugger' and not include_debugger:
            return False
1009 1010
        return dirname not in ("Mac", "Distutils", "Plex")
    def file_matches(filename):
Mark Florisson's avatar
Tests!  
Mark Florisson committed
1011
        filename, ext = os.path.splitext(filename)
1012
        blacklist = ['libcython', 'libpython', 'test_libcython_in_gdb',
1013
                     'TestLibCython']
Mark Florisson's avatar
Tests!  
Mark Florisson committed
1014 1015 1016 1017 1018
        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
1019
    import doctest
1020
    for dirpath, dirnames, filenames in os.walk(path):
Robert Bradshaw's avatar
Robert Bradshaw committed
1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
        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
1033 1034
                if [ 1 for match in exclude_selectors if match(modulename) ]:
                    continue
Robert Bradshaw's avatar
Robert Bradshaw committed
1035 1036 1037
                if 'in_gdb' in modulename:
                    # These should only be imported from gdb.
                    continue
Robert Bradshaw's avatar
Robert Bradshaw committed
1038 1039 1040 1041 1042 1043 1044 1045
                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
1046

1047 1048 1049 1050 1051 1052

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

1055
    def __init__(self, treefile, workdir, cleanup_workdir=True):
1056
        self.name = os.path.splitext(os.path.basename(treefile))[0]
1057
        self.treefile = treefile
1058
        self.workdir = os.path.join(workdir, self.name)
1059
        self.cleanup_workdir = cleanup_workdir
1060 1061 1062
        cython_syspath = [self.cython_root]
        for path in sys.path:
            if path.startswith(self.cython_root) and path not in cython_syspath:
1063 1064
                # Py3 installation and refnanny build prepend their
                # fixed paths to sys.path => prefer that over the
1065 1066 1067
                # generic one (cython_root itself goes last)
                cython_syspath.append(path)
        self.cython_syspath = os.pathsep.join(cython_syspath[::-1])
1068 1069 1070
        unittest.TestCase.__init__(self)

    def shortDescription(self):
1071
        return "End-to-end %s" % self.name
1072 1073 1074

    def setUp(self):
        from Cython.TestUtils import unpack_source_tree
1075
        _, self.commands = unpack_source_tree(self.treefile, self.workdir)
1076 1077 1078 1079 1080 1081 1082
        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:
1083 1084 1085 1086 1087 1088 1089
            for trial in range(5):
                try:
                    shutil.rmtree(self.workdir)
                except OSError:
                    time.sleep(0.1)
                else:
                    break
1090
        os.chdir(self.old_dir)
1091

1092 1093 1094 1095 1096 1097
    def _try_decode(self, content):
        try:
            return content.decode()
        except UnicodeDecodeError:
            return content.decode('iso-8859-1')

1098
    def runTest(self):
1099
        self.success = False
1100
        commands = (self.commands
Robert Bradshaw's avatar
Robert Bradshaw committed
1101
            .replace("CYTHON", "PYTHON %s" % os.path.join(self.cython_root, 'cython.py'))
1102
            .replace("PYTHON", sys.executable))
1103 1104
        old_path = os.environ.get('PYTHONPATH')
        os.environ['PYTHONPATH'] = self.cython_syspath + os.pathsep + (old_path or '')
1105
        try:
Robert Bradshaw's avatar
Robert Bradshaw committed
1106
            for command in commands.split('\n'):
Robert Bradshaw's avatar
Robert Bradshaw committed
1107 1108 1109 1110 1111 1112 1113 1114
                p = subprocess.Popen(commands,
                                     stderr=subprocess.PIPE,
                                     stdout=subprocess.PIPE,
                                     shell=True)
                out, err = p.communicate()
                res = p.returncode
                if res != 0:
                    print(command)
1115 1116
                    print(self._try_decode(out))
                    print(self._try_decode(err))
Robert Bradshaw's avatar
Robert Bradshaw committed
1117
                self.assertEqual(0, res, "non-zero exit status")
1118
        finally:
1119 1120 1121 1122
            if old_path:
                os.environ['PYTHONPATH'] = old_path
            else:
                del os.environ['PYTHONPATH']
1123
        self.success = True
1124 1125


1126 1127 1128 1129
# TODO: Support cython_freeze needed here as well.
# TODO: Windows support.

class EmbedTest(unittest.TestCase):
1130

1131
    working_dir = "Demos/embed"
1132

1133 1134 1135
    def setUp(self):
        self.old_dir = os.getcwd()
        os.chdir(self.working_dir)
1136
        os.system(
1137
            "make PYTHON='%s' clean > /dev/null" % sys.executable)
1138

1139 1140
    def tearDown(self):
        try:
1141 1142
            os.system(
                "make PYTHON='%s' clean > /dev/null" % sys.executable)
1143 1144 1145
        except:
            pass
        os.chdir(self.old_dir)
1146

1147
    def test_embed(self):
1148
        from distutils import sysconfig
1149
        libname = sysconfig.get_config_var('LIBRARY')
1150
        libdir = sysconfig.get_config_var('LIBDIR')
1151 1152 1153 1154 1155 1156 1157
        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')
1158
        cython = 'cython.py'
Stefan Behnel's avatar
Stefan Behnel committed
1159
        if sys.version_info[0] >=3 and CY3_DIR:
1160 1161
            cython = os.path.join(CY3_DIR, cython)
        cython = os.path.abspath(os.path.join('..', '..', cython))
1162
        self.assert_(os.system(
1163
            "make PYTHON='%s' CYTHON='%s' LIBDIR1='%s' test > make.output" % (sys.executable, cython, libdir)) == 0)
1164 1165 1166 1167
        try:
            os.remove('make.output')
        except OSError:
            pass
1168

1169 1170
class MissingDependencyExcluder:
    def __init__(self, deps):
1171
        # deps: { matcher func : module name }
1172
        self.exclude_matchers = []
1173
        for matcher, mod in deps.items():
1174 1175 1176
            try:
                __import__(mod)
            except ImportError:
Robert Bradshaw's avatar
Robert Bradshaw committed
1177
                self.exclude_matchers.append(string_selector(matcher))
1178
        self.tests_missing_deps = []
Robert Bradshaw's avatar
Robert Bradshaw committed
1179
    def __call__(self, testname, tags=None):
1180
        for matcher in self.exclude_matchers:
Robert Bradshaw's avatar
Robert Bradshaw committed
1181
            if matcher(testname, tags):
1182 1183 1184 1185
                self.tests_missing_deps.append(testname)
                return True
        return False

1186 1187 1188 1189 1190
class VersionDependencyExcluder:
    def __init__(self, deps):
        # deps: { version : matcher func }
        from sys import version_info
        self.exclude_matchers = []
1191 1192
        for ver, (compare, matcher) in deps.items():
            if compare(version_info, ver):
1193 1194
                self.exclude_matchers.append(matcher)
        self.tests_missing_deps = []
Robert Bradshaw's avatar
Robert Bradshaw committed
1195
    def __call__(self, testname, tags=None):
1196 1197 1198 1199 1200 1201
        for matcher in self.exclude_matchers:
            if matcher(testname):
                self.tests_missing_deps.append(testname)
                return True
        return False

1202 1203 1204 1205
class FileListExcluder:

    def __init__(self, list_file):
        self.excludes = {}
1206 1207 1208 1209 1210 1211 1212 1213
        f = open(list_file)
        try:
            for line in f.readlines():
                line = line.strip()
                if line and line[0] != '#':
                    self.excludes[line.split()[0]] = True
        finally:
            f.close()
1214

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

Robert Bradshaw's avatar
Robert Bradshaw committed
1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232
class TagsSelector:

    def __init__(self, tag, value):
        self.tag = tag
        self.value = value
    
    def __call__(self, testname, tags=None):
        if tags is None:
            return False
        else:
            return self.value in tags[self.tag]

class RegExSelector:
    
    def __init__(self, pattern_string):
1233 1234 1235 1236 1237
        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
1238 1239 1240 1241 1242 1243 1244 1245 1246 1247

    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:])
1248 1249 1250

class ShardExcludeSelector:
    # This is an exclude selector so it can override the (include) selectors.
Robert Bradshaw's avatar
Robert Bradshaw committed
1251 1252
    # It may not provide uniform distribution (in time or count), but is a
    # determanistic partition of the tests which is important.
1253 1254 1255 1256 1257 1258
    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
Robert Bradshaw's avatar
Robert Bradshaw committed
1259 1260
        

1261 1262 1263 1264 1265 1266 1267 1268 1269 1270
def refactor_for_py3(distdir, cy3_dir):
    # need to convert Cython sources first
    import lib2to3.refactor
    from distutils.util import copydir_run_2to3
    fixers = [ fix for fix in lib2to3.refactor.get_fixers_from_package("lib2to3.fixes")
               if fix.split('fix_')[-1] not in ('next',)
               ]
    if not os.path.exists(cy3_dir):
        os.makedirs(cy3_dir)
    import distutils.log as dlog
1271
    dlog.set_threshold(dlog.INFO)
1272 1273 1274 1275 1276 1277
    copydir_run_2to3(distdir, cy3_dir, fixer_names=fixers,
                     template = '''
                     global-exclude *
                     graft Cython
                     recursive-exclude Cython *
                     recursive-include Cython *.py *.pyx *.pxd
Mark Florisson's avatar
Mark Florisson committed
1278
                     recursive-include Cython/Debugger/Tests *
1279
                     recursive-include Cython/Utility *
1280 1281
                     recursive-exclude pyximport test
                     include pyximport/*.py
1282
                     include runtests.py
1283
                     include cython.py
1284 1285 1286
                     ''')
    sys.path.insert(0, cy3_dir)

1287 1288 1289 1290
    for keep_2x_file in KEEP_2X_FILES:
        destfile = os.path.join(cy3_dir, keep_2x_file)
        shutil.copy(keep_2x_file, destfile)

1291 1292
class PendingThreadsError(RuntimeError):
    pass
1293

1294 1295 1296
threads_seen = []

def check_thread_termination(ignore_seen=True):
1297 1298 1299 1300 1301 1302 1303 1304 1305
    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():
1306 1307 1308
            if not ignore_seen:
                blocking_threads.append(t)
                continue
1309 1310 1311 1312 1313 1314
            for seen in threads_seen:
                if t is seen:
                    break
            else:
                threads_seen.append(t)
                blocking_threads.append(t)
1315 1316 1317 1318 1319
    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))
1320
    raise PendingThreadsError("left-over threads found after running test")
1321

1322 1323
def subprocess_output(cmd):
    try:
Mark Florisson's avatar
Mark Florisson committed
1324 1325
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        return p.communicate()[0].decode('UTF-8')
1326 1327 1328 1329 1330 1331 1332 1333
    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
1334
        old_dir = os.getcwd()
1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347
        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

1348 1349 1350 1351 1352 1353 1354 1355
_orig_stdout, _orig_stderr = sys.stdout, sys.stderr
def flush_and_terminate(status):
    try:
        _orig_stdout.flush()
        _orig_stderr.flush()
    finally:
        os._exit(status)

1356
def main():
1357

1358
    global DISTDIR, WITH_CYTHON
1359 1360
    DISTDIR = os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]))

1361 1362 1363
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option("--no-cleanup", dest="cleanup_workdir",
Stefan Behnel's avatar
Stefan Behnel committed
1364 1365
                      action="store_false", default=True,
                      help="do not delete the generated C files (allows passing --no-cython on next run)")
1366 1367 1368
    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)")
1369 1370 1371
    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
1372 1373 1374
    parser.add_option("--no-cython", dest="with_cython",
                      action="store_false", default=True,
                      help="do not run the Cython compiler, only the C compiler")
1375 1376
    parser.add_option("--compiler", dest="compiler", default=None,
                      help="C compiler type")
1377 1378 1379
    backend_list = ','.join(BACKENDS)
    parser.add_option("--backends", dest="backends", default=backend_list,
                      help="select backends to test (default: %s)" % backend_list)
Stefan Behnel's avatar
Stefan Behnel committed
1380 1381
    parser.add_option("--no-c", dest="use_c",
                      action="store_false", default=True,
1382
                      help="do not test C compilation backend")
Stefan Behnel's avatar
Stefan Behnel committed
1383 1384
    parser.add_option("--no-cpp", dest="use_cpp",
                      action="store_false", default=True,
1385
                      help="do not test C++ compilation backend")
1386 1387 1388
    parser.add_option("--no-unit", dest="unittests",
                      action="store_false", default=True,
                      help="do not run the unit tests")
1389 1390 1391
    parser.add_option("--no-doctest", dest="doctests",
                      action="store_false", default=True,
                      help="do not run the doctests")
1392 1393 1394
    parser.add_option("--no-file", dest="filetests",
                      action="store_false", default=True,
                      help="do not run the file based tests")
1395 1396
    parser.add_option("--no-pyregr", dest="pyregr",
                      action="store_false", default=True,
1397
                      help="do not run the regression tests of CPython in tests/pyregr/")
Stefan Behnel's avatar
Stefan Behnel committed
1398
    parser.add_option("--cython-only", dest="cython_only",
1399 1400
                      action="store_true", default=False,
                      help="only compile pyx to c, do not run C compiler or run the tests")
1401
    parser.add_option("--no-refnanny", dest="with_refnanny",
Stefan Behnel's avatar
Stefan Behnel committed
1402
                      action="store_false", default=True,
1403
                      help="do not regression test reference counting")
1404 1405 1406
    parser.add_option("--no-fork", dest="fork",
                      action="store_false", default=True,
                      help="do not fork to run tests")
1407 1408 1409
    parser.add_option("--sys-pyregr", dest="system_pyregr",
                      action="store_true", default=False,
                      help="run the regression tests of the CPython installation")
1410 1411 1412
    parser.add_option("-x", "--exclude", dest="exclude",
                      action="append", metavar="PATTERN",
                      help="exclude tests matching the PATTERN")
1413 1414 1415 1416 1417 1418
    parser.add_option("--shard_count", dest="shard_count", metavar="N",
                      type=int, default=1,
                      help="shard this run into several parallel runs")
    parser.add_option("--shard_num", dest="shard_num", metavar="K",
                      type=int, default=-1,
                      help="test only this single shard")
1419
    parser.add_option("-C", "--coverage", dest="coverage",
Stefan Behnel's avatar
Stefan Behnel committed
1420 1421
                      action="store_true", default=False,
                      help="collect source coverage data for the Compiler")
1422 1423 1424
    parser.add_option("--coverage-xml", dest="coverage_xml",
                      action="store_true", default=False,
                      help="collect source coverage data for the Compiler in XML format")
Stefan Behnel's avatar
Stefan Behnel committed
1425 1426 1427
    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
1428
    parser.add_option("-A", "--annotate", dest="annotate_source",
1429
                      action="store_true", default=True,
Stefan Behnel's avatar
Stefan Behnel committed
1430
                      help="generate annotated HTML versions of the test source files")
1431 1432 1433
    parser.add_option("--no-annotate", dest="annotate_source",
                      action="store_false",
                      help="do not generate annotated HTML versions of the test source files")
1434
    parser.add_option("-v", "--verbose", dest="verbosity",
Stefan Behnel's avatar
Stefan Behnel committed
1435 1436
                      action="count", default=0,
                      help="display test progress, pass twice to print test names")
1437 1438
    parser.add_option("-T", "--ticket", dest="tickets",
                      action="append",
1439
                      help="a bug ticket number to run the respective test in 'tests/*'")
1440 1441 1442
    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)'")
1443 1444
    parser.add_option("--xml-output", dest="xml_output_dir", metavar="DIR",
                      help="write test results in XML to directory DIR")
1445 1446 1447
    parser.add_option("--exit-ok", dest="exit_ok", default=False,
                      action="store_true",
                      help="exit without error code even on test failures")
1448 1449 1450 1451
    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")
1452 1453
    parser.add_option("--cython-dir", dest="cython_dir", default=os.getcwd(),
                      help="Cython installation directory (default: use local source version)")
1454 1455
    parser.add_option("--debug", dest="for_debugging", default=False, action="store_true",
                      help="configure for easier use with a debugger (e.g. gdb)")
1456 1457
    parser.add_option("--pyximport-py", dest="pyximport_py", default=False, action="store_true",
                      help="use pyximport to automatically compile imported .pyx and .py files")
1458 1459
    parser.add_option("--watermark", dest="watermark", default=None,
                      help="deterministic generated by string")
1460 1461 1462

    options, cmd_args = parser.parse_args()

1463
    WORKDIR = os.path.abspath(options.work_dir)
1464
    
1465 1466
    if sys.version_info[0] >= 3:
        options.doctests = False
1467
        if options.with_cython:
1468
            sys.path.insert(0, options.cython_dir)
1469 1470 1471 1472
            try:
                # try if Cython is installed in a Py3 version
                import Cython.Compiler.Main
            except Exception:
Stefan Behnel's avatar
comment  
Stefan Behnel committed
1473 1474
                # back out anything the import process loaded, then
                # 2to3 the Cython sources to make them re-importable
1475
                cy_modules = [ name for name in sys.modules
Stefan Behnel's avatar
Stefan Behnel committed
1476
                               if name == 'Cython' or name.startswith('Cython.') ]
1477 1478 1479
                for name in cy_modules:
                    del sys.modules[name]
                # hasn't been refactored yet - do it now
1480 1481
                global CY3_DIR
                CY3_DIR = cy3_dir = os.path.join(WORKDIR, 'Cy3')
1482 1483 1484 1485 1486 1487
                if sys.version_info >= (3,1):
                    refactor_for_py3(DISTDIR, cy3_dir)
                elif os.path.isdir(cy3_dir):
                    sys.path.insert(0, cy3_dir)
                else:
                    options.with_cython = False
1488

1489 1490 1491 1492
    if options.watermark:
        import Cython.Compiler.Version
        Cython.Compiler.Version.watermark = options.watermark

1493
    WITH_CYTHON = options.with_cython
1494

1495
    coverage = None
Stefan Behnel's avatar
Stefan Behnel committed
1496
    if options.coverage or options.coverage_xml or options.coverage_html:
1497
        if options.shard_count <= 1 and options.shard_num < 0:
1498 1499 1500
            if not WITH_CYTHON:
                options.coverage = options.coverage_xml = options.coverage_html = False
            else:
1501
                print("Enabling coverage analysis")
1502
                from coverage import coverage as _coverage
1503
                coverage = _coverage(branch=True, omit=['Test*'])
1504 1505
                coverage.erase()
                coverage.start()
1506

1507
    if WITH_CYTHON:
1508
        global CompilationOptions, pyrex_default_options, cython_compile
1509 1510 1511 1512
        from Cython.Compiler.Main import \
            CompilationOptions, \
            default_options as pyrex_default_options, \
            compile as cython_compile
1513 1514
        from Cython.Compiler import Errors
        Errors.LEVEL = 0 # show all warnings
Stefan Behnel's avatar
cleanup  
Stefan Behnel committed
1515
        from Cython.Compiler import Options
1516
        Options.generate_cleanup_code = 3   # complete cleanup code
Stefan Behnel's avatar
cleanup  
Stefan Behnel committed
1517 1518
        from Cython.Compiler import DebugFlags
        DebugFlags.debug_temp_code_comments = 1
1519

1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532
    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
1533
            print("Errors for shards %s" % ", ".join([str(e) for e in errors]))
1534 1535 1536 1537
            return_code = 1
        else:
            return_code = 0
    else:
1538
        _, return_code = runtests(options, cmd_args, coverage)
1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554
    print("ALL DONE")


    try:
        check_thread_termination(ignore_seen=False)
        sys.exit(return_code)
    except PendingThreadsError:
        # normal program exit won't kill the threads, do it the hard way here
        flush_and_terminate(return_code)


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

1555
def runtests(options, cmd_args, coverage=None):
1556

1557 1558 1559 1560 1561 1562 1563
    WITH_CYTHON = options.with_cython
    ROOTDIR = os.path.abspath(options.root_dir)
    WORKDIR = os.path.abspath(options.work_dir)

    if options.shard_num > -1:
        WORKDIR = os.path.join(WORKDIR, str(options.shard_num))
    
1564
    # RUN ALL TESTS!
1565
    UNITTEST_MODULE = "Cython"
1566
    UNITTEST_ROOT = os.path.join(os.path.dirname(__file__), UNITTEST_MODULE)
1567 1568
    if WITH_CYTHON:
        if os.path.exists(WORKDIR):
1569
            for path in os.listdir(WORKDIR):
1570
                if path in ("support", "Cy3"): continue
1571
                shutil.rmtree(os.path.join(WORKDIR, path), ignore_errors=True)
1572 1573
    if not os.path.exists(WORKDIR):
        os.makedirs(WORKDIR)
1574

1575 1576 1577 1578 1579 1580 1581
    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")
1582

1583 1584 1585 1586
    if options.for_debugging:
        options.cleanup_workdir = False
        options.cleanup_sharedlibs = False
        options.fork = False
1587
        if WITH_CYTHON and include_debugger:
1588 1589 1590
            from Cython.Compiler.Main import default_options as compiler_default_options
            compiler_default_options['gdb_debug'] = True
            compiler_default_options['output_dir'] = os.getcwd()
1591

1592 1593 1594 1595 1596
    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"))
1597
        sys.path.insert(0, os.path.split(libpath)[0])
1598
        CFLAGS.append("-DCYTHON_REFNANNY=1")
1599

Stefan Behnel's avatar
cleanup  
Stefan Behnel committed
1600 1601 1602 1603 1604
    if options.xml_output_dir and options.fork:
        # doesn't currently work together
        sys.stderr.write("Disabling forked testing to support XML test output\n")
        options.fork = False

1605 1606 1607
    if WITH_CYTHON and options.language_level == 3:
        sys.stderr.write("Using Cython language level 3.\n")

1608
    test_bugs = False
Stefan Behnel's avatar
Stefan Behnel committed
1609 1610 1611
    if options.tickets:
        for ticket_number in options.tickets:
            test_bugs = True
Robert Bradshaw's avatar
Robert Bradshaw committed
1612
            cmd_args.append('ticket:%s' % ticket_number)
1613 1614 1615 1616
    if not test_bugs:
        for selector in cmd_args:
            if selector.startswith('bugs'):
                test_bugs = True
1617

Robert Bradshaw's avatar
Robert Bradshaw committed
1618
    selectors = [ string_selector(r) for r in cmd_args ]
1619
    if not selectors:
Robert Bradshaw's avatar
Robert Bradshaw committed
1620
        selectors = [ lambda x, tags=None: True ]
1621

1622 1623 1624
    # Chech which external modules are not present and exclude tests
    # which depends on them (by prefix)

1625 1626
    missing_dep_excluder = MissingDependencyExcluder(EXT_DEP_MODULES)
    version_dep_excluder = VersionDependencyExcluder(VER_DEP_MODULES)
Robert Bradshaw's avatar
cleanup  
Robert Bradshaw committed
1627
    exclude_selectors = [missing_dep_excluder, version_dep_excluder] # want to print msg at exit
1628

1629
    if options.exclude:
Robert Bradshaw's avatar
Robert Bradshaw committed
1630
        exclude_selectors += [ string_selector(r) for r in options.exclude ]
1631

1632 1633 1634
    if options.shard_num > -1:
        exclude_selectors.append(ShardExcludeSelector(options.shard_num, options.shard_count))

1635
    if not test_bugs:
1636
        exclude_selectors += [ FileListExcluder(os.path.join(ROOTDIR, "bugs.txt")) ]
1637

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

1641 1642 1643
    global COMPILER
    if options.compiler:
        COMPILER = options.compiler
1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656

    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)
1657 1658
    if options.shard_num <= 0:
        sys.stderr.write("Backends: %s\n" % ','.join(backends))
1659 1660 1661
    languages = backends

    sys.stderr.write("\n")
Stefan Behnel's avatar
Stefan Behnel committed
1662

1663 1664 1665
    test_suite = unittest.TestSuite()

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

1668
    if options.doctests:
1669
        collect_doctests(UNITTEST_ROOT, UNITTEST_MODULE + ".", test_suite, selectors, exclude_selectors)
1670

Stefan Behnel's avatar
Stefan Behnel committed
1671
    if options.filetests and languages:
1672
        filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors,
1673
                                options.annotate_source, options.cleanup_workdir,
1674 1675
                                options.cleanup_sharedlibs, options.cleanup_failures,
                                options.pyregr,
1676
                                options.cython_only, languages, test_bugs,
1677
                                options.fork, options.language_level)
1678 1679
        test_suite.addTest(filetests.build_suite())

Stefan Behnel's avatar
Stefan Behnel committed
1680
    if options.system_pyregr and languages:
1681 1682 1683 1684
        sys_pyregr_dir = os.path.join(sys.prefix, 'lib', 'python'+sys.version[:3], 'test')
        if os.path.isdir(sys_pyregr_dir):
            filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors,
                                    options.annotate_source, options.cleanup_workdir,
1685 1686
                                    options.cleanup_sharedlibs, options.cleanup_failures,
                                    True,
1687
                                    options.cython_only, languages, test_bugs,
1688
                                    options.fork, sys.version_info[0])
1689 1690
            sys.stderr.write("Including CPython regression tests in %s\n" % sys_pyregr_dir)
            test_suite.addTest(filetests.handle_directory(sys_pyregr_dir, 'pyregr'))
1691

Stefan Behnel's avatar
Stefan Behnel committed
1692
    if options.xml_output_dir:
1693
        from Cython.Tests.xmlrunner import XMLTestRunner
Stefan Behnel's avatar
Stefan Behnel committed
1694 1695
        test_runner = XMLTestRunner(output=options.xml_output_dir,
                                    verbose=options.verbosity > 0)
1696 1697 1698
    else:
        test_runner = unittest.TextTestRunner(verbosity=options.verbosity)

1699
    if options.pyximport_py:
1700
        from pyximport import pyximport
1701
        pyximport.install(pyimport=True, build_dir=os.path.join(WORKDIR, '_pyximport'),
1702
                          load_py_module_on_import_failure=True, inplace=True)
1703

1704
    result = test_runner.run(test_suite)
1705

1706
    if coverage is not None:
1707
        coverage.stop()
1708
        ignored_modules = ('Options', 'Version', 'DebugFlags', 'CmdLine')
1709 1710
        modules = [ module for name, module in sys.modules.items()
                    if module is not None and
1711
                    name.startswith('Cython.Compiler.') and
1712
                    name[len('Cython.Compiler.'):] not in ignored_modules ]
1713 1714 1715
        if options.coverage:
            coverage.report(modules, show_missing=0)
        if options.coverage_xml:
Stefan Behnel's avatar
Stefan Behnel committed
1716
            coverage.xml_report(modules, outfile="coverage-report.xml")
Stefan Behnel's avatar
Stefan Behnel committed
1717 1718
        if options.coverage_html:
            coverage.html_report(modules, directory="coverage-report-html")
1719 1720 1721 1722 1723

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

1725
    if options.with_refnanny:
Dag Sverre Seljebotn's avatar
Cleanup  
Dag Sverre Seljebotn committed
1726
        import refnanny
1727
        sys.stderr.write("\n".join([repr(x) for x in refnanny.reflog]))
1728

1729
    if options.exit_ok:
1730
        return options.shard_num, 0
1731
    else:
1732
        return options.shard_num, not result.wasSuccessful()
1733 1734 1735 1736 1737


if __name__ == '__main__':
    try:
        main()
Stefan Behnel's avatar
Stefan Behnel committed
1738 1739
    except SystemExit: # <= Py2.4 ...
        raise
1740 1741 1742 1743 1744 1745
    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
1746
            flush_and_terminate(1)