runtests.py 54.5 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
Stefan Behnel committed
101
                                          'run.generators_py', # generators, with statement
102
                                          'run.pure_py', # decorators, with statement
103
                                          ]),
Stefan Behnel's avatar
Stefan Behnel committed
104 105
    (2,7) : (operator.lt, lambda x: x in ['run.withstat_py', # multi context with statement
                                          ]),
106 107 108 109
    # 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...
110 111 112
    (2,999): (operator.lt, lambda x: x in ['run.special_methods_T561_py3',
                                           'run.test_raisefrom',
                                           ]),
113
    (3,): (operator.ge, lambda x: x in ['run.non_future_division',
Stefan Behnel's avatar
Stefan Behnel committed
114
                                        'compile.extsetslice',
115 116
                                        'compile.extdelslice',
                                        'run.special_methods_T561_py2']),
117 118
}

119 120 121 122 123 124 125 126
# 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'),
]

127
COMPILER = None
128
INCLUDE_DIRS = [ d for d in os.getenv('INCLUDE', '').split(os.pathsep) if d ]
129 130
CFLAGS = os.getenv('CFLAGS', '').split()

131 132 133 134 135 136 137
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)
Robert Bradshaw's avatar
Robert Bradshaw committed
138 139
        else:
            print "dup", args
140 141 142
        return res
    return func

Robert Bradshaw's avatar
Robert Bradshaw committed
143 144
def parse_tags(filepath):
    tags = defaultdict(list)
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
    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
160 161
    return tags

Stefan Behnel's avatar
Stefan Behnel committed
162 163
parse_tags = memoize(parse_tags)

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

Stefan Behnel's avatar
Stefan Behnel committed
166

167 168 169 170
class build_ext(_build_ext):
    def build_extension(self, ext):
        if ext.language == 'c++':
            try:
171
                try: # Py2.7+ & Py3.2+
172 173 174 175
                    compiler_obj = self.compiler_obj
                except AttributeError:
                    compiler_obj = self.compiler
                compiler_obj.compiler_so.remove('-Wstrict-prototypes')
176 177 178
            except Exception:
                pass
        _build_ext.build_extension(self, ext)
179 180

class ErrorWriter(object):
181
    match_error = re.compile('(warning:)?(?:.*:)?\s*([-0-9]+)\s*:\s*([-0-9]+)\s*:\s*(.*)').match
182 183 184 185
    def __init__(self):
        self.output = []
        self.write = self.output.append

186
    def _collect(self, collect_errors, collect_warnings):
187
        s = ''.join(self.output)
188
        result = []
189 190 191
        for line in s.split('\n'):
            match = self.match_error(line)
            if match:
192 193 194
                is_warning, line, column, message = match.groups()
                if (is_warning and collect_warnings) or \
                        (not is_warning and collect_errors):
195 196
                    result.append( (int(line), int(column), message.strip()) )
        result.sort()
Stefan Behnel's avatar
Stefan Behnel committed
197
        return [ "%d:%d: %s" % values for values in result ]
198 199 200 201 202 203 204 205 206

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

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

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

208
class TestBuilder(object):
209
    def __init__(self, rootdir, workdir, selectors, exclude_selectors, annotate,
210
                 cleanup_workdir, cleanup_sharedlibs, with_pyregr, cython_only,
211
                 languages, test_bugs, fork, language_level):
212 213
        self.rootdir = rootdir
        self.workdir = workdir
214
        self.selectors = selectors
215
        self.exclude_selectors = exclude_selectors
216
        self.annotate = annotate
217
        self.cleanup_workdir = cleanup_workdir
218
        self.cleanup_sharedlibs = cleanup_sharedlibs
219
        self.with_pyregr = with_pyregr
220 221
        self.cython_only = cython_only
        self.languages = languages
222
        self.test_bugs = test_bugs
223
        self.fork = fork
224
        self.language_level = language_level
225 226 227

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

246
    def handle_directory(self, path, context):
247 248 249 250
        workdir = os.path.join(self.workdir, context)
        if not os.path.exists(workdir):
            os.makedirs(workdir)

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

            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
285
                test_class = CythonPyregrTestCase
286
            elif mode == 'run':
287
                if module.startswith("test_"):
288
                    test_class = CythonUnitTestCase
289
                else:
290
                    test_class = CythonRunTestCase
291
            else:
292
                test_class = CythonCompileTestCase
293

294
            for test in self.build_tests(test_class, path, workdir,
Robert Bradshaw's avatar
Robert Bradshaw committed
295
                                         module, mode == 'error', tags):
296
                suite.addTest(test)
297
            if mode == 'run' and ext == '.py':
298 299
                # additionally test file in real Python
                suite.addTest(PureDoctestTestCase(module, os.path.join(path, filename)))
300
                
301 302
        return suite

Robert Bradshaw's avatar
Robert Bradshaw committed
303
    def build_tests(self, test_class, path, workdir, module, expect_errors, tags):
Vitja Makarov's avatar
Vitja Makarov committed
304 305 306 307 308
        if 'werror' in tags['tags']:
            warning_errors = True
        else:
            warning_errors = False

309
        if expect_errors:
Robert Bradshaw's avatar
Robert Bradshaw committed
310
            if 'cpp' in tags['tag'] and 'cpp' in self.languages:
Robert Bradshaw's avatar
Robert Bradshaw committed
311 312 313
                languages = ['cpp']
            else:
                languages = self.languages[:1]
314 315
        else:
            languages = self.languages
Robert Bradshaw's avatar
Robert Bradshaw committed
316
        if 'cpp' in tags['tag'] and 'c' in languages:
317 318
            languages = list(languages)
            languages.remove('c')
319
        tests = [ self.build_test(test_class, path, workdir, module,
Vitja Makarov's avatar
Vitja Makarov committed
320
                                  language, expect_errors, warning_errors)
321
                  for language in languages ]
322 323 324
        return tests

    def build_test(self, test_class, path, workdir, module,
Vitja Makarov's avatar
Vitja Makarov committed
325
                   language, expect_errors, warning_errors):
Stefan Behnel's avatar
Stefan Behnel committed
326 327 328
        workdir = os.path.join(workdir, language)
        if not os.path.exists(workdir):
            os.makedirs(workdir)
329 330 331 332 333 334
        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,
335
                          cython_only=self.cython_only,
336
                          fork=self.fork,
Vitja Makarov's avatar
Vitja Makarov committed
337 338
                          language_level=self.language_level,
                          warning_errors=warning_errors)
339

340
class CythonCompileTestCase(unittest.TestCase):
341
    def __init__(self, test_directory, workdir, module, language='c',
342
                 expect_errors=False, annotate=False, cleanup_workdir=True,
343
                 cleanup_sharedlibs=True, cython_only=False, fork=True,
Vitja Makarov's avatar
Vitja Makarov committed
344
                 language_level=2, warning_errors=False):
345
        self.test_directory = test_directory
346 347
        self.workdir = workdir
        self.module = module
348
        self.language = language
349
        self.expect_errors = expect_errors
350
        self.annotate = annotate
351
        self.cleanup_workdir = cleanup_workdir
352
        self.cleanup_sharedlibs = cleanup_sharedlibs
353
        self.cython_only = cython_only
354
        self.fork = fork
355
        self.language_level = language_level
Vitja Makarov's avatar
Vitja Makarov committed
356
        self.warning_errors = warning_errors
357 358 359
        unittest.TestCase.__init__(self)

    def shortDescription(self):
360
        return "compiling (%s) %s" % (self.language, self.module)
361

Stefan Behnel's avatar
Stefan Behnel committed
362
    def setUp(self):
Vitja Makarov's avatar
Vitja Makarov committed
363
        from Cython.Compiler import Options
364 365
        self._saved_options = [ (name, getattr(Options, name))
                                for name in ('warning_errors', 'error_on_unknown_names') ]
Vitja Makarov's avatar
Vitja Makarov committed
366 367
        Options.warning_errors = self.warning_errors

Stefan Behnel's avatar
Stefan Behnel committed
368 369 370
        if self.workdir not in sys.path:
            sys.path.insert(0, self.workdir)

371
    def tearDown(self):
Vitja Makarov's avatar
Vitja Makarov committed
372
        from Cython.Compiler import Options
373 374
        for name, value in self._saved_options:
            setattr(Options, name, value)
Vitja Makarov's avatar
Vitja Makarov committed
375

Stefan Behnel's avatar
Stefan Behnel committed
376 377 378 379 380 381 382 383
        try:
            sys.path.remove(self.workdir)
        except ValueError:
            pass
        try:
            del sys.modules[self.module]
        except KeyError:
            pass
384
        cleanup_c_files = WITH_CYTHON and self.cleanup_workdir
385
        cleanup_lib_files = self.cleanup_sharedlibs
386
        if os.path.exists(self.workdir):
387
            for rmfile in os.listdir(self.workdir):
388 389 390
                if not cleanup_c_files:
                    if rmfile[-2:] in (".c", ".h") or rmfile[-4:] == ".cpp":
                        continue
391 392
                if not cleanup_lib_files and rmfile.endswith(".so") or rmfile.endswith(".dll"):
                    continue
393 394 395 396 397 398 399 400 401 402 403 404
                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)
405

406
    def runTest(self):
407 408 409
        self.runCompileTest()

    def runCompileTest(self):
410 411
        self.compile(self.test_directory, self.module, self.workdir,
                     self.test_directory, self.expect_errors, self.annotate)
412

413 414 415 416 417
    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
418 419 420
    def build_target_filename(self, module_name):
        target = '%s.%s' % (module_name, self.language)
        return target
Robert Bradshaw's avatar
Robert Bradshaw committed
421 422
    
    def related_files(self, test_directory, module_name):
423
        is_related = re.compile('%s_.*[.].*' % module_name).match
Robert Bradshaw's avatar
Robert Bradshaw committed
424 425 426 427 428 429 430
        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)
431

Robert Bradshaw's avatar
Robert Bradshaw committed
432 433 434 435
    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))])
436 437

    def split_source_and_output(self, test_directory, module, workdir):
438
        source_file = self.find_module_source_file(os.path.join(test_directory, module) + '.pyx')
439
        source_and_output = io_open(source_file, 'rU', encoding='ISO-8859-1')
440
        try:
441 442
            out = io_open(os.path.join(workdir, module + os.path.splitext(source_file)[1]),
                              'w', encoding='ISO-8859-1')
443 444 445 446 447 448 449 450 451
            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()
452 453 454
        try:
            geterrors = out.geterrors
        except AttributeError:
455
            out.close()
456 457 458 459
            return []
        else:
            return geterrors()

460 461
    def run_cython(self, test_directory, module, targetdir, incdir, annotate,
                   extra_compile_options=None):
462 463 464
        include_dirs = INCLUDE_DIRS[:]
        if incdir:
            include_dirs.append(incdir)
465
        source = self.find_module_source_file(
466
            os.path.join(test_directory, module + '.pyx'))
Stefan Behnel's avatar
Stefan Behnel committed
467
        target = os.path.join(targetdir, self.build_target_filename(module))
468

469 470
        if extra_compile_options is None:
            extra_compile_options = {}
471

472 473 474 475 476 477
        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
478

479
        options = CompilationOptions(
480
            default_options,
481 482
            include_path = include_dirs,
            output_file = target,
483
            annotate = annotate,
484 485
            use_listing_file = False,
            cplus = self.language == 'cpp',
486
            language_level = self.language_level,
487
            generate_pxi = False,
488
            evaluate_tree_assertions = True,
489
            **extra_compile_options
490
            )
491 492 493
        cython_compile(source, options=options,
                       full_module_name=module)

494
    def run_distutils(self, test_directory, module, workdir, incdir,
495
                      extra_extension_args=None):
496 497 498 499 500 501 502 503
        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()
504 505
            if COMPILER:
                build_extension.compiler = COMPILER
506 507 508 509
            ext_include_dirs = []
            for match, get_additional_include_dirs in EXT_DEP_INCLUDES:
                if match(module):
                    ext_include_dirs += get_additional_include_dirs()
510 511 512
            ext_compile_flags = CFLAGS[:]
            if  build_extension.compiler == 'mingw32':
                ext_compile_flags.append('-Wno-format')
513 514
            if extra_extension_args is None:
                extra_extension_args = {}
515

Robert Bradshaw's avatar
Robert Bradshaw committed
516 517
            related_files = self.related_files(test_directory, module)
            self.copy_files(test_directory, workdir, related_files)
518 519
            extension = Extension(
                module,
Robert Bradshaw's avatar
Robert Bradshaw committed
520
                sources = self.source_files(workdir, module, related_files),
521
                include_dirs = ext_include_dirs,
522
                extra_compile_args = ext_compile_flags,
523
                **extra_extension_args
524
                )
525 526
            if self.language == 'cpp':
                extension.language = 'c++'
527 528 529 530 531 532
            build_extension.extensions = [extension]
            build_extension.build_temp = workdir
            build_extension.build_lib  = workdir
            build_extension.run()
        finally:
            os.chdir(cwd)
533

534
    def compile(self, test_directory, module, workdir, incdir,
535
                expect_errors, annotate):
536 537 538
        expected_errors = errors = ()
        if expect_errors:
            expected_errors = self.split_source_and_output(
539 540
                test_directory, module, workdir)
            test_directory = workdir
541

542 543 544 545
        if WITH_CYTHON:
            old_stderr = sys.stderr
            try:
                sys.stderr = ErrorWriter()
546
                self.run_cython(test_directory, module, workdir, incdir, annotate)
547 548 549
                errors = sys.stderr.geterrors()
            finally:
                sys.stderr = old_stderr
550 551

        if errors or expected_errors:
552 553 554 555 556 557 558 559 560 561 562 563
            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
Stefan Behnel committed
564
                print("\n\n=== Got errors: ===")
565 566 567
                print('\n'.join(errors))
                print('\n')
                raise
568
        else:
569
            if not self.cython_only:
570
                self.run_distutils(test_directory, module, workdir, incdir)
571 572

class CythonRunTestCase(CythonCompileTestCase):
573
    def shortDescription(self):
574
        return "compiling (%s) and running %s" % (self.language, self.module)
575 576

    def run(self, result=None):
577 578
        if result is None:
            result = self.defaultTestResult()
Stefan Behnel's avatar
Stefan Behnel committed
579
        result.startTest(self)
580
        try:
Stefan Behnel's avatar
Stefan Behnel committed
581
            self.setUp()
