fix #480: float() as a type cast for function return values
[cython.git] / runtests.py
index b6c01abf7bcd80a74d654f6875edaa279f61d32f..9ac487f7b5e77d455711687efa048850e9894488 100644 (file)
@@ -3,11 +3,13 @@
 import os
 import sys
 import re
+import gc
 import codecs
 import shutil
 import unittest
 import doctest
 import operator
+import tempfile
 try:
     from StringIO import StringIO
 except ImportError:
@@ -32,7 +34,8 @@ TEST_RUN_DIRS = ['run', 'pyregr']
 # Lists external modules, and a matcher matching tests
 # which should be excluded if the module is not present.
 EXT_DEP_MODULES = {
-    'numpy' : re.compile('.*\.numpy_.*').match
+    'numpy' : re.compile('.*\.numpy_.*').match,
+    'pstats' : re.compile('.*\.pstats_.*').match
 }
 
 def get_numpy_include_dirs():
@@ -99,7 +102,7 @@ class ErrorWriter(object):
 class TestBuilder(object):
     def __init__(self, rootdir, workdir, selectors, exclude_selectors, annotate,
                  cleanup_workdir, cleanup_sharedlibs, with_pyregr, cython_only,
-                 languages, test_bugs):
+                 languages, test_bugs, fork):
         self.rootdir = rootdir
         self.workdir = workdir
         self.selectors = selectors
@@ -111,6 +114,7 @@ class TestBuilder(object):
         self.cython_only = cython_only
         self.languages = languages
         self.test_bugs = test_bugs
+        self.fork = fork
 
     def build_suite(self):
         suite = unittest.TestSuite()
@@ -192,12 +196,13 @@ class TestBuilder(object):
                           annotate=self.annotate,
                           cleanup_workdir=self.cleanup_workdir,
                           cleanup_sharedlibs=self.cleanup_sharedlibs,
-                          cython_only=self.cython_only)
+                          cython_only=self.cython_only,
+                          fork=self.fork)
 
 class CythonCompileTestCase(unittest.TestCase):
     def __init__(self, directory, workdir, module, language='c',
                  expect_errors=False, annotate=False, cleanup_workdir=True,
-                 cleanup_sharedlibs=True, cython_only=False):
+                 cleanup_sharedlibs=True, cython_only=False, fork=True):
         self.directory = directory
         self.workdir = workdir
         self.module = module
@@ -207,6 +212,7 @@ class CythonCompileTestCase(unittest.TestCase):
         self.cleanup_workdir = cleanup_workdir
         self.cleanup_sharedlibs = cleanup_sharedlibs
         self.cython_only = cython_only
+        self.fork = fork
         unittest.TestCase.__init__(self)
 
     def shortDescription(self):
@@ -391,50 +397,68 @@ class CythonRunTestCase(CythonCompileTestCase):
             pass
 
     def run_doctests(self, module_name, result):
-        if sys.version_info[0] >= 3 or not hasattr(os, 'fork'):
+        if sys.version_info[0] >= 3 or not hasattr(os, 'fork') or not self.fork:
             doctest.DocTestSuite(module_name).run(result)
+            gc.collect()
             return
 
         # fork to make sure we do not keep the tested module loaded
-        input, output = os.pipe()
+        result_handle, result_file = tempfile.mkstemp()
+        os.close(result_handle)
         child_id = os.fork()
         if not child_id:
             result_code = 0
             try:
-                output = os.fdopen(output, 'wb')
-                tests = None
                 try:
-                    partial_result = PartialTestResult(result)
-                    tests = doctest.DocTestSuite(module_name)
-                    tests.run(partial_result)
-                except Exception:
-                    if tests is None:
-                        # importing failed, try to fake a test class
-                        tests = _FakeClass(
-                            failureException=None,
-                            **{module_name: None})
-                    partial_result.addError(tests, sys.exc_info())
-                    result_code = 1
-                pickle.dump(partial_result.data(), output)
+                    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(
+                                failureException=None,
+                                shortDescription = self.shortDescription,
+                                **{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:
+                    import traceback
+                    traceback.print_exc()
             finally:
                 try: output.close()
                 except: pass
                 os._exit(result_code)
 
-        input = os.fdopen(input, 'rb')
-        PartialTestResult.join_results(result, pickle.load(input))
-        cid, result_code = os.waitpid(child_id, 0)
-        if result_code:
-            raise Exception("Tests in module '%s' exited with status %d" %
-                            (module_name, result_code >> 8))
+        try:
+            cid, result_code = os.waitpid(child_id, 0)
+            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 >> 8))
+        finally:
+            try: os.unlink(result_file)
+            except: pass
 
 
 is_private_field = re.compile('^_[^_]').match
 
 class _FakeClass(object):
     def __init__(self, **kwargs):
-        self.shortDescription = lambda x: kwargs.get('module_name')
+        self._shortDescription = kwargs.get('module_name')
         self.__dict__.update(kwargs)
+    def shortDescription(self):
+        return self._shortDescription
 
 try: # Py2.7+ and Py3.2+
     from unittest.runner import _TextTestResult
@@ -663,6 +687,9 @@ if __name__ == '__main__':
     parser.add_option("--no-refnanny", dest="with_refnanny",
                       action="store_false", default=True,
                       help="do not regression test reference counting")
+    parser.add_option("--no-fork", dest="fork",
+                      action="store_false", default=True,
+                      help="do not fork to run tests")
     parser.add_option("--sys-pyregr", dest="system_pyregr",
                       action="store_true", default=False,
                       help="run the regression tests of the CPython installation")
@@ -716,8 +743,13 @@ if __name__ == '__main__':
                              ''')
             sys.path.insert(0, cy3_dir)
     elif sys.version_info[0] >= 3:
-        # make sure we do not import (or run) Cython itself
-        options.with_cython = False
+        # make sure we do not import (or run) Cython itself (unless
+        # 2to3 was already run)
+        cy3_dir = os.path.join(WORKDIR, 'Cy3')
+        if os.path.isdir(cy3_dir):
+            sys.path.insert(0, cy3_dir)
+        else:
+            options.with_cython = False
         options.doctests    = False
         options.unittests   = False
         options.pyregr      = False
@@ -795,6 +827,9 @@ if __name__ == '__main__':
     
     if not test_bugs:
         exclude_selectors += [ FileListExcluder("tests/bugs.txt") ]
+    
+    if sys.platform in ['win32', 'cygwin'] and sys.version_info < (2,6):
+        exclude_selectors += [ lambda x: x == "run.specialfloat" ]
 
     languages = []
     if options.use_c:
@@ -814,20 +849,22 @@ if __name__ == '__main__':
         filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors,
                                 options.annotate_source, options.cleanup_workdir,
                                 options.cleanup_sharedlibs, options.pyregr,
-                                options.cython_only, languages, test_bugs)
+                                options.cython_only, languages, test_bugs,
+                                options.fork)
         test_suite.addTest(filetests.build_suite())
 
     if options.system_pyregr and languages:
         filetests = TestBuilder(ROOTDIR, WORKDIR, selectors, exclude_selectors,
                                 options.annotate_source, options.cleanup_workdir,
                                 options.cleanup_sharedlibs, True,
-                                options.cython_only, languages, test_bugs)
+                                options.cython_only, languages, test_bugs,
+                                options.fork)
         test_suite.addTest(
             filetests.handle_directory(
                 os.path.join(sys.prefix, 'lib', 'python'+sys.version[:3], 'test'),
                 'pyregr'))
 
-    unittest.TextTestRunner(verbosity=options.verbosity).run(test_suite)
+    result = unittest.TextTestRunner(verbosity=options.verbosity).run(test_suite)
 
     if options.coverage:
         coverage.stop()
@@ -846,3 +883,5 @@ if __name__ == '__main__':
     if options.with_refnanny:
         import refnanny
         sys.stderr.write("\n".join([repr(x) for x in refnanny.reflog]))
+
+    sys.exit(not result.wasSuccessful())