3 import os, sys, re, shutil, unittest, doctest
8 from distutils.dist import Distribution
9 from distutils.core import Extension
10 from distutils.command.build_ext import build_ext
11 distutils_distro = Distribution()
13 TEST_DIRS = ['compile', 'errors', 'run']
14 TEST_RUN_DIRS = ['run']
16 INCLUDE_DIRS = [ d for d in os.getenv('INCLUDE', '').split(os.pathsep) if d ]
17 CFLAGS = os.getenv('CFLAGS', '').split()
20 class ErrorWriter(object):
21 match_error = re.compile('(?:.*:)?([-0-9]+):([-0-9]+):(.*)').match
24 self.write = self.output.append
27 s = ''.join(self.output)
29 for line in s.split('\n'):
30 match = self.match_error(line)
32 line, column, message = match.groups()
33 errors.append( "%d:%d:%s" % (int(line), int(column), message.strip()) )
36 class TestBuilder(object):
37 def __init__(self, rootdir, workdir, selectors, annotate):
38 self.rootdir = rootdir
39 self.workdir = workdir
40 self.selectors = selectors
41 self.annotate = annotate
43 def build_suite(self):
44 suite = unittest.TestSuite()
45 filenames = os.listdir(self.rootdir)
47 for filename in filenames:
48 if not WITH_CYTHON and filename == "errors":
49 # we won't get any errors without running Cython
51 path = os.path.join(self.rootdir, filename)
52 if os.path.isdir(path) and filename in TEST_DIRS:
54 self.handle_directory(path, filename))
57 def handle_directory(self, path, context):
58 workdir = os.path.join(self.workdir, context)
59 if not os.path.exists(workdir):
61 if workdir not in sys.path:
62 sys.path.insert(0, workdir)
64 expect_errors = (context == 'errors')
65 suite = unittest.TestSuite()
66 filenames = os.listdir(path)
68 for filename in filenames:
69 if not filename.endswith(".pyx"):
71 module = filename[:-4]
72 fqmodule = "%s.%s" % (context, module)
73 if not [ 1 for match in self.selectors
76 if context in TEST_RUN_DIRS:
77 test = CythonRunTestCase(
78 path, workdir, module, self.annotate)
80 test = CythonCompileTestCase(
81 path, workdir, module, expect_errors, self.annotate)
85 class CythonCompileTestCase(unittest.TestCase):
86 def __init__(self, directory, workdir, module,
87 expect_errors=False, annotate=False):
88 self.directory = directory
89 self.workdir = workdir
91 self.expect_errors = expect_errors
92 self.annotate = annotate
93 unittest.TestCase.__init__(self)
95 def shortDescription(self):
96 return "compiling " + self.module
99 cleanup_c_files = WITH_CYTHON and CLEANUP_WORKDIR
100 if os.path.exists(self.workdir):
101 for rmfile in os.listdir(self.workdir):
102 if not cleanup_c_files and rmfile[-2:] in (".c", ".h"):
104 if self.annotate and rmfile.endswith(".html"):
107 rmfile = os.path.join(self.workdir, rmfile)
108 if os.path.isdir(rmfile):
109 shutil.rmtree(rmfile, ignore_errors=True)
115 os.makedirs(self.workdir)
118 self.compile(self.directory, self.module, self.workdir,
119 self.directory, self.expect_errors, self.annotate)
121 def split_source_and_output(self, directory, module, workdir):
122 source_and_output = open(os.path.join(directory, module + '.pyx'), 'rU')
123 out = open(os.path.join(workdir, module + '.pyx'), 'w')
124 for line in source_and_output:
126 if line.startswith("_ERRORS"):
132 geterrors = out.geterrors
133 except AttributeError:
138 def run_cython(self, directory, module, targetdir, incdir, annotate):
139 include_dirs = INCLUDE_DIRS[:]
141 include_dirs.append(incdir)
142 source = os.path.join(directory, module + '.pyx')
143 target = os.path.join(targetdir, module + '.c')
144 options = CompilationOptions(
145 pyrex_default_options,
146 include_path = include_dirs,
147 output_file = target,
149 use_listing_file = False, cplus = False, generate_pxi = False)
150 cython_compile(source, options=options,
151 full_module_name=module)
153 def run_distutils(self, module, workdir, incdir):
157 build_extension = build_ext(distutils_distro)
158 build_extension.include_dirs = INCLUDE_DIRS[:]
160 build_extension.include_dirs.append(incdir)
161 build_extension.finalize_options()
163 extension = Extension(
165 sources = [module + '.c'],
166 extra_compile_args = CFLAGS,
168 build_extension.extensions = [extension]
169 build_extension.build_temp = workdir
170 build_extension.build_lib = workdir
171 build_extension.run()
175 def compile(self, directory, module, workdir, incdir,
176 expect_errors, annotate):
177 expected_errors = errors = ()
179 expected_errors = self.split_source_and_output(
180 directory, module, workdir)
184 old_stderr = sys.stderr
186 sys.stderr = ErrorWriter()
187 self.run_cython(directory, module, workdir, incdir, annotate)
188 errors = sys.stderr.geterrors()
190 sys.stderr = old_stderr
192 if errors or expected_errors:
193 for expected, error in zip(expected_errors, errors):
194 self.assertEquals(expected, error)
195 if len(errors) < len(expected_errors):
196 expected_error = expected_errors[len(errors)]
197 self.assertEquals(expected_error, None)
198 elif len(errors) > len(expected_errors):
199 unexpected_error = errors[len(expected_errors)]
200 self.assertEquals(None, unexpected_error)
202 self.run_distutils(module, workdir, incdir)
204 class CythonRunTestCase(CythonCompileTestCase):
205 def shortDescription(self):
206 return "compiling and running " + self.module
208 def run(self, result=None):
210 result = self.defaultTestResult()
211 result.startTest(self)
214 doctest.DocTestSuite(self.module).run(result)
216 result.addError(self, sys.exc_info())
217 result.stopTest(self)
223 if __name__ == '__main__':
225 sys.argv.remove("--no-cython")
232 from Cython.Compiler.Main import \
233 CompilationOptions, \
234 default_options as pyrex_default_options, \
235 compile as cython_compile
237 from distutils.dist import Distribution
238 from distutils.core import Extension
239 from distutils.command.build_ext import build_ext
240 distutils_distro = Distribution()
243 ROOTDIR = os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]), 'tests')
244 WORKDIR = os.path.join(os.getcwd(), 'BUILD')
246 if os.path.exists(WORKDIR):
247 shutil.rmtree(WORKDIR, ignore_errors=True)
248 if not os.path.exists(WORKDIR):
252 from Cython.Compiler.Version import version
253 from Cython.Compiler.Main import \
254 CompilationOptions, \
255 default_options as pyrex_default_options, \
256 compile as cython_compile
257 print("Running tests against Cython %s" % version)
259 print("Running tests without Cython.")
260 print("Python %s" % sys.version)
264 sys.argv.remove("-C")
272 sys.argv.remove("--no-cleanup")
274 CLEANUP_WORKDIR = True
276 CLEANUP_WORKDIR = False
279 sys.argv.remove("-a")
281 annotate_source = False
283 annotate_source = True
286 sys.argv.remove("-vv")
289 sys.argv.remove("-v")
298 selectors = [ re.compile(r, re.I|re.U).search for r in sys.argv[1:] ]
300 selectors = [ lambda x:True ]
302 tests = TestBuilder(ROOTDIR, WORKDIR, selectors, annotate_source)
303 test_suite = tests.build_suite()
305 if coverage is not None:
308 unittest.TextTestRunner(verbosity=verbosity).run(test_suite)
310 if coverage is not None:
312 ignored_modules = ('Options', 'Version', 'DebugFlags')
313 modules = [ module for name, module in sys.modules.items()
314 if module is not None and
315 name.startswith('Cython.Compiler.') and
316 name[len('Cython.Compiler.'):] not in ignored_modules ]
317 coverage.report(modules, show_missing=0)