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

3 4 5
import os
import sys
import re
6
import gc
7 8
import codecs
import shutil
9
import time
10 11
import unittest
import doctest
12
import operator
13
import tempfile
14
import traceback
15 16 17 18
try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO
19 20 21 22 23 24

try:
    import cPickle as pickle
except ImportError:
    import pickle

25 26 27 28 29
try:
    from io import open as io_open
except ImportError:
    from codecs import open as io_open

30 31 32 33 34
try:
    import threading
except ImportError: # No threads, no problems
    threading = None

Robert Bradshaw's avatar
Robert Bradshaw committed
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
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)

51
WITH_CYTHON = True
52
CY3_DIR = None
53 54 55 56 57 58

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

Robert Bradshaw's avatar
Robert Bradshaw committed
59 60 61 62 63 64 65 66 67 68 69 70
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)

71
EXT_DEP_MODULES = {
Robert Bradshaw's avatar
Robert Bradshaw committed
72 73 74
    'numpy' : 'tag:numpy',
    'pstats' : 'tag:pstats',
    'posix' : 'tag:posix',
75 76
}

77 78 79 80
def get_numpy_include_dirs():
    import numpy
    return [numpy.get_include()]

Robert Bradshaw's avatar
Robert Bradshaw committed
81
# TODO: use tags
82 83 84 85 86
EXT_DEP_INCLUDES = [
    # test name matcher , callable returning list
    (re.compile('numpy_.*').match, get_numpy_include_dirs),
]

Robert Bradshaw's avatar
Robert Bradshaw committed
87
# TODO: use tags
88
VER_DEP_MODULES = {
89
    # tests are excluded if 'CurrentPythonVersion OP VersionTuple', i.e.
Stefan Behnel's avatar
Stefan Behnel committed
90 91 92 93
    # (2,4) : (operator.lt, ...) excludes ... when PyVer < 2.4.x
    (2,4) : (operator.lt, lambda x: x in ['run.extern_builtins_T258',
                                          'run.builtin_sorted'
                                          ]),
94 95
    (2,5) : (operator.lt, lambda x: x in ['run.any',
                                          'run.all',
Haoyu Bai's avatar
Haoyu Bai committed
96
                                          'run.relativeimport_T542',
97
                                          'run.relativeimport_star_T542',
98
                                          ]),
99 100
    (2,6) : (operator.lt, lambda x: x in ['run.print_function',
                                          'run.cython3',
Stefan Behnel's avatar
comment  
Stefan Behnel committed
101
                                          'run.generators_py', # generators, with statement
102
                                          'run.pure_py', # decorators, with statement
103
                                          ]),
104 105 106 107 108
    # 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...
    (2,999): (operator.lt, lambda x: x in ['run.special_methods_T561_py3']),
109
    (3,): (operator.ge, lambda x: x in ['run.non_future_division',
Stefan Behnel's avatar
Stefan Behnel committed
110
                                        'compile.extsetslice',
111 112
                                        'compile.extdelslice',
                                        'run.special_methods_T561_py2']),
113 114
}

115 116 117 118 119 120 121 122
# 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'),
]

123
COMPILER = None
124
INCLUDE_DIRS = [ d for d in os.getenv('INCLUDE', '').split(os.pathsep) if d ]
125 126
CFLAGS = os.getenv('CFLAGS', '').split()

127 128 129 130 131 132 133 134 135 136
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
Robert Bradshaw committed
137 138
def parse_tags(filepath):
    tags = defaultdict(list)
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
    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
154 155
    return tags

Stefan Behnel's avatar
Stefan Behnel committed
156 157 158
parse_tags = memoize(parse_tags)


159 160 161 162
class build_ext(_build_ext):
    def build_extension(self, ext):
        if ext.language == 'c++':
            try:
163
                try: # Py2.7+ & Py3.2+
164 165 166 167
                    compiler_obj = self.compiler_obj
                except AttributeError:
                    compiler_obj = self.compiler
                compiler_obj.compiler_so.remove('-Wstrict-prototypes')
168 169 170
            except Exception:
                pass
        _build_ext.build_extension(self, ext)
171 172

class ErrorWriter(object):
173
    match_error = re.compile('(warning:)?(?:.*:)?\s*([-0-9]+)\s*:\s*([-0-9]+)\s*:\s*(.*)').match
174 175 176 177
    def __init__(self):
        self.output = []
        self.write = self.output.append

178
    def _collect(self, collect_errors, collect_warnings):
179
        s = ''.join(self.output)
180
        result = []
181 182 183
        for line in s.split('\n'):
            match = self.match_error(line)
            if match:
184 185 186
                is_warning, line, column, message = match.groups()
                if (is_warning and collect_warnings) or \
                        (not is_warning and collect_errors):
187 188
                    result.append( (int(line), int(column), message.strip()) )
        result.sort()
Stefan Behnel's avatar
Stefan Behnel committed
189
        return [ "%d:%d: %s" % values for values in result ]
190 191 192 193 194 195 196 197 198

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

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

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

200
class TestBuilder(object):
201
    def __init__(self, rootdir, workdir, selectors, exclude_selectors, annotate,
Stefan Behnel's avatar
Stefan Behnel committed
202
                 cleanup_workdir, cleanup_sharedlibs, with_pyregr, cython_only,
203
                 languages, test_bugs, fork, language_level):
204 205
        self.rootdir = rootdir
        self.workdir = workdir
206
        self.selectors = selectors
207
        self.exclude_selectors = exclude_selectors
208
        self.annotate = annotate
209
        self.cleanup_workdir = cleanup_workdir
210
        self.cleanup_sharedlibs = cleanup_sharedlibs
211
        self.with_pyregr = with_pyregr
Stefan Behnel's avatar
Stefan Behnel committed
212 213
        self.cython_only = cython_only
        self.languages = languages
214
        self.test_bugs = test_bugs
215
        self.fork = fork
216
        self.language_level = language_level
217 218 219

    def build_suite(self):
        suite = unittest.TestSuite()
220 221 222
        filenames = os.listdir(self.rootdir)
        filenames.sort()
        for filename in filenames:
223
            path = os.path.join(self.rootdir, filename)
Robert Bradshaw's avatar
cleanup  
Robert Bradshaw committed
224
            if os.path.isdir(path):
225 226
                if filename == 'pyregr' and not self.with_pyregr:
                    continue
227 228
                if filename == 'broken' and not self.test_bugs:
                    continue
229
                suite.addTest(
230
                    self.handle_directory(path, filename))
231 232
        if sys.platform not in ['win32']:
            # Non-Windows makefile.
233 234
            if [1 for selector in self.selectors if selector("embedded")] \
                and not [1 for selector in self.exclude_selectors if selector("embedded")]:
235
                suite.addTest(unittest.makeSuite(EmbedTest))
236 237
        return suite

238
    def handle_directory(self, path, context):
239 240 241 242
        workdir = os.path.join(self.workdir, context)
        if not os.path.exists(workdir):
            os.makedirs(workdir)

243
        suite = unittest.TestSuite()
244 245 246
        filenames = os.listdir(path)
        filenames.sort()
        for filename in filenames:
247 248 249
            filepath = os.path.join(path, filename)
            module, ext = os.path.splitext(filename)
            if ext not in ('.py', '.pyx', '.srctree'):
250
                continue
251 252 253
            if filename.startswith('.'):
                continue # certain emacs backup files
            tags = parse_tags(filepath)
Robert Bradshaw's avatar
Robert Bradshaw committed
254
            fqmodule = "%s.%s" % (context, module)
255
            if not [ 1 for match in self.selectors
Robert Bradshaw's avatar
Robert Bradshaw committed
256
                     if match(fqmodule, tags) ]:
257
                continue
258
            if self.exclude_selectors:
Robert Bradshaw's avatar
Robert Bradshaw committed
259 260
                if [1 for match in self.exclude_selectors 
                        if match(fqmodule, tags)]:
261
                    continue
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276

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

            if ext == '.srctree':
                suite.addTest(EndToEndTest(filepath, workdir, self.cleanup_workdir))
                continue

            # Choose the test suite.
            if mode == 'pyregr':
                if not filename.startswith('test_'):
                    continue
277
                test_class = CythonPyregrTestCase
278
            elif mode == 'run':
279
                if module.startswith("test_"):
Stefan Behnel's avatar
Stefan Behnel committed
280
                    test_class = CythonUnitTestCase
281
                else:
Stefan Behnel's avatar
Stefan Behnel committed
282
                    test_class = CythonRunTestCase
283
            else:
Stefan Behnel's avatar
Stefan Behnel committed
284
                test_class = CythonCompileTestCase
285

Stefan Behnel's avatar
Stefan Behnel committed
286
            for test in self.build_tests(test_class, path, workdir,
Robert Bradshaw's avatar
Robert Bradshaw committed
287
                                         module, mode == 'error', tags):
Stefan Behnel's avatar
Stefan Behnel committed
288
                suite.addTest(test)
289
            if mode == 'run' and ext == '.py':
290 291
                # additionally test file in real Python
                suite.addTest(PureDoctestTestCase(module, os.path.join(path, filename)))
292
                
293 294
        return suite

Robert Bradshaw's avatar
Robert Bradshaw committed
295
    def build_tests(self, test_class, path, workdir, module, expect_errors, tags):
Vitja Makarov's avatar
Vitja Makarov committed
296 297 298 299 300
        if 'werror' in tags['tags']:
            warning_errors = True
        else:
            warning_errors = False

301
        if expect_errors:
Robert Bradshaw's avatar
Robert Bradshaw committed
302
            if 'cpp' in tags['tag'] and 'cpp' in self.languages:
Robert Bradshaw's avatar
Robert Bradshaw committed
303 304 305
                languages = ['cpp']
            else:
                languages = self.languages[:1]
306 307
        else:
            languages = self.languages
Robert Bradshaw's avatar
Robert Bradshaw committed
308
        if 'cpp' in tags['tag'] and 'c' in languages:
309 310
            languages = list(languages)
            languages.remove('c')
Stefan Behnel's avatar
Stefan Behnel committed
311
        tests = [ self.build_test(test_class, path, workdir, module,
Vitja Makarov's avatar
Vitja Makarov committed
312
                                  language, expect_errors, warning_errors)
313
                  for language in languages ]
Stefan Behnel's avatar
Stefan Behnel committed
314 315 316
        return tests

    def build_test(self, test_class, path, workdir, module,
Vitja Makarov's avatar
Vitja Makarov committed
317
                   language, expect_errors, warning_errors):
Stefan Behnel's avatar
Stefan Behnel committed
318 319 320
        workdir = os.path.join(workdir, language)
        if not os.path.exists(workdir):
            os.makedirs(workdir)
Stefan Behnel's avatar
Stefan Behnel committed
321 322 323 324 325 326
        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,
327
                          cython_only=self.cython_only,
328
                          fork=self.fork,
Vitja Makarov's avatar
Vitja Makarov committed
329 330
                          language_level=self.language_level,
                          warning_errors=warning_errors)
Stefan Behnel's avatar
Stefan Behnel committed
331

332
class CythonCompileTestCase(unittest.TestCase):
333
    def __init__(self, test_directory, workdir, module, language='c',
334
                 expect_errors=False, annotate=False, cleanup_workdir=True,
335
                 cleanup_sharedlibs=True, cython_only=False, fork=True,
Vitja Makarov's avatar
Vitja Makarov committed
336
                 language_level=2, warning_errors=False):
337
        self.test_directory = test_directory
338 339
        self.workdir = workdir
        self.module = module
Stefan Behnel's avatar
Stefan Behnel committed
340
        self.language = language