582 583
            try:
                self.runCompileTest()
584
                self.run_tests(result)
585 586
            finally:
                check_thread_termination()
587 588 589
        except Exception:
            result.addError(self, sys.exc_info())
            result.stopTest(self)
590 591 592 593
        try:
            self.tearDown()
        except Exception:
            pass
594

595 596 597 598
    def run_tests(self, result):
        if not self.cython_only:
            self.run_doctests(self.module, result)

599
    def run_doctests(self, module_name, result):
600 601 602 603 604 605 606
        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
607 608
    if not fork or sys.version_info[0] >= 3 or not hasattr(os, 'fork'):
        run_func(result)
609 610 611 612 613 614 615 616 617
        gc.collect()
        return

    # fork to make sure we do not keep the tested module loaded
    result_handle, result_file = tempfile.mkstemp()
    os.close(result_handle)
    child_id = os.fork()
    if not child_id:
        result_code = 0
618
        try:
619 620
            try:
                tests = None
621
                try:
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
                    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()
638
        finally:
639
            try: output.close()
640
            except: pass
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663
            os._exit(result_code)

    try:
        cid, result_code = os.waitpid(child_id, 0)
        # 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
664

665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689
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]
690
                check_thread_termination()
691 692 693 694 695 696 697
        except Exception:
            result.addError(self, sys.exc_info())
            result.stopTest(self)
        try:
            self.tearDown()
        except Exception:
            pass
698

699 700 701 702
is_private_field = re.compile('^_[^_]').match

class _FakeClass(object):
    def __init__(self, **kwargs):
703
        self._shortDescription = kwargs.get('module_name')
704
        self.__dict__.update(kwargs)
705 706
    def shortDescription(self):
        return self._shortDescription
707

708 709 710 711 712 713
try: # Py2.7+ and Py3.2+
    from unittest.runner import _TextTestResult
except ImportError:
    from unittest import _TextTestResult

class PartialTestResult(_TextTestResult):
714
    def __init__(self, base_result):
715
        _TextTestResult.__init__(
716 717 718
            self, self._StringIO(), True,
            base_result.dots + base_result.showAll*2)

719 720 721 722 723 724
    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
725
                elif attr_name != '_shortDescription':
726 727
                    setattr(test_case, attr_name, None)

728
    def data(self):
729 730
        self.strip_error_results(self.failures)
        self.strip_error_results(self.errors)
731 732 733 734 735 736 737
        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
738
        failures, errors, tests_run, output = data
739 740 741 742 743 744 745 746 747 748 749 750 751
        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)


752
class CythonUnitTestCase(CythonRunTestCase):
753
    def shortDescription(self):
754
        return "compiling (%s) tests in %s" % (self.language, self.module)
755

756 757 758 759 760
    def run_tests(self, result):
        unittest.defaultTestLoader.loadTestsFromName(self.module).run(result)


class CythonPyregrTestCase(CythonRunTestCase):
761 762 763 764 765
    def setUp(self):
        CythonRunTestCase.setUp(self)
        from Cython.Compiler import Options
        Options.error_on_unknown_names = False

766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785
    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):
786
        try:
787 788 789 790
            from test import test_support as support
        except ImportError: # Py3k
            from test import support

791 792 793 794 795
        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)
796

797 798
            support.run_unittest = run_unittest
            support.run_doctest = run_doctest
799

800 801 802 803 804 805 806 807
            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)
808

809
include_debugger = sys.version_info[:2] > (2, 5)
810

811
def collect_unittests(path, module_prefix, suite, selectors):
812 813 814 815 816 817 818
    def file_matches(filename):
        return filename.startswith("Test") and filename.endswith(".py")

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

    loader = unittest.TestLoader()
819

820 821 822
    if include_debugger:
        skipped_dirs = []
    else:
823
        skipped_dirs = ['Cython' + os.path.sep + 'Debugger' + os.path.sep]
824

825
    for dirpath, dirnames, filenames in os.walk(path):
826 827 828 829 830 831 832 833 834
        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
835 836 837 838 839
        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")]
840
                    modulename = module_prefix + filepath[len(path)+1:].replace(os.path.sep, '.')
841 842
                    if not [ 1 for match in selectors if match(modulename) ]:
                        continue
843 844 845
                    module = __import__(modulename)
                    for x in modulename.split('.')[1:]:
                        module = getattr(module, x)
Robert Bradshaw's avatar
Robert Bradshaw committed
846
                    suite.addTests([loader.loadTestsFromModule(module)])
847

848 849


850 851
def collect_doctests(path, module_prefix, suite, selectors):
    def package_matches(dirname):
852 853
        if dirname == 'Debugger' and not include_debugger:
            return False
854 855
        return dirname not in ("Mac", "Distutils", "Plex")
    def file_matches(filename):
Mark Florisson's avatar
Mark Florisson committed
856
        filename, ext = os.path.splitext(filename)
857
        blacklist = ['libcython', 'libpython', 'test_libcython_in_gdb',
858
                     'TestLibCython']
Mark Florisson's avatar
Mark Florisson committed
859 860 861 862 863
        return (ext == '.py' and not
                '~' in filename and not
                '#' in filename and not
                filename.startswith('.') and not
                filename in blacklist)
864 865
    import doctest, types
    for dirpath, dirnames, filenames in os.walk(path):
Robert Bradshaw's avatar
Robert Bradshaw committed
866 867 868 869 870 871 872 873 874 875 876 877
        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
878 879 880
                if 'in_gdb' in modulename:
                    # These should only be imported from gdb.
                    continue
Robert Bradshaw's avatar
Robert Bradshaw committed
881 882 883 884 885 886 887 888
                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
889

890 891 892 893 894 895

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

898
    def __init__(self, treefile, workdir, cleanup_workdir=True):
899
        self.name = os.path.splitext(os.path.basename(treefile))[0]
900
        self.treefile = treefile
901
        self.workdir = os.path.join(workdir, self.name)
902
        self.cleanup_workdir = cleanup_workdir
903 904 905 906 907 908 909 910
        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
911 912 913
        unittest.TestCase.__init__(self)

    def shortDescription(self):
914
        return "End-to-end %s" % self.name
915 916 917

    def setUp(self):
        from Cython.TestUtils import unpack_source_tree
918
        _, self.commands = unpack_source_tree(self.treefile, self.workdir)
919 920 921 922 923 924 925
        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:
926 927 928 929 930 931 932
            for trial in range(5):
                try:
                    shutil.rmtree(self.workdir)
                except OSError:
                    time.sleep(0.1)
                else:
                    break
933
        os.chdir(self.old_dir)
934

935 936
    def runTest(self):
        commands = (self.commands
Robert Bradshaw's avatar
Robert Bradshaw committed
937
            .replace("CYTHON", "PYTHON %s" % os.path.join(self.cython_root, 'cython.py'))
938
            .replace("PYTHON", sys.executable))
939
        try:
Robert Bradshaw's avatar
Robert Bradshaw committed
940
            old_path = os.environ.get('PYTHONPATH')
Robert Bradshaw's avatar
Robert Bradshaw committed
941
            os.environ['PYTHONPATH'] = self.cython_syspath + os.pathsep + os.path.join(self.cython_syspath, (old_path or ''))
Robert Bradshaw's avatar
Robert Bradshaw committed
942 943 944 945 946 947 948 949 950 951
            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
952 953 954
                        print(command)
                        print(out)
                        print(err)
Robert Bradshaw's avatar
Robert Bradshaw committed
955 956 957
                else:
                    res = os.system(command)
                self.assertEqual(0, res, "non-zero exit status")
958
        finally:
959 960 961 962
            if old_path:
                os.environ['PYTHONPATH'] = old_path
            else:
                del os.environ['PYTHONPATH']
963 964


965 966 967 968
# TODO: Support cython_freeze needed here as well.
# TODO: Windows support.

class EmbedTest(unittest.TestCase):
969

970
    working_dir = "Demos/embed"
971

972 973 974
    def setUp(self):
        self.old_dir = os.getcwd()
        os.chdir(self.working_dir)
975
        os.system(
976
            "make PYTHON='%s' clean > /dev/null" % sys.executable)
977

978 979
    def tearDown(self):
        try:
980 981
            os.system(
                "make PYTHON='%s' clean > /dev/null" % sys.executable)
982 983 984
        except:
            pass
        os.chdir(self.old_dir)
985

986
    def test_embed(self):
987
        from distutils import sysconfig
988
        libname = sysconfig.get_config_var('LIBRARY')
989
        libdir = sysconfig.get_config_var('LIBDIR')
990 991 992 993 994 995 996
        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')
997
        cython = 'cython.py'
Stefan Behnel's avatar
Stefan Behnel committed
998
        if sys.version_info[0] >=3 and CY3_DIR:
999 1000
            cython = os.path.join(CY3_DIR, cython)
        cython = os.path.abspath(os.path.join('..', '..', cython))
1001
        self.assert_(os.system(
1002
            "make PYTHON='%s' CYTHON='%s' LIBDIR1='%s' test > make.output" % (sys.executable, cython, libdir)) == 0)
1003 1004 1005 1006
        try:
            os.remove('make.output')
        except OSError:
            pass
1007

1008 1009 1010 1011 1012 1013 1014 1015
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
1016
                self.exclude_matchers.append(string_selector(matcher))
1017
        self.tests_missing_deps = []
Robert Bradshaw's avatar
Robert Bradshaw committed
1018
    def __call__(self, testname, tags=None):
1019
        for matcher in self.exclude_matchers:
Robert Bradshaw's avatar
Robert Bradshaw committed
1020
            if matcher(testname, tags):
1021 1022 1023 1024
                self.tests_missing_deps.append(testname)
                return True
        return False

1025 1026 1027 1028 1029
class VersionDependencyExcluder:
    def __init__(self, deps):
        # deps: { version : matcher func }
        from sys import version_info
        self.exclude_matchers = []
1030 1031
        for ver, (compare, matcher) in deps.items():
            if compare(version_info, ver):
1032 1033
                self.exclude_matchers.append(matcher)
        self.tests_missing_deps = []
Robert Bradshaw's avatar
Robert Bradshaw committed
1034
    def __call__(self, testname, tags=None):
1035 1036 1037 1038 1039 1040
        for matcher in self.exclude_matchers:
            if matcher(testname):
                self.tests_missing_deps.append(testname)
                return True
        return False

1041 1042 1043 1044
class FileListExcluder:

    def __init__(self, list_file):
        self.excludes = {}
1045 1046 1047 1048 1049 1050 1051 1052
        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()
1053

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

Robert Bradshaw's avatar
Robert Bradshaw committed
1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084
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:])
        

1085 1086 1087 1088 1089 1090 1091 1092 1093 1094
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
1095
    dlog.set_threshold(dlog.INFO)
1096 1097 1098 1099 1100 1101
    copydir_run_2to3(distdir, cy3_dir, fixer_names=fixers,
                     template = '''
                     global-exclude *
                     graft Cython
                     recursive-exclude Cython *
                     recursive-include Cython *.py *.pyx *.pxd
1102
                     recursive-include Cython/Debugger/Tests *
1103
                     include runtests.py
1104
                     include cython.py
1105 1106 1107
                     ''')
    sys.path.insert(0, cy3_dir)

1108 1109 1110 1111
    for keep_2x_file in KEEP_2X_FILES:
        destfile = os.path.join(cy3_dir, keep_2x_file)
        shutil.copy(keep_2x_file, destfile)

1112 1113
class PendingThreadsError(RuntimeError):
    pass
1114

1115 1116 1117
threads_seen = []

def check_thread_termination(ignore_seen=True):
1118 1119 1120 1121 1122 1123 1124 1125 1126
    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():
1127 1128 1129
            if not ignore_seen:
                blocking_threads.append(t)
                continue
1130 1131 1132 1133 1134 1135
            for seen in threads_seen:
                if t is seen:
                    break
            else:
                threads_seen.append(t)
                blocking_threads.append(t)
1136 1137 1138 1139 1140
    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))
1141
    raise PendingThreadsError("left-over threads found after running test")
1142

1143
def main():
1144 1145 1146

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

1147 1148 1149
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option("--no-cleanup", dest="cleanup_workdir",
Stefan Behnel's avatar
Stefan Behnel committed
1150 1151
                      action="store_false", default=True,
                      help="do not delete the generated C files (allows passing --no-cython on next run)")
1152 1153 1154
    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
1155 1156 1157
    parser.add_option("--no-cython", dest="with_cython",
                      action="store_false", default=True,
                      help="do not run the Cython compiler, only the C compiler")
1158 1159
    parser.add_option("--compiler", dest="compiler", default=None,
                      help="C compiler type")
1160 1161 1162 1163 1164 1165
    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")
1166 1167 1168
    parser.add_option("--no-unit", dest="unittests",
                      action="store_false", default=True,
                      help="do not run the unit tests")
1169 1170 1171
    parser.add_option("--no-doctest", dest="doctests",
                      action="store_false", default=True,
                      help="do not run the doctests")
1172 1173 1174
    parser.add_option("--no-file", dest="filetests",
                      action="store_false", default=True,
                      help="do not run the file based tests")
1175 1176
    parser.add_option("--no-pyregr", dest="pyregr",
                      action="store_false", default=True,
1177
                      help="do not run the regression tests of CPython in tests/pyregr/")
1178
    parser.add_option("--cython-only", dest="cython_only",
1179 1180
                      action="store_true", default=False,
                      help="only compile pyx to c, do not run C compiler or run the tests")
1181
    parser.add_option("--no-refnanny", dest="with_refnanny",
Stefan Behnel's avatar
Stefan Behnel committed
1182
                      action="store_false", default=True,
1183
                      help="do not regression test reference counting")
1184 1185 1186
    parser.add_option("--no-fork", dest="fork",
                      action="store_false", default=True,
                      help="do not fork to run tests")
1187 1188 1189
    parser.add_option("--sys-pyregr", dest="system_pyregr",
                      action="store_true", default=False,
                      help="run the regression tests of the CPython installation")
1190 1191 1192
    parser.add_option("-x", "--exclude", dest="exclude",
                      action="append", metavar="PATTERN",
                      help="exclude tests matching the PATTERN")
1193
    parser.add_option("-C", "--coverage", dest="coverage",
Stefan Behnel's avatar
Stefan Behnel committed
1194 1195
                      action="store_true", default=False,
                      help="collect source coverage data for the Compiler")
