work-around for ticket #691: look up unknown global names in builtins at runtime
[cython.git] / runtests.py
index 3dce408c0d9819e3188c12fd4cd6485a1322caaa..e9d09f1562f12ea5d12ce986601e06812ffa9f19 100644 (file)
@@ -22,6 +22,11 @@ try:
 except ImportError:
     import pickle
 
+try:
+    from io import open as io_open
+except ImportError:
+    from codecs import open as io_open
+
 try:
     import threading
 except ImportError: # No threads, no problems
@@ -63,25 +68,23 @@ if sys.platform == 'win32':
     except ValueError: pass
     distutils_distro.parse_config_files(cfgfiles)
 
-# Lists external modules, and a matcher matching tests
-# which should be excluded if the module is not present.
-# TODO: use tags for these
 EXT_DEP_MODULES = {
-    'numpy' : re.compile('.*\.numpy_.*').match,
-    'pstats' : re.compile('.*\.pstats_.*').match,
-    'posix' : re.compile('.*\.posix_.*').match,
+    'numpy' : 'tag:numpy',
+    'pstats' : 'tag:pstats',
+    'posix' : 'tag:posix',
 }
 
 def get_numpy_include_dirs():
     import numpy
     return [numpy.get_include()]
 
-
+# TODO: use tags
 EXT_DEP_INCLUDES = [
     # test name matcher , callable returning list
     (re.compile('numpy_.*').match, get_numpy_include_dirs),
 ]
 
+# TODO: use tags
 VER_DEP_MODULES = {
     # tests are excluded if 'CurrentPythonVersion OP VersionTuple', i.e.
     # (2,4) : (operator.lt, ...) excludes ... when PyVer < 2.4.x
@@ -90,16 +93,23 @@ VER_DEP_MODULES = {
                                           ]),
     (2,5) : (operator.lt, lambda x: x in ['run.any',
                                           'run.all',
+                                          'run.relativeimport_T542',
+                                          'run.relativeimport_star_T542',
                                           ]),
     (2,6) : (operator.lt, lambda x: x in ['run.print_function',
                                           'run.cython3',
+                                          'run.generators_py', # generators, with statement
                                           'run.pure_py', # decorators, with statement
                                           ]),
+    (2,7) : (operator.lt, lambda x: x in ['run.withstat_py', # multi context with statement
+                                          ]),
     # 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']),
+    (2,999): (operator.lt, lambda x: x in ['run.special_methods_T561_py3',
+                                           'run.test_raisefrom',
+                                           ]),
     (3,): (operator.ge, lambda x: x in ['run.non_future_division',
                                         'compile.extsetslice',
                                         'compile.extdelslice',
@@ -130,17 +140,21 @@ def memoize(f):
 
 def parse_tags(filepath):
     tags = defaultdict(list)
-    for line in open(filepath):
-        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])
+    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()
     return tags
 
 parse_tags = memoize(parse_tags)
@@ -283,6 +297,11 @@ class TestBuilder(object):
         return suite
 
     def build_tests(self, test_class, path, workdir, module, expect_errors, tags):
+        if 'werror' in tags['tags']:
+            warning_errors = True
+        else:
+            warning_errors = False
+
         if expect_errors:
             if 'cpp' in tags['tag'] and 'cpp' in self.languages:
                 languages = ['cpp']
@@ -294,12 +313,12 @@ class TestBuilder(object):
             languages = list(languages)
             languages.remove('c')
         tests = [ self.build_test(test_class, path, workdir, module,
-                                  language, expect_errors)
+                                  language, expect_errors, warning_errors)
                   for language in languages ]
         return tests
 
     def build_test(self, test_class, path, workdir, module,
-                   language, expect_errors):
+                   language, expect_errors, warning_errors):
         workdir = os.path.join(workdir, language)
         if not os.path.exists(workdir):
             os.makedirs(workdir)
@@ -311,13 +330,14 @@ class TestBuilder(object):
                           cleanup_sharedlibs=self.cleanup_sharedlibs,
                           cython_only=self.cython_only,
                           fork=self.fork,
-                          language_level=self.language_level)
+                          language_level=self.language_level,
+                          warning_errors=warning_errors)
 
 class CythonCompileTestCase(unittest.TestCase):
     def __init__(self, test_directory, workdir, module, language='c',
                  expect_errors=False, annotate=False, cleanup_workdir=True,
                  cleanup_sharedlibs=True, cython_only=False, fork=True,
-                 language_level=2):
+                 language_level=2, warning_errors=False):
         self.test_directory = test_directory
         self.workdir = workdir
         self.module = module
@@ -329,16 +349,26 @@ class CythonCompileTestCase(unittest.TestCase):
         self.cython_only = cython_only
         self.fork = fork
         self.language_level = language_level
+        self.warning_errors = warning_errors
         unittest.TestCase.__init__(self)
 
     def shortDescription(self):
         return "compiling (%s) %s" % (self.language, self.module)
 
     def setUp(self):
+        from Cython.Compiler import Options
+        self._saved_options = [ (name, getattr(Options, name))
+                                for name in ('warning_errors', 'error_on_unknown_names') ]
+        Options.warning_errors = self.warning_errors
+
         if self.workdir not in sys.path:
             sys.path.insert(0, self.workdir)
 
     def tearDown(self):
+        from Cython.Compiler import Options
+        for name, value in self._saved_options:
+            setattr(Options, name, value)
+
         try:
             sys.path.remove(self.workdir)
         except ValueError:
@@ -400,10 +430,10 @@ class CythonCompileTestCase(unittest.TestCase):
 
     def split_source_and_output(self, test_directory, module, workdir):
         source_file = self.find_module_source_file(os.path.join(test_directory, module) + '.pyx')
-        source_and_output = codecs.open(source_file, 'rU', 'ISO-8859-1')
+        source_and_output = io_open(source_file, 'rU', encoding='ISO-8859-1')
         try:
-            out = codecs.open(os.path.join(workdir, module + os.path.splitext(source_file)[1]),
-                              'w', 'ISO-8859-1')
+            out = io_open(os.path.join(workdir, module + os.path.splitext(source_file)[1]),
+                              'w', encoding='ISO-8859-1')
             for line in source_and_output:
                 last_line = line
                 if line.startswith("_ERRORS"):
@@ -560,64 +590,70 @@ class CythonRunTestCase(CythonCompileTestCase):
             self.run_doctests(self.module, result)
 
     def run_doctests(self, module_name, result):
-        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
-        result_handle, result_file = tempfile.mkstemp()
-        os.close(result_handle)
-        child_id = os.fork()
-        if not child_id:
-            result_code = 0
-            try:
-                try:
-                    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=sys.exc_info()[1],
-                                _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:
-                    traceback.print_exc()
-            finally:
-                try: output.close()
-                except: pass
-                os._exit(result_code)
+        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):
+    if not fork or sys.version_info[0] >= 3 or not hasattr(os, 'fork'):
+        run_func(result)
+        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
         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:
+                tests = None
                 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))
+                    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()
         finally:
-            try: os.unlink(result_file)
+            try: output.close()
             except: pass
+            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
 
 class PureDoctestTestCase(unittest.TestCase):
     def __init__(self, module_name, module_path):
@@ -715,6 +751,11 @@ class CythonUnitTestCase(CythonRunTestCase):
 
 
 class CythonPyregrTestCase(CythonRunTestCase):
+    def setUp(self):
+        CythonRunTestCase.setUp(self)
+        from Cython.Compiler import Options
+        Options.error_on_unknown_names = False
+
     def _run_unittest(self, result, *classes):
         """Run tests from unittest.TestCase-derived classes."""
         valid_types = (unittest.TestSuite, unittest.TestCase)
@@ -740,20 +781,23 @@ class CythonPyregrTestCase(CythonRunTestCase):
         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)
+        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)
 
-        support.run_unittest = run_unittest
-        support.run_doctest = run_doctest
+            support.run_unittest = run_unittest
+            support.run_doctest = run_doctest
 
-        try:
-            module = __import__(self.module)
-            if hasattr(module, 'test_main'):
-                module.test_main()
-        except (unittest.SkipTest, support.ResourceDenied):
-            result.addSkip(self, 'ok')
+            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)
 
 include_debugger = sys.version_info[:2] > (2, 5)
 
@@ -962,11 +1006,11 @@ class MissingDependencyExcluder:
             try:
                 __import__(mod)
             except ImportError:
-                self.exclude_matchers.append(matcher)
+                self.exclude_matchers.append(string_selector(matcher))
         self.tests_missing_deps = []
     def __call__(self, testname, tags=None):
         for matcher in self.exclude_matchers:
-            if matcher(testname):
+            if matcher(testname, tags):
                 self.tests_missing_deps.append(testname)
                 return True
         return False
@@ -1001,7 +1045,6 @@ class FileListExcluder:
             f.close()
 
     def __call__(self, testname, tags=None):
-        print testname
         return testname in self.excludes or testname.split('.')[-1] in self.excludes
 
 class TagsSelector:
@@ -1264,7 +1307,7 @@ def main():
     if options.tickets:
         for ticket_number in options.tickets:
             test_bugs = True
-            cmd_args.append('.*T%s$' % ticket_number)
+            cmd_args.append('ticket:%s' % ticket_number)
     if not test_bugs:
         for selector in cmd_args:
             if selector.startswith('bugs'):
@@ -1317,15 +1360,15 @@ def main():
         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.fork, options.language_level)
-        test_suite.addTest(
-            filetests.handle_directory(
-                os.path.join(sys.prefix, 'lib', 'python'+sys.version[:3], 'test'),
-                'pyregr'))
+        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,
+                                    options.fork, sys.version_info[0])
+            sys.stderr.write("Including CPython regression tests in %s\n" % sys_pyregr_dir)
+            test_suite.addTest(filetests.handle_directory(sys_pyregr_dir, 'pyregr'))
 
     if options.xml_output_dir:
         from Cython.Tests.xmlrunner import XMLTestRunner