341
        self.expect_errors = expect_errors
342
        self.annotate = annotate
343
        self.cleanup_workdir = cleanup_workdir
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
344
        self.cleanup_sharedlibs = cleanup_sharedlibs
Stefan Behnel's avatar
Stefan Behnel committed
345
        self.cython_only = cython_only
346
        self.fork = fork
347
        self.language_level = language_level
Vitja Makarov's avatar
Vitja Makarov committed
348
        self.warning_errors = warning_errors
349 350 351
        unittest.TestCase.__init__(self)

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

Stefan Behnel's avatar
Stefan Behnel committed
354
    def setUp(self):
Vitja Makarov's avatar
Vitja Makarov committed
355 356 357
        from Cython.Compiler import Options
        Options.warning_errors = self.warning_errors

Stefan Behnel's avatar
Stefan Behnel committed
358 359 360
        if self.workdir not in sys.path:
            sys.path.insert(0, self.workdir)

361
    def tearDown(self):
Vitja Makarov's avatar
Vitja Makarov committed
362 363 364
        from Cython.Compiler import Options
        Options.warning_errors = False

Stefan Behnel's avatar
Stefan Behnel committed
365 366 367 368 369 370 371 372
        try:
            sys.path.remove(self.workdir)
        except ValueError:
            pass
        try:
            del sys.modules[self.module]
        except KeyError:
            pass
373
        cleanup_c_files = WITH_CYTHON and self.cleanup_workdir
374
        cleanup_lib_files = self.cleanup_sharedlibs
375
        if os.path.exists(self.workdir):
376
            for rmfile in os.listdir(self.workdir):
377 378 379
                if not cleanup_c_files:
                    if rmfile[-2:] in (".c", ".h") or rmfile[-4:] == ".cpp":
                        continue
380 381
                if not cleanup_lib_files and rmfile.endswith(".so") or rmfile.endswith(".dll"):
                    continue
382 383 384 385 386 387 388 389 390 391 392 393
                if self.annotate and rmfile.endswith(".html"):
                    continue
                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
        else:
            os.makedirs(self.workdir)
394

395
    def runTest(self):
396 397 398
        self.runCompileTest()

    def runCompileTest(self):
399 400
        self.compile(self.test_directory, self.module, self.workdir,
                     self.test_directory, self.expect_errors, self.annotate)
401

402 403 404 405 406
    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
407 408 409 410
    def build_target_filename(self, module_name):
        target = '%s.%s' % (module_name, self.language)
        return target

411 412 413 414 415 416 417 418
    def copy_related_files(self, test_directory, target_directory, module_name):
        is_related = re.compile('%s_.*[.].*' % module_name).match
        for filename in os.listdir(test_directory):
            if is_related(filename):
                shutil.copy(os.path.join(test_directory, filename),
                            target_directory)

    def find_source_files(self, workdir, module_name):
419 420
        is_related = re.compile('%s_.*[.]%s' % (module_name, self.language)).match
        return [self.build_target_filename(module_name)] + [
421 422
            filename for filename in os.listdir(workdir)
            if is_related(filename) and os.path.isfile(os.path.join(workdir, filename)) ]
423 424

    def split_source_and_output(self, test_directory, module, workdir):
425
        source_file = self.find_module_source_file(os.path.join(test_directory, module) + '.pyx')
426
        source_and_output = io_open(source_file, 'rU', encoding='ISO-8859-1')
427
        try:
428 429
            out = io_open(os.path.join(workdir, module + os.path.splitext(source_file)[1]),
                              'w', encoding='ISO-8859-1')
430 431 432 433 434 435 436 437 438
            for line in source_and_output:
                last_line = line
                if line.startswith("_ERRORS"):
                    out.close()
                    out = ErrorWriter()
                else:
                    out.write(line)
        finally:
            source_and_output.close()
439 440 441
        try:
            geterrors = out.geterrors
        except AttributeError:
442
            out.close()
443 444 445 446
            return []
        else:
            return geterrors()

447 448
    def run_cython(self, test_directory, module, targetdir, incdir, annotate,
                   extra_compile_options=None):
449 450 451
        include_dirs = INCLUDE_DIRS[:]
        if incdir:
            include_dirs.append(incdir)
452
        source = self.find_module_source_file(
453
            os.path.join(test_directory, module + '.pyx'))
Stefan Behnel's avatar
Stefan Behnel committed
454
        target = os.path.join(targetdir, self.build_target_filename(module))
455

456 457
        if extra_compile_options is None:
            extra_compile_options = {}
458

459 460 461 462 463 464
        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
465

466
        options = CompilationOptions(
467
            default_options,
468 469
            include_path = include_dirs,
            output_file = target,
470
            annotate = annotate,
Stefan Behnel's avatar
Stefan Behnel committed
471 472
            use_listing_file = False,
            cplus = self.language == 'cpp',
473
            language_level = self.language_level,
474
            generate_pxi = False,
475
            evaluate_tree_assertions = True,
476
            **extra_compile_options
477
            )
478 479 480
        cython_compile(source, options=options,
                       full_module_name=module)

481
    def run_distutils(self, test_directory, module, workdir, incdir,
482
                      extra_extension_args=None):
483 484 485 486 487 488 489 490
        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()
491 492
            if COMPILER:
                build_extension.compiler = COMPILER
493 494 495 496
            ext_include_dirs = []
            for match, get_additional_include_dirs in EXT_DEP_INCLUDES:
                if match(module):
                    ext_include_dirs += get_additional_include_dirs()
497 498 499
            ext_compile_flags = CFLAGS[:]
            if  build_extension.compiler == 'mingw32':
                ext_compile_flags.append('-Wno-format')
500 501
            if extra_extension_args is None:
                extra_extension_args = {}
502

503
            self.copy_related_files(test_directory, workdir, module)
504 505
            extension = Extension(
                module,
506
                sources = self.find_source_files(workdir, module),
507
                include_dirs = ext_include_dirs,
508
                extra_compile_args = ext_compile_flags,
509
                **extra_extension_args
510
                )
511 512
            if self.language == 'cpp':
                extension.language = 'c++'
513 514 515 516 517 518
            build_extension.extensions = [extension]
            build_extension.build_temp = workdir
            build_extension.build_lib  = workdir
            build_extension.run()
        finally:
            os.chdir(cwd)
519

520
    def compile(self, test_directory, module, workdir, incdir,
521
                expect_errors, annotate):
522 523 524
        expected_errors = errors = ()
        if expect_errors:
            expected_errors = self.split_source_and_output(
525 526
                test_directory, module, workdir)
            test_directory = workdir
527

528 529 530 531
        if WITH_CYTHON:
            old_stderr = sys.stderr
            try:
                sys.stderr = ErrorWriter()
532
                self.run_cython(test_directory, module, workdir, incdir, annotate)
533 534 535
                errors = sys.stderr.geterrors()
            finally:
                sys.stderr = old_stderr
536 537

        if errors or expected_errors:
538 539 540 541 542 543 544 545 546 547 548 549
            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
550
                print("\n\n=== Got errors: ===")
551 552 553
                print('\n'.join(errors))
                print('\n')
                raise
554
        else:
Stefan Behnel's avatar
Stefan Behnel committed
555
            if not self.cython_only:
556
                self.run_distutils(test_directory, module, workdir, incdir)
557 558

class CythonRunTestCase(CythonCompileTestCase):
559
    def shortDescription(self):
Stefan Behnel's avatar
Stefan Behnel committed
560
        return "compiling (%s) and running %s" % (self.language, self.module)
561 562

    def run(self, result=None):
563 564
        if result is None:
            result = self.defaultTestResult()
Stefan Behnel's avatar
Stefan Behnel committed
565
        result.startTest(self)
566
        try:
Stefan Behnel's avatar
Stefan Behnel committed
567
            self.setUp()
568 569
            try:
                self.runCompileTest()
570
                self.run_tests(result)
571 572
            finally:
                check_thread_termination()
573 574 575
        except Exception:
            result.addError(self, sys.exc_info())
            result.stopTest(self)
576 577 578 579
        try:
            self.tearDown()
        except Exception:
            pass
580

581 582 583 584
    def run_tests(self, result):
        if not self.cython_only:
            self.run_doctests(self.module, result)

585
    def run_doctests(self, module_name, result):
586
        if sys.version_info[0] >= 3 or not hasattr(os, 'fork') or not self.fork:
587
            doctest.DocTestSuite(module_name).run(result)
588
            gc.collect()
589 590 591
            return

        # fork to make sure we do not keep the tested module loaded
592
        result_handle, result_file = tempfile.mkstemp()
593
        os.close(result_handle)
594 595 596 597 598
        child_id = os.fork()
        if not child_id:
            result_code = 0
            try:
                try:
599 600 601 602 603 604 605 606 607 608
                    tests = None
                    try:
                        partial_result = PartialTestResult(result)
                        tests = doctest.DocTestSuite(module_name)
                        tests.run(partial_result)
                        gc.collect()
                    except Exception:
                        if tests is None:
                            # importing failed, try to fake a test class
                            tests = _FakeClass(
Craig Citro's avatar
Craig Citro committed
609 610 611
                                failureException=sys.exc_info()[1],
                                _shortDescription=self.shortDescription(),
                                module_name=None)
612 613
                        partial_result.addError(tests, sys.exc_info())
                        result_code = 1
614
                    output = open(result_file, 'wb')
615 616 617
                    pickle.dump(partial_result.data(), output)
                except:
                    traceback.print_exc()
618 619 620 621 622
            finally:
                try: output.close()
                except: pass
                os._exit(result_code)

623 624
        try:
            cid, result_code = os.waitpid(child_id, 0)
Craig Citro's avatar
Craig Citro committed
625 626 627 628 629 630 631
            # 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
632 633 634 635 636 637 638 639
            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" %
Craig Citro's avatar
Craig Citro committed
640
                                (module_name, result_code))
641
        finally:
642 643
            try: os.unlink(result_file)
            except: pass
644

645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669
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]
670
                check_thread_termination()
671 672 673 674 675 676 677
        except Exception:
            result.addError(self, sys.exc_info())
            result.stopTest(self)
        try:
            self.tearDown()
        except Exception:
            pass
678

679 680 681 682
is_private_field = re.compile('^_[^_]').match

class _FakeClass(object):
    def __init__(self, **kwargs):
683
        self._shortDescription = kwargs.get('module_name')
684
        self.__dict__.update(kwargs)
685 686
    def shortDescription(self):
        return self._shortDescription
687

688 689 690 691 692 693
try: # Py2.7+ and Py3.2+
    from unittest.runner import _TextTestResult
except ImportError:
    from unittest import _TextTestResult

class PartialTestResult(_TextTestResult):
694
    def __init__(self, base_result):
695
        _TextTestResult.__init__(
696 697 698
            self, self._StringIO(), True,
            base_result.dots + base_result.showAll*2)

699 700 701 702 703 704
    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
705
                elif attr_name != '_shortDescription':
706 707
                    setattr(test_case, attr_name, None)

708
    def data(self):
709 710
        self.strip_error_results(self.failures)
        self.strip_error_results(self.errors)
711 712 713 714 715 716 717
        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
718
        failures, errors, tests_run, output = data
719 720 721 722 723 724 725 726 727 728 729 730 731
        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)


732
class CythonUnitTestCase(CythonRunTestCase):
733
    def shortDescription(self):
Stefan Behnel's avatar
Stefan Behnel committed
734
        return "compiling (%s) tests in %s" % (self.language, self.module)
735

736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760
    def run_tests(self, result):
        unittest.defaultTestLoader.loadTestsFromName(self.module).run(result)


class CythonPyregrTestCase(CythonRunTestCase):
    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):
761
        try:
762 763 764 765 766 767 768 769 770 771 772 773
            from test import test_support as support
        except ImportError: # Py3k
            from test import support

        def run_unittest(*classes):
            return self._run_unittest(result, *classes)
        def run_doctest(module, verbosity=None):
            return self._run_doctest(result, module)

        support.run_unittest = run_unittest
        support.run_doctest = run_doctest

774
        try:
775 776 777 778 779
            module = __import__(self.module)
            if hasattr(module, 'test_main'):
                module.test_main()
        except (unittest.SkipTest, support.ResourceDenied):
            result.addSkip(self, 'ok')
780

781
include_debugger = sys.version_info[:2] > (2, 5)
782

783
def collect_unittests(path, module_prefix, suite, selectors):
784 785 786 787 788 789 790
    def file_matches(filename):
        return filename.startswith("Test") and filename.endswith(".py")

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

    loader = unittest.TestLoader()
791

792 793 794
    if include_debugger:
        skipped_dirs = []
    else:
795
        skipped_dirs = ['Cython' + os.path.sep + 'Debugger' + os.path.sep]
796

797
    for dirpath, dirnames, filenames in os.walk(path):
798 799 800 801 802 803 804 805 806
        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
807 808 809 810 811
        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")]
812
                    modulename = module_prefix + filepath[len(path)+1:].replace(os.path.sep, '.')
813 814
                    if not [ 1 for match in selectors if match(modulename) ]:
                        continue
815 816 817
                    module = __import__(modulename)
                    for x in modulename.split('.')[1:]:
                        module = getattr(module, x)
Robert Bradshaw's avatar
Robert Bradshaw committed
818
                    suite.addTests([loader.loadTestsFromModule(module)])
819

820 821


822 823
def collect_doctests(path, module_prefix, suite, selectors):
    def package_matches(dirname):
824 825
        if dirname == 'Debugger' and not include_debugger:
            return False
826 827
        return dirname not in ("Mac", "Distutils", "Plex")
    def file_matches(filename):
Mark Florisson's avatar
Tests!  
Mark Florisson committed
828
        filename, ext = os.path.splitext(filename)
829
        blacklist = ['libcython', 'libpython', 'test_libcython_in_gdb',
830
                     'TestLibCython']
Mark Florisson's avatar
Tests!  
Mark Florisson committed
831 832 833 834 835
        return (ext == '.py' and not
                '~' in filename and not
                '#' in filename and not
                filename.startswith('.') and not
                filename in blacklist)
836 837
    import doctest, types
    for dirpath, dirnames, filenames in os.walk(path):
Robert Bradshaw's avatar
Robert Bradshaw committed
838 839 840 841 842 843 844 845 846 847 848 849
        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
Robert Bradshaw's avatar
Robert Bradshaw committed
850 851 852
                if 'in_gdb' in modulename:
                    # These should only be imported from gdb.
                    continue
Robert Bradshaw's avatar
Robert Bradshaw committed
853 854 855 856 857 858 859 860
                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
861

862 863 864 865 866 867

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

870
    def __init__(self, treefile, workdir, cleanup_workdir=True):
871
        self.name = os.path.splitext(os.path.basename(treefile))[0]
872
        self.treefile = treefile
873
        self.workdir = os.path.join(workdir, self.name)
874
        self.cleanup_workdir = cleanup_workdir
875 876 877 878 879 880 881 882
        cython_syspath = self.cython_root
        for path in sys.path[::-1]:
            if path.startswith(self.cython_root):
                # Py3 installation and refnanny build prepend their
                # fixed paths to sys.path => prefer that over the
                # generic one
                cython_syspath = path + os.pathsep + cython_syspath
        self.cython_syspath = cython_syspath
883 884 885
        unittest.TestCase.__init__(self)

    def shortDescription(self):
886
        return "End-to-end %s" % self.name
887 888 889

    def setUp(self):
        from Cython.TestUtils import unpack_source_tree
890
        _, self.commands = unpack_source_tree(self.treefile, self.workdir)
891 892 893 894 895 896 897
        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:
898 899 900 901 902 903 904
            for trial in range(5):
                try:
                    shutil.rmtree(self.workdir)
                except OSError:
                    time.sleep(0.1)
                else:
                    break
905
        os.chdir(self.old_dir)
906

907 908
    def runTest(self):
        commands = (self.commands
Robert Bradshaw's avatar
Robert Bradshaw committed
909
            .replace("CYTHON", "PYTHON %s" % os.path.join(self.cython_root, 'cython.py'))
910
            .replace("PYTHON", sys.executable))
911
        try:
Robert Bradshaw's avatar
Robert Bradshaw committed
912
            old_path = os.environ.get('PYTHONPATH')
Robert Bradshaw's avatar
Robert Bradshaw committed
913
            os.environ['PYTHONPATH'] = self.cython_syspath + os.pathsep + os.path.join(self.cython_syspath, (old_path or ''))
Robert Bradshaw's avatar
Robert Bradshaw committed
914 915 916 917 918 919 920 921 922 923
            for command in commands.split('\n'):
                if sys.version_info[:2] >= (2,4):
                    import subprocess
                    p = subprocess.Popen(commands,
                                         stderr=subprocess.PIPE,
                                         stdout=subprocess.PIPE,
                                         shell=True)
                    out, err = p.communicate()
                    res = p.returncode
                    if res != 0:
Stefan Behnel's avatar
Stefan Behnel committed
924 925 926
                        print(command)
                        print(out)
                        print(err)
Robert Bradshaw's avatar
Robert Bradshaw committed
927 928 929
                else:
                    res = os.system(command)
                self.assertEqual(0, res, "non-zero exit status")
930
        finally:
931 932 933 934
            if old_path:
                os.environ['PYTHONPATH'] = old_path
            else:
                del os.environ['PYTHONPATH']
935 936


937 938 939 940
# TODO: Support cython_freeze needed here as well.
# TODO: Windows support.

class EmbedTest(unittest.TestCase):
941

942
    working_dir = "Demos/embed"
943

944 945 946
    def setUp(self):
        self.old_dir = os.getcwd()
        os.chdir(self.working_dir)
947
        os.system(
948
            "make PYTHON='%s' clean > /dev/null" % sys.executable)
949

950 951
    def tearDown(self):
        try:
952 953
            os.system(
                "make PYTHON='%s' clean > /dev/null" % sys.executable)
954 955 956
        except:
            pass
        os.chdir(self.old_dir)
957

958
    def test_embed(self):
959
        from distutils import sysconfig
960
        libname = sysconfig.get_config_var('LIBRARY')
961
        libdir = sysconfig.get_config_var('LIBDIR')
962 963 964 965 966 967 968
        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')
969
        cython = 'cython.py'
Stefan Behnel's avatar
Stefan Behnel committed
970
        if sys.version_info[0] >=3 and CY3_DIR:
971 972
            cython = os.path.join(CY3_DIR, cython)
        cython = os.path.abspath(os.path.join('..', '..', cython))
973
        self.assert_(os.system(
974
            "make PYTHON='%s' CYTHON='%s' LIBDIR1='%s' test > make.output" % (sys.executable, cython, libdir)) == 0)
975 976 977 978
        try:
            os.remove('make.output')
        except OSError:
            pass
979

980 981 982 983 984 985 986 987
class MissingDependencyExcluder:
    def __init__(self, deps):
        # deps: { module name : matcher func }
        self.exclude_matchers = []
        for mod, matcher in deps.items():
            try:
                __import__(mod)
            except ImportError:
Robert Bradshaw's avatar
Robert Bradshaw committed
988
                self.exclude_matchers.append(string_selector(matcher))
989
        self.tests_missing_deps = []
Robert Bradshaw's avatar
Robert Bradshaw committed
990
    def __call__(self, testname, tags=None):
991
        for matcher in self.exclude_matchers:
Robert Bradshaw's avatar
Robert Bradshaw committed
992
            if matcher(testname, tags):
993 994 995 996
                self.tests_missing_deps.append(testname)
                return True
        return False

997 998 999 1000 1001
class VersionDependencyExcluder:
    def __init__(self, deps):
        # deps: { version : matcher func }
        from sys import version_info
        self.exclude_matchers = []
1002 1003
        for ver, (compare, matcher) in deps.items():
            if compare(version_info, ver):
1004 1005
                self.exclude_matchers.append(matcher)
        self.tests_missing_deps = []
Robert Bradshaw's avatar
Robert Bradshaw committed
1006
    def __call__(self, testname, tags=None):
1007 1008 1009 1010 1011 1012
        for matcher in self.exclude_matchers:
            if matcher(testname):
                self.tests_missing_deps.append(testname)
                return True
        return False

1013 1014 1015 1016
class FileListExcluder:

    def __init__(self, list_file):
        self.excludes = {}
1017 1018 1019 1020 1021 1022 1023 1024
        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()
1025

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

Robert Bradshaw's avatar
Robert Bradshaw committed
1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056
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):
        self.pattern = re.compile(pattern_string, re.I|re.U)

    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:])
        

1057 1058 1059 1060 1061 1062 1063 1064 1065 1066
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
1067
    dlog.set_threshold(dlog.INFO)
1068 1069 1070 1071 1072 1073
    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
1074
                     recursive-include Cython/Debugger/Tests *
1075
                     include runtests.py
1076
                     include cython.py