1196 1197 1198
    parser.add_option("--coverage-xml", dest="coverage_xml",
                      action="store_true", default=False,
                      help="collect source coverage data for the Compiler in XML format")
1199 1200 1201
    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
1202
    parser.add_option("-A", "--annotate", dest="annotate_source",
1203
                      action="store_true", default=True,
Stefan Behnel's avatar
Stefan Behnel committed
1204
                      help="generate annotated HTML versions of the test source files")
1205 1206 1207
    parser.add_option("--no-annotate", dest="annotate_source",
                      action="store_false",
                      help="do not generate annotated HTML versions of the test source files")
1208
    parser.add_option("-v", "--verbose", dest="verbosity",
Stefan Behnel's avatar
Stefan Behnel committed
1209 1210
                      action="count", default=0,
                      help="display test progress, pass twice to print test names")
1211 1212
    parser.add_option("-T", "--ticket", dest="tickets",
                      action="append",
1213
                      help="a bug ticket number to run the respective test in 'tests/*'")
1214 1215 1216
    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)'")
1217 1218
    parser.add_option("--xml-output", dest="xml_output_dir", metavar="DIR",
                      help="write test results in XML to directory DIR")
1219 1220 1221
    parser.add_option("--exit-ok", dest="exit_ok", default=False,
                      action="store_true",
                      help="exit without error code even on test failures")
1222 1223 1224 1225
    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")
1226 1227 1228

    options, cmd_args = parser.parse_args()

1229 1230
    ROOTDIR = os.path.abspath(options.root_dir)
    WORKDIR = os.path.abspath(options.work_dir)
1231

1232 1233
    if sys.version_info[0] >= 3:
        options.doctests = False
1234
        if options.with_cython:
1235 1236 1237 1238
            try:
                # try if Cython is installed in a Py3 version
                import Cython.Compiler.Main
            except Exception:
Stefan Behnel's avatar
Stefan Behnel committed
1239 1240
                # back out anything the import process loaded, then
                # 2to3 the Cython sources to make them re-importable
1241
                cy_modules = [ name for name in sys.modules
Stefan Behnel's avatar
Stefan Behnel committed
1242
                               if name == 'Cython' or name.startswith('Cython.') ]
1243 1244 1245
                for name in cy_modules:
                    del sys.modules[name]
                # hasn't been refactored yet - do it now
1246 1247
                global CY3_DIR
                CY3_DIR = cy3_dir = os.path.join(WORKDIR, 'Cy3')
1248 1249 1250 1251 1252 1253
                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
1254

1255
    WITH_CYTHON = options.with_cython
1256

1257
    if options.coverage or options.coverage_xml or options.coverage_html:
1258
        if not WITH_CYTHON:
1259
            options.coverage = options.coverage_xml = options.coverage_html = False
1260
        else:
1261
            from coverage import coverage as _coverage
Stefan Behnel's avatar
Stefan Behnel committed
1262
            coverage = _coverage(branch=True)
1263 1264 1265
            coverage.erase()
            coverage.start()

1266
    if WITH_CYTHON:
1267
        global CompilationOptions, pyrex_default_options, cython_compile
1268 1269 1270 1271
        from Cython.Compiler.Main import \
            CompilationOptions, \
            default_options as pyrex_default_options, \
            compile as cython_compile
1272 1273
        from Cython.Compiler import Errors
        Errors.LEVEL = 0 # show all warnings
Stefan Behnel's avatar
Stefan Behnel committed
1274
        from Cython.Compiler import Options
1275
        Options.generate_cleanup_code = 3   # complete cleanup code
Stefan Behnel's avatar
Stefan Behnel committed
1276 1277
        from Cython.Compiler import DebugFlags
        DebugFlags.debug_temp_code_comments = 1
1278

1279
    # RUN ALL TESTS!
1280
    UNITTEST_MODULE = "Cython"
1281
    UNITTEST_ROOT = os.path.join(os.path.dirname(__file__), UNITTEST_MODULE)
1282 1283
    if WITH_CYTHON:
        if os.path.exists(WORKDIR):
1284
            for path in os.listdir(WORKDIR):
1285
                if path in ("support", "Cy3"): continue
1286
                shutil.rmtree(os.path.join(WORKDIR, path), ignore_errors=True)
1287 1288
    if not os.path.exists(WORKDIR):
        os.makedirs(WORKDIR)
1289

1290 1291
    sys.stderr.write("Python %s\n" % sys.version)
    sys.stderr.write("\n")
1292 1293
    if WITH_CYTHON:
        from Cython.Compiler.Version import version
1294
        sys.stderr.write("Running tests against Cython %s\n" % version)
1295
    else:
1296
        sys.stderr.write("Running tests without Cython.\n")
1297

1298 1299 1300 1301 1302
    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"))
1303
        sys.path.insert(0, os.path.split(libpath)[0])
1304
        CFLAGS.append("-DCYTHON_REFNANNY=1")
1305

Stefan Behnel's avatar
Stefan Behnel committed
1306 1307 1308 1309 1310
    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

1311 1312 1313 1314 1315
    if WITH_CYTHON and options.language_level == 3:
        sys.stderr.write("Using Cython language level 3.\n")

    sys.stderr.write("\n")

1316
    test_bugs = False
Stefan Behnel's avatar
Stefan Behnel committed
1317 1318 1319
    if options.tickets:
        for ticket_number in options.tickets:
            test_bugs = True
1320
            cmd_args.append('ticket:%s' % ticket_number)
1321 1322 1323 1324
    if not test_bugs:
        for selector in cmd_args:
            if selector.startswith('bugs'):
                test_bugs = True
1325

1326
    import re
Robert Bradshaw's avatar
Robert Bradshaw committed
1327
    selectors = [ string_selector(r) for r in cmd_args ]
1328
    if not selectors:
Robert Bradshaw's avatar
Robert Bradshaw committed
1329
        selectors = [ lambda x, tags=None: True ]
1330

1331 1332 1333
    # Chech which external modules are not present and exclude tests
    # which depends on them (by prefix)

1334 1335
    missing_dep_excluder = MissingDependencyExcluder(EXT_DEP_MODULES)
    version_dep_excluder = VersionDependencyExcluder(VER_DEP_MODULES)
1336
    exclude_selectors = [missing_dep_excluder, version_dep_excluder] # want to pring msg at exit
1337

1338
    if options.exclude:
Robert Bradshaw's avatar
Robert Bradshaw committed
1339
        exclude_selectors += [ string_selector(r) for r in options.exclude ]
1340

1341
    if not test_bugs:
1342
        exclude_selectors += [ FileListExcluder(os.path.join(ROOTDIR, "bugs.txt")) ]
1343

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

1347 1348 1349
    global COMPILER
    if options.compiler:
        COMPILER = options.compiler
1350 1351 1352 1353 1354 1355
    languages = []
    if options.use_c:
        languages.append('c')
    if options.use_cpp:
        languages.append('cpp')

1356 1357 1358
    test_suite = unittest.TestSuite()

    if options.unittests:
1359
        collect_unittests(UNITTEST_ROOT, UNITTEST_MODULE + ".", test_suite, selectors)
1360

1361 1362
    if options.doctests:
        collect_doctests(UNITTEST_ROOT, UNITTEST_MODULE + ".", test_suite, selectors)
1363

1364
    if options.filetests and languages:
1365
        filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors,
1366
                                options.annotate_source, options.cleanup_workdir,
Stefan Behnel's avatar
Stefan Behnel committed
1367
                                options.cleanup_sharedlibs, options.pyregr,
1368
                                options.cython_only, languages, test_bugs,
1369
                                options.fork, options.language_level)
1370 1371
        test_suite.addTest(filetests.build_suite())

1372
    if options.system_pyregr and languages:
1373 1374 1375 1376 1377 1378
        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,
1379
                                    options.fork, sys.version_info[0])
1380 1381
            sys.stderr.write("Including CPython regression tests in %s\n" % sys_pyregr_dir)
            test_suite.addTest(filetests.handle_directory(sys_pyregr_dir, 'pyregr'))
1382

Stefan Behnel's avatar
Stefan Behnel committed
1383
    if options.xml_output_dir:
1384
        from Cython.Tests.xmlrunner import XMLTestRunner
Stefan Behnel's avatar
Stefan Behnel committed
1385 1386
        test_runner = XMLTestRunner(output=options.xml_output_dir,
                                    verbose=options.verbosity > 0)
1387 1388 1389 1390
    else:
        test_runner = unittest.TextTestRunner(verbosity=options.verbosity)

    result = test_runner.run(test_suite)
1391

1392
    if options.coverage or options.coverage_xml or options.coverage_html:
1393
        coverage.stop()
1394
        ignored_modules = ('Options', 'Version', 'DebugFlags', 'CmdLine')
1395 1396
        modules = [ module for name, module in sys.modules.items()
                    if module is not None and
1397
                    name.startswith('Cython.Compiler.') and
1398
                    name[len('Cython.Compiler.'):] not in ignored_modules ]
1399 1400 1401
        if options.coverage:
            coverage.report(modules, show_missing=0)
        if options.coverage_xml:
1402
            coverage.xml_report(modules, outfile="coverage-report.xml")
1403 1404
        if options.coverage_html:
            coverage.html_report(modules, directory="coverage-report-html")
1405 1406 1407 1408 1409

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

1411
    if options.with_refnanny:
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
1412
        import refnanny
1413
        sys.stderr.write("\n".join([repr(x) for x in refnanny.reflog]))
1414

1415 1416
    print("ALL DONE")

1417
    if options.exit_ok:
1418
        return_code = 0
1419
    else:
1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431
        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
1432 1433
    except SystemExit: # <= Py2.4 ...
        raise
1434 1435 1436 1437 1438 1439 1440
    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)