3 import os, sys, re, shutil, unittest, doctest
7 from distutils.dist import Distribution
8 from distutils.core import Extension
9 from distutils.command.build_ext import build_ext
10 distutils_distro = Distribution()
12 TEST_DIRS = ['compile', 'errors', 'run', 'pyregr']
13 TEST_RUN_DIRS = ['run', 'pyregr']
15 INCLUDE_DIRS = [ d for d in os.getenv('INCLUDE', '').split(os.pathsep) if d ]
16 CFLAGS = os.getenv('CFLAGS', '').split()
19 class ErrorWriter(object):
20 match_error = re.compile('(warning:)?(?:.*:)?\s*([-0-9]+)\s*:\s*([-0-9]+)\s*:\s*(.*)').match
23 self.write = self.output.append
25 def _collect(self, collect_errors, collect_warnings):
26 s = ''.join(self.output)
28 for line in s.split('\n'):
29 match = self.match_error(line)
31 is_warning, line, column, message = match.groups()
32 if (is_warning and collect_warnings) or \
33 (not is_warning and collect_errors):
34 result.append( (int(line), int(column), message.strip()) )
36 return [ "%d:%d: %s" % values for values in result ]
39 return self._collect(True, False)
41 def getwarnings(self):
42 return self._collect(False, True)
45 return self._collect(True, True)
47 class TestBuilder(object):
48 def __init__(self, rootdir, workdir, selectors, annotate,
49 cleanup_workdir, cleanup_sharedlibs, with_pyregr):
50 self.rootdir = rootdir
51 self.workdir = workdir
52 self.selectors = selectors
53 self.annotate = annotate
54 self.cleanup_workdir = cleanup_workdir
55 self.cleanup_sharedlibs = cleanup_sharedlibs
56 self.with_pyregr = with_pyregr
58 def build_suite(self):
59 suite = unittest.TestSuite()
60 filenames = os.listdir(self.rootdir)
62 for filename in filenames:
63 if not WITH_CYTHON and filename == "errors":
64 # we won't get any errors without running Cython
66 path = os.path.join(self.rootdir, filename)
67 if os.path.isdir(path) and filename in TEST_DIRS:
68 if filename == 'pyregr' and not self.with_pyregr:
71 self.handle_directory(path, filename))
74 def handle_directory(self, path, context):
75 workdir = os.path.join(self.workdir, context)
76 if not os.path.exists(workdir):
78 if workdir not in sys.path:
79 sys.path.insert(0, workdir)
81 expect_errors = (context == 'errors')
82 suite = unittest.TestSuite()
83 filenames = os.listdir(path)
85 for filename in filenames:
86 if not (filename.endswith(".pyx") or filename.endswith(".py")):
88 if context == 'pyregr' and not filename.startswith('test_'):
90 module = os.path.splitext(filename)[0]
91 fqmodule = "%s.%s" % (context, module)
92 if not [ 1 for match in self.selectors
95 if context in TEST_RUN_DIRS:
96 if module.startswith("test_"):
97 build_test = CythonUnitTestCase
99 build_test = CythonRunTestCase
101 path, workdir, module,
102 annotate=self.annotate,
103 cleanup_workdir=self.cleanup_workdir,
104 cleanup_sharedlibs=self.cleanup_sharedlibs)
106 test = CythonCompileTestCase(
107 path, workdir, module,
108 expect_errors=expect_errors,
109 annotate=self.annotate,
110 cleanup_workdir=self.cleanup_workdir,
111 cleanup_sharedlibs=self.cleanup_sharedlibs)
115 class CythonCompileTestCase(unittest.TestCase):
116 def __init__(self, directory, workdir, module,
117 expect_errors=False, annotate=False, cleanup_workdir=True,
118 cleanup_sharedlibs=True):
119 self.directory = directory
120 self.workdir = workdir
122 self.expect_errors = expect_errors
123 self.annotate = annotate
124 self.cleanup_workdir = cleanup_workdir
125 self.cleanup_sharedlibs = cleanup_sharedlibs
126 unittest.TestCase.__init__(self)
128 def shortDescription(self):
129 return "compiling " + self.module
132 cleanup_c_files = WITH_CYTHON and self.cleanup_workdir
133 cleanup_lib_files = self.cleanup_sharedlibs
134 if os.path.exists(self.workdir):
135 for rmfile in os.listdir(self.workdir):
136 if not cleanup_c_files and rmfile[-2:] in (".c", ".h"):
138 if not cleanup_lib_files and rmfile.endswith(".so") or rmfile.endswith(".dll"):
140 if self.annotate and rmfile.endswith(".html"):
143 rmfile = os.path.join(self.workdir, rmfile)
144 if os.path.isdir(rmfile):
145 shutil.rmtree(rmfile, ignore_errors=True)
151 os.makedirs(self.workdir)
154 self.runCompileTest()
156 def runCompileTest(self):
157 self.compile(self.directory, self.module, self.workdir,
158 self.directory, self.expect_errors, self.annotate)
160 def find_module_source_file(self, source_file):
161 if not os.path.exists(source_file):
162 source_file = source_file[:-1]
165 def split_source_and_output(self, directory, module, workdir):
166 source_file = os.path.join(directory, module) + '.pyx'
167 source_and_output = open(
168 self.find_module_source_file(source_file), 'rU')
169 out = open(os.path.join(workdir, module + '.pyx'), 'w')
170 for line in source_and_output:
172 if line.startswith("_ERRORS"):
178 geterrors = out.geterrors
179 except AttributeError:
184 def run_cython(self, directory, module, targetdir, incdir, annotate):
185 include_dirs = INCLUDE_DIRS[:]
187 include_dirs.append(incdir)
188 source = self.find_module_source_file(
189 os.path.join(directory, module + '.pyx'))
190 target = os.path.join(targetdir, module + '.c')
191 options = CompilationOptions(
192 pyrex_default_options,
193 include_path = include_dirs,
194 output_file = target,
196 use_listing_file = False, cplus = False, generate_pxi = False)
197 cython_compile(source, options=options,
198 full_module_name=module)
200 def run_distutils(self, module, workdir, incdir):
204 build_extension = build_ext(distutils_distro)
205 build_extension.include_dirs = INCLUDE_DIRS[:]
207 build_extension.include_dirs.append(incdir)
208 build_extension.finalize_options()
210 extension = Extension(
212 sources = [module + '.c'],
213 extra_compile_args = CFLAGS,
215 build_extension.extensions = [extension]
216 build_extension.build_temp = workdir
217 build_extension.build_lib = workdir
218 build_extension.run()
222 def compile(self, directory, module, workdir, incdir,
223 expect_errors, annotate):
224 expected_errors = errors = ()
226 expected_errors = self.split_source_and_output(
227 directory, module, workdir)
231 old_stderr = sys.stderr
233 sys.stderr = ErrorWriter()
234 self.run_cython(directory, module, workdir, incdir, annotate)
235 errors = sys.stderr.geterrors()
237 sys.stderr = old_stderr
239 if errors or expected_errors:
240 for expected, error in zip(expected_errors, errors):
241 self.assertEquals(expected, error)
242 if len(errors) < len(expected_errors):
243 expected_error = expected_errors[len(errors)]
244 self.assertEquals(expected_error, None)
245 elif len(errors) > len(expected_errors):
246 unexpected_error = errors[len(expected_errors)]
247 self.assertEquals(None, unexpected_error)
249 self.run_distutils(module, workdir, incdir)
251 class CythonRunTestCase(CythonCompileTestCase):
252 def shortDescription(self):
253 return "compiling and running " + self.module
255 def run(self, result=None):
257 result = self.defaultTestResult()
258 result.startTest(self)
260 self.runCompileTest()
261 doctest.DocTestSuite(self.module).run(result)
263 result.addError(self, sys.exc_info())
264 result.stopTest(self)
270 class CythonUnitTestCase(CythonCompileTestCase):
271 def shortDescription(self):
272 return "compiling and running unit tests in " + self.module
274 def run(self, result=None):
276 result = self.defaultTestResult()
277 result.startTest(self)
279 self.runCompileTest()
280 unittest.defaultTestLoader.loadTestsFromName(self.module).run(result)
282 result.addError(self, sys.exc_info())
283 result.stopTest(self)
289 def collect_unittests(path, suite, selectors):
290 def file_matches(filename):
291 return filename.startswith("Test") and filename.endswith(".py")
293 def package_matches(dirname):
294 return dirname == "Tests"
296 loader = unittest.TestLoader()
298 for dirpath, dirnames, filenames in os.walk(path):
299 parentname = os.path.split(dirpath)[-1]
300 if package_matches(parentname):
303 filepath = os.path.join(dirpath, f)[:-len(".py")]
304 modulename = filepath[len(path)+1:].replace(os.path.sep, '.')
305 if not [ 1 for match in selectors if match(modulename) ]:
307 module = __import__(modulename)
308 for x in modulename.split('.')[1:]:
309 module = getattr(module, x)
310 suite.addTests([loader.loadTestsFromModule(module)])
312 if __name__ == '__main__':
313 from optparse import OptionParser
314 parser = OptionParser()
315 parser.add_option("--no-cleanup", dest="cleanup_workdir",
316 action="store_false", default=True,
317 help="do not delete the generated C files (allows passing --no-cython on next run)")
318 parser.add_option("--no-cleanup-sharedlibs", dest="cleanup_sharedlibs",
319 action="store_false", default=True,
320 help="do not delete the generated shared libary files (allows manual module experimentation)")
321 parser.add_option("--no-cython", dest="with_cython",
322 action="store_false", default=True,
323 help="do not run the Cython compiler, only the C compiler")
324 parser.add_option("--no-unit", dest="unittests",
325 action="store_false", default=True,
326 help="do not run the unit tests")
327 parser.add_option("--no-file", dest="filetests",
328 action="store_false", default=True,
329 help="do not run the file based tests")
330 parser.add_option("--no-pyregr", dest="pyregr",
331 action="store_false", default=True,
332 help="do not run the regression tests of CPython in tests/pyregr/")
333 parser.add_option("-C", "--coverage", dest="coverage",
334 action="store_true", default=False,
335 help="collect source coverage data for the Compiler")
336 parser.add_option("-A", "--annotate", dest="annotate_source",
337 action="store_true", default=False,
338 help="generate annotated HTML versions of the test source files")
339 parser.add_option("-v", "--verbose", dest="verbosity",
340 action="count", default=0,
341 help="display test progress, pass twice to print test names")
343 options, cmd_args = parser.parse_args()
350 WITH_CYTHON = options.with_cython
353 from Cython.Compiler.Main import \
354 CompilationOptions, \
355 default_options as pyrex_default_options, \
356 compile as cython_compile
357 from Cython.Compiler import Errors
358 Errors.LEVEL = 0 # show all warnings
361 ROOTDIR = os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]), 'tests')
362 WORKDIR = os.path.join(os.getcwd(), 'BUILD')
364 if os.path.exists(WORKDIR):
365 shutil.rmtree(WORKDIR, ignore_errors=True)
366 if not os.path.exists(WORKDIR):
370 from Cython.Compiler.Version import version
371 print("Running tests against Cython %s" % version)
373 print("Running tests without Cython.")
374 print("Python %s" % sys.version)
378 selectors = [ re.compile(r, re.I|re.U).search for r in cmd_args ]
380 selectors = [ lambda x:True ]
382 test_suite = unittest.TestSuite()
384 if options.unittests:
385 collect_unittests(os.getcwd(), test_suite, selectors)
387 if options.filetests:
388 filetests = TestBuilder(ROOTDIR, WORKDIR, selectors,
389 options.annotate_source, options.cleanup_workdir,
390 options.cleanup_sharedlibs, options.pyregr)
391 test_suite.addTests([filetests.build_suite()])
393 unittest.TextTestRunner(verbosity=options.verbosity).run(test_suite)
397 ignored_modules = ('Options', 'Version', 'DebugFlags')
398 modules = [ module for name, module in sys.modules.items()
399 if module is not None and
400 name.startswith('Cython.Compiler.') and
401 name[len('Cython.Compiler.'):] not in ignored_modules ]
402 coverage.report(modules, show_missing=0)