1077 1078 1079
                     ''')
    sys.path.insert(0, cy3_dir)

1080 1081 1082 1083
    for keep_2x_file in KEEP_2X_FILES:
        destfile = os.path.join(cy3_dir, keep_2x_file)
        shutil.copy(keep_2x_file, destfile)

1084 1085
class PendingThreadsError(RuntimeError):
    pass
1086

1087 1088 1089
threads_seen = []

def check_thread_termination(ignore_seen=True):
1090 1091 1092 1093 1094 1095 1096 1097 1098
    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():
1099 1100 1101
            if not ignore_seen:
                blocking_threads.append(t)
                continue
1102 1103 1104 1105 1106 1107
            for seen in threads_seen:
                if t is seen:
                    break
            else:
                threads_seen.append(t)
                blocking_threads.append(t)
1108 1109 1110 1111 1112
    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))
1113
    raise PendingThreadsError("left-over threads found after running test")
1114

1115
def main():
1116 1117 1118

    DISTDIR = os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]))

1119 1120 1121
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option("--no-cleanup", dest="cleanup_workdir",
Stefan Behnel's avatar
Stefan Behnel committed
1122 1123
                      action="store_false", default=True,
                      help="do not delete the generated C files (allows passing --no-cython on next run)")
1124 1125 1126
    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)")
Stefan Behnel's avatar
Stefan Behnel committed
1127 1128 1129
    parser.add_option("--no-cython", dest="with_cython",
                      action="store_false", default=True,
                      help="do not run the Cython compiler, only the C compiler")
1130 1131
    parser.add_option("--compiler", dest="compiler", default=None,
                      help="C compiler type")
Stefan Behnel's avatar
Stefan Behnel committed
1132 1133 1134 1135 1136 1137
    parser.add_option("--no-c", dest="use_c",
                      action="store_false", default=True,
                      help="do not test C compilation")
    parser.add_option("--no-cpp", dest="use_cpp",
                      action="store_false", default=True,
                      help="do not test C++ compilation")
1138 1139 1140
    parser.add_option("--no-unit", dest="unittests",
                      action="store_false", default=True,
                      help="do not run the unit tests")
1141 1142 1143
    parser.add_option("--no-doctest", dest="doctests",
                      action="store_false", default=True,
                      help="do not run the doctests")
1144 1145 1146
    parser.add_option("--no-file", dest="filetests",
                      action="store_false", default=True,
                      help="do not run the file based tests")
1147 1148
    parser.add_option("--no-pyregr", dest="pyregr",
                      action="store_false", default=True,
1149
                      help="do not run the regression tests of CPython in tests/pyregr/")
Stefan Behnel's avatar
Stefan Behnel committed
1150
    parser.add_option("--cython-only", dest="cython_only",
1151 1152
                      action="store_true", default=False,
                      help="only compile pyx to c, do not run C compiler or run the tests")
1153
    parser.add_option("--no-refnanny", dest="with_refnanny",
Stefan Behnel's avatar
Stefan Behnel committed
1154
                      action="store_false", default=True,
1155
                      help="do not regression test reference counting")
1156 1157 1158
    parser.add_option("--no-fork", dest="fork",
                      action="store_false", default=True,
                      help="do not fork to run tests")
1159 1160 1161
    parser.add_option("--sys-pyregr", dest="system_pyregr",
                      action="store_true", default=False,
                      help="run the regression tests of the CPython installation")
1162 1163 1164
    parser.add_option("-x", "--exclude", dest="exclude",
                      action="append", metavar="PATTERN",
                      help="exclude tests matching the PATTERN")
1165
    parser.add_option("-C", "--coverage", dest="coverage",
Stefan Behnel's avatar
Stefan Behnel committed
1166 1167
                      action="store_true", default=False,
                      help="collect source coverage data for the Compiler")
1168 1169 1170
    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
1171
    parser.add_option("-A", "--annotate", dest="annotate_source",
1172
                      action="store_true", default=True,
Stefan Behnel's avatar
Stefan Behnel committed
1173
                      help="generate annotated HTML versions of the test source files")
1174 1175 1176
    parser.add_option("--no-annotate", dest="annotate_source",
                      action="store_false",
                      help="do not generate annotated HTML versions of the test source files")
1177
    parser.add_option("-v", "--verbose", dest="verbosity",
Stefan Behnel's avatar
Stefan Behnel committed
1178 1179
                      action="count", default=0,
                      help="display test progress, pass twice to print test names")
1180 1181
    parser.add_option("-T", "--ticket", dest="tickets",
                      action="append",
1182
                      help="a bug ticket number to run the respective test in 'tests/*'")
1183 1184 1185
    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)'")
1186 1187
    parser.add_option("--xml-output", dest="xml_output_dir", metavar="DIR",
                      help="write test results in XML to directory DIR")
1188 1189 1190
    parser.add_option("--exit-ok", dest="exit_ok", default=False,
                      action="store_true",
                      help="exit without error code even on test failures")
1191 1192 1193 1194
    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")
1195 1196 1197

    options, cmd_args = parser.parse_args()

1198 1199
    ROOTDIR = os.path.abspath(options.root_dir)
    WORKDIR = os.path.abspath(options.work_dir)
1200

1201 1202
    if sys.version_info[0] >= 3:
        options.doctests = False
1203
        if options.with_cython:
1204 1205 1206 1207
            try:
                # try if Cython is installed in a Py3 version
                import Cython.Compiler.Main
            except Exception:
Stefan Behnel's avatar
comment  
Stefan Behnel committed
1208 1209
                # back out anything the import process loaded, then
                # 2to3 the Cython sources to make them re-importable
1210
                cy_modules = [ name for name in sys.modules
Stefan Behnel's avatar
Stefan Behnel committed
1211
                               if name == 'Cython' or name.startswith('Cython.') ]
1212 1213 1214
                for name in cy_modules:
                    del sys.modules[name]
                # hasn't been refactored yet - do it now
1215 1216
                global CY3_DIR
                CY3_DIR = cy3_dir = os.path.join(WORKDIR, 'Cy3')
1217 1218 1219 1220 1221 1222
                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
1223

1224
    WITH_CYTHON = options.with_cython
1225

1226
    if options.coverage or options.coverage_xml:
1227
        if not WITH_CYTHON:
Stefan Behnel's avatar
Stefan Behnel committed
1228
            options.coverage = options.coverage_xml = False
1229
        else:
1230
            from coverage import coverage as _coverage
Stefan Behnel's avatar
Stefan Behnel committed
1231
            coverage = _coverage(branch=True)
1232 1233 1234
            coverage.erase()
            coverage.start()

1235
    if WITH_CYTHON:
1236
        global CompilationOptions, pyrex_default_options, cython_compile
1237 1238 1239 1240
        from Cython.Compiler.Main import \
            CompilationOptions, \
            default_options as pyrex_default_options, \
            compile as cython_compile
1241 1242
        from Cython.Compiler import Errors
        Errors.LEVEL = 0 # show all warnings
Stefan Behnel's avatar
cleanup  
Stefan Behnel committed
1243
        from Cython.Compiler import Options
1244
        Options.generate_cleanup_code = 3   # complete cleanup code
Stefan Behnel's avatar
cleanup  
Stefan Behnel committed
1245 1246
        from Cython.Compiler import DebugFlags
        DebugFlags.debug_temp_code_comments = 1
1247

1248
    # RUN ALL TESTS!
1249
    UNITTEST_MODULE = "Cython"
1250
    UNITTEST_ROOT = os.path.join(os.path.dirname(__file__), UNITTEST_MODULE)
1251 1252
    if WITH_CYTHON:
        if os.path.exists(WORKDIR):
1253
            for path in os.listdir(WORKDIR):
1254
                if path in ("support", "Cy3"): continue
1255
                shutil.rmtree(os.path.join(WORKDIR, path), ignore_errors=True)
1256 1257
    if not os.path.exists(WORKDIR):
        os.makedirs(WORKDIR)
1258

1259 1260
    sys.stderr.write("Python %s\n" % sys.version)
    sys.stderr.write("\n")
1261 1262
    if WITH_CYTHON:
        from Cython.Compiler.Version import version
1263
        sys.stderr.write("Running tests against Cython %s\n" % version)
1264
    else:
1265
        sys.stderr.write("Running tests without Cython.\n")
1266

1267 1268 1269 1270 1271
    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"))
1272
        sys.path.insert(0, os.path.split(libpath)[0])
1273
        CFLAGS.append("-DCYTHON_REFNANNY=1")
1274

Stefan Behnel's avatar
cleanup  
Stefan Behnel committed
1275 1276 1277 1278 1279
    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

1280 1281 1282 1283 1284
    if WITH_CYTHON and options.language_level == 3:
        sys.stderr.write("Using Cython language level 3.\n")

    sys.stderr.write("\n")

1285
    test_bugs = False
Stefan Behnel's avatar
Stefan Behnel committed
1286 1287 1288
    if options.tickets:
        for ticket_number in options.tickets:
            test_bugs = True
Robert Bradshaw's avatar
Robert Bradshaw committed
1289
            cmd_args.append('ticket:%s' % ticket_number)
1290 1291 1292 1293
    if not test_bugs:
        for selector in cmd_args:
            if selector.startswith('bugs'):
                test_bugs = True
1294

1295
    import re
Robert Bradshaw's avatar
Robert Bradshaw committed
1296
    selectors = [ string_selector(r) for r in cmd_args ]
1297
    if not selectors:
Robert Bradshaw's avatar
Robert Bradshaw committed
1298
        selectors = [ lambda x, tags=None: True ]
1299

1300 1301 1302
    # Chech which external modules are not present and exclude tests
    # which depends on them (by prefix)

1303 1304
    missing_dep_excluder = MissingDependencyExcluder(EXT_DEP_MODULES)
    version_dep_excluder = VersionDependencyExcluder(VER_DEP_MODULES)
1305
    exclude_selectors = [missing_dep_excluder, version_dep_excluder] # want to pring msg at exit
1306

1307
    if options.exclude:
Robert Bradshaw's avatar
Robert Bradshaw committed
1308
        exclude_selectors += [ string_selector(r) for r in options.exclude ]
1309

1310
    if not test_bugs:
1311
        exclude_selectors += [ FileListExcluder(os.path.join(ROOTDIR, "bugs.txt")) ]
1312

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

1316 1317 1318
    global COMPILER
    if options.compiler:
        COMPILER = options.compiler
Stefan Behnel's avatar
Stefan Behnel committed
1319 1320 1321 1322 1323 1324
    languages = []
    if options.use_c:
        languages.append('c')
    if options.use_cpp:
        languages.append('cpp')

1325 1326 1327
    test_suite = unittest.TestSuite()

    if options.unittests:
1328
        collect_unittests(UNITTEST_ROOT, UNITTEST_MODULE + ".", test_suite, selectors)
1329

1330 1331
    if options.doctests:
        collect_doctests(UNITTEST_ROOT, UNITTEST_MODULE + ".", test_suite, selectors)
1332

Stefan Behnel's avatar
Stefan Behnel committed
1333
    if options.filetests and languages:
1334
        filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors,
1335
                                options.annotate_source, options.cleanup_workdir,
Stefan Behnel's avatar
Stefan Behnel committed
1336
                                options.cleanup_sharedlibs, options.pyregr,
1337
                                options.cython_only, languages, test_bugs,
1338
                                options.fork, options.language_level)
1339 1340
        test_suite.addTest(filetests.build_suite())

Stefan Behnel's avatar
Stefan Behnel committed
1341
    if options.system_pyregr and languages:
1342 1343 1344 1345 1346 1347
        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,
                                    options.cleanup_sharedlibs, True,
                                    options.cython_only, languages, test_bugs,
1348
                                    options.fork, sys.version_info[0])
1349 1350
            sys.stderr.write("Including CPython regression tests in %s\n" % sys_pyregr_dir)
            test_suite.addTest(filetests.handle_directory(sys_pyregr_dir, 'pyregr'))
1351

Stefan Behnel's avatar
Stefan Behnel committed
1352
    if options.xml_output_dir:
1353
        from Cython.Tests.xmlrunner import XMLTestRunner
Stefan Behnel's avatar
Stefan Behnel committed
1354 1355
        test_runner = XMLTestRunner(output=options.xml_output_dir,
                                    verbose=options.verbosity > 0)
1356 1357 1358 1359
    else:
        test_runner = unittest.TextTestRunner(verbosity=options.verbosity)

    result = test_runner.run(test_suite)
1360

1361
    if options.coverage or options.coverage_xml:
1362
        coverage.stop()
1363
        ignored_modules = ('Options', 'Version', 'DebugFlags', 'CmdLine')
1364 1365
        modules = [ module for name, module in sys.modules.items()
                    if module is not None and
1366
                    name.startswith('Cython.Compiler.') and
1367
                    name[len('Cython.Compiler.'):] not in ignored_modules ]
1368 1369 1370
        if options.coverage:
            coverage.report(modules, show_missing=0)
        if options.coverage_xml:
Stefan Behnel's avatar
Stefan Behnel committed
1371
            coverage.xml_report(modules, outfile="coverage-report.xml")
1372 1373 1374 1375 1376

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

1378
    if options.with_refnanny:
Dag Sverre Seljebotn's avatar
Cleanup  
Dag Sverre Seljebotn committed
1379
        import refnanny
1380
        sys.stderr.write("\n".join([repr(x) for x in refnanny.reflog]))
1381

1382 1383
    print("ALL DONE")

1384
    if options.exit_ok:
1385
        return_code = 0
1386
    else:
1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398
        return_code = not result.wasSuccessful()

    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
        os._exit(return_code)

if __name__ == '__main__':
    try:
        main()
Stefan Behnel's avatar
Stefan Behnel committed
1399 1400
    except SystemExit: # <= Py2.4 ...
        raise
1401 1402 1403 1404 1405 1406 1407
    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
            os._exit(1)