cleanup in runtests.py, support for testing compiler errors ('tests/errors/' directory)
authorStefan Behnel <scoder@users.berlios.de>
Mon, 28 Apr 2008 08:33:38 +0000 (10:33 +0200)
committerStefan Behnel <scoder@users.berlios.de>
Mon, 28 Apr 2008 08:33:38 +0000 (10:33 +0200)
runtests.py

index 1c3441bbf2f6bb5effbe59d1c0e5be92e99dd4a1..85d9e99757807c97e9d7a36c1868f5e76c342aec 100644 (file)
@@ -1,19 +1,40 @@
 #!/usr/bin/python
 
-import os, sys, unittest, doctest
+import os, sys, re, shutil, unittest, doctest
 
-from Cython.Distutils.extension import Extension
-from Cython.Distutils import build_ext
+from Cython.Compiler.Main import \
+    CompilationOptions, \
+    default_options as pyrex_default_options, \
+    compile as cython_compile
 
 from distutils.dist import Distribution
+from distutils.core import Extension
+from distutils.command.build_ext import build_ext
 distutils_distro = Distribution()
 
-TEST_DIRS = ['compile', 'run']
+TEST_DIRS = ['compile', 'errors', 'run']
 TEST_RUN_DIRS = ['run']
 
 INCLUDE_DIRS = [ d for d in os.getenv('INCLUDE', '').split(os.pathsep) if d ]
 CFLAGS = os.getenv('CFLAGS', '').split()
 
+
+class ErrorWriter(object):
+    match_error = re.compile('(?:.*:)?([-0-9]+):([-0-9]+):(.*)').match
+    def __init__(self):
+        self.output = []
+        self.write = self.output.append
+
+    def geterrors(self):
+        s = ''.join(self.output)
+        errors = []
+        for line in s.split('\n'):
+            match = self.match_error(line)
+            if match:
+                line, column, message = match.groups()
+                errors.append( "%d:%d:%s" % (int(line), int(column), message.strip()) )
+        return errors
+
 class TestBuilder(object):
     def __init__(self, rootdir, workdir, selectors):
         self.rootdir = rootdir
@@ -30,6 +51,7 @@ class TestBuilder(object):
         return suite
 
     def handle_directory(self, path, context):
+        expect_errors = (context == 'errors')
         suite = unittest.TestSuite()
         for filename in os.listdir(path):
             if not filename.endswith(".pyx"):
@@ -39,59 +61,120 @@ class TestBuilder(object):
             if not [ 1 for match in self.selectors
                      if match(fqmodule) ]:
                 continue
-            suite.addTest(
-                CythonCompileTestCase(path, self.workdir, module))
             if context in TEST_RUN_DIRS:
-                suite.addTest(
-                    CythonRunTestCase(self.workdir, module))
+                test = CythonRunTestCase(path, self.workdir, module)
+            else:
+                test = CythonCompileTestCase(
+                    path, self.workdir, module, expect_errors)
+            suite.addTest(test)
         return suite
 
 class CythonCompileTestCase(unittest.TestCase):
-    def __init__(self, directory, workdir, module):
+    def __init__(self, directory, workdir, module, expect_errors=False):
         self.directory = directory
         self.workdir = workdir
         self.module = module
+        self.expect_errors = expect_errors
         unittest.TestCase.__init__(self)
 
     def shortDescription(self):
         return "compiling " + self.module
 
-    def runTest(self):
-        self.compile(self.directory, self.module, self.workdir)
+    def setUp(self):
+        if os.path.exists(self.workdir):
+            shutil.rmtree(self.workdir, ignore_errors=True)
+        os.makedirs(self.workdir)
 
-    def compile(self, directory, module, workdir):
+    def runTest(self):
+        self.compile(self.directory, self.module, self.workdir,
+                     self.directory, self.expect_errors)
+
+    def split_source_and_output(self, directory, module, workdir):
+        source_and_output = open(os.path.join(directory, module + '.pyx'), 'rU')
+        out = open(os.path.join(workdir, module + '.pyx'), 'w')
+        for line in source_and_output:
+            last_line = line
+            if line.startswith("_ERRORS"):
+                out.close()
+                out = ErrorWriter()
+            else:
+                out.write(line)
+        try:
+            geterrors = out.geterrors
+        except AttributeError:
+            return []
+        else:
+            return geterrors()
+
+    def run_cython(self, directory, module, targetdir, incdir):
+        include_dirs = INCLUDE_DIRS[:]
+        if incdir:
+            include_dirs.append(incdir)
+        source = os.path.join(directory, module + '.pyx')
+        target = os.path.join(targetdir, module + '.c')
+        options = CompilationOptions(
+            pyrex_default_options,
+            include_path = include_dirs,
+            output_file = target,
+            use_listing_file = False, cplus = False, generate_pxi = False)
+        cython_compile(source, options=options,
+                       full_module_name=module)
+
+    def run_distutils(self, module, workdir, incdir):
         build_extension = build_ext(distutils_distro)
         build_extension.include_dirs = INCLUDE_DIRS[:]
-        build_extension.include_dirs.append(directory)
+        if incdir:
+            build_extension.include_dirs.append(incdir)
         build_extension.finalize_options()
 
         extension = Extension(
             module,
-            sources = [os.path.join(directory, module + '.pyx')],
+            sources = [os.path.join(workdir, module + '.c')],
             extra_compile_args = CFLAGS,
-            pyrex_c_in_temp = 1
             )
         build_extension.extensions = [extension]
         build_extension.build_temp = workdir
         build_extension.build_lib  = workdir
-        build_extension.pyrex_c_in_temp = 1
         build_extension.run()
 
-class CythonRunTestCase(unittest.TestCase):
-    def __init__(self, rootdir, module):
-        self.rootdir, self.module = rootdir, module
-        unittest.TestCase.__init__(self)
+    def compile(self, directory, module, workdir, incdir, expect_errors):
+        expected_errors = errors = ()
+        if expect_errors:
+            expected_errors = self.split_source_and_output(
+                directory, module, workdir)
+            directory = workdir
 
+        old_stderr = sys.stderr
+        try:
+            sys.stderr = ErrorWriter()
+            self.run_cython(directory, module, workdir, incdir)
+            errors = sys.stderr.geterrors()
+        finally:
+            sys.stderr = old_stderr
+
+        if errors or expected_errors:
+            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)
+        else:
+            self.run_distutils(module, workdir, incdir)
+
+class CythonRunTestCase(CythonCompileTestCase):
     def shortDescription(self):
-        return "running " + self.module
+        return "compiling and running " + self.module
 
     def runTest(self):
         self.run()
 
     def run(self, result=None):
-        if not sys.path or sys.path[0] != self.rootdir:
-            sys.path.insert(0, self.rootdir)
-        if result is None: result = self.defaultTestResult()
+        if result is None:
+            result = self.defaultTestResult()
+        CythonCompileTestCase.runTest(self)
         try:
             try:
                 doctest.DocTestSuite(self.module).run(result)
@@ -111,6 +194,9 @@ if __name__ == '__main__':
     if not os.path.exists(WORKDIR):
         os.makedirs(WORKDIR)
 
+    if not sys.path or sys.path[0] != WORKDIR:
+        sys.path.insert(0, WORKDIR)
+
     try:
         sys.argv.remove("-C")
     except ValueError: