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, cythononly):
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
57 self.cythononly = cythononly
59 def build_suite(self):
60 suite = unittest.TestSuite()
61 filenames = os.listdir(self.rootdir)
63 for filename in filenames:
64 if not WITH_CYTHON and filename == "errors":
65 # we won't get any errors without running Cython
67 path = os.path.join(self.rootdir, filename)
68 if os.path.isdir(path) and filename in TEST_DIRS:
69 if filename == 'pyregr' and not self.with_pyregr:
72 self.handle_directory(path, filename))
75 def handle_directory(self, path, context):
76 workdir = os.path.join(self.workdir, context)
77 if not os.path.exists(workdir):
79 if workdir not in sys.path:
80 sys.path.insert(0, workdir)
82 expect_errors = (context == 'errors')
83 suite = unittest.TestSuite()
84 filenames = os.listdir(path)
86 for filename in filenames:
87 if not (filename.endswith(".pyx") or filename.endswith(".py")):
89 if filename.startswith('.'): continue # certain emacs backup files
90 if context == 'pyregr' and not filename.startswith('test_'):
92 module = os.path.splitext(filename)[0]
93 fqmodule = "%s.%s" % (context, module)
94 if not [ 1 for match in self.selectors
97 if context in TEST_RUN_DIRS:
98 if module.startswith("test_"):
99 build_test = CythonUnitTestCase
101 build_test = CythonRunTestCase
103 path, workdir, module,
104 annotate=self.annotate,
105 cleanup_workdir=self.cleanup_workdir,
106 cleanup_sharedlibs=self.cleanup_sharedlibs,
107 cythononly=self.cythononly)
109 test = CythonCompileTestCase(
110 path, workdir, module,
111 expect_errors=expect_errors,
112 annotate=self.annotate,
113 cleanup_workdir=self.cleanup_workdir,
114 cleanup_sharedlibs=self.cleanup_sharedlibs,
115 cythononly=self.cythononly)
119 class CythonCompileTestCase(unittest.TestCase):
120 def __init__(self, directory, workdir, module,
121 expect_errors=False, annotate=False, cleanup_workdir=True,
122 cleanup_sharedlibs=True, cythononly=False):
123 self.directory = directory
124 self.workdir = workdir
126 self.expect_errors = expect_errors
127 self.annotate = annotate
128 self.cleanup_workdir = cleanup_workdir
129 self.cleanup_sharedlibs = cleanup_sharedlibs
130 self.cythononly = cythononly
131 unittest.TestCase.__init__(self)
133 def shortDescription(self):
134 return "compiling " + self.module
137 cleanup_c_files = WITH_CYTHON and self.cleanup_workdir
138 cleanup_lib_files = self.cleanup_sharedlibs
139 if os.path.exists(self.workdir):
140 for rmfile in os.listdir(self.workdir):
141 if not cleanup_c_files and rmfile[-2:] in (".c", ".h"):
143 if not cleanup_lib_files and rmfile.endswith(".so") or rmfile.endswith(".dll"):
145 if self.annotate and rmfile.endswith(".html"):
148 rmfile = os.path.join(self.workdir, rmfile)
149 if os.path.isdir(rmfile):
150 shutil.rmtree(rmfile, ignore_errors=True)
156 os.makedirs(self.workdir)
159 self.runCompileTest()
161 def runCompileTest(self):
162 self.compile(self.directory, self.module, self.workdir,
163 self.directory, self.expect_errors, self.annotate)
165 def find_module_source_file(self, source_file):
166 if not os.path.exists(source_file):
167 source_file = source_file[:-1]
170 def split_source_and_output(self, directory, module, workdir):
171 source_file = os.path.join(directory, module) + '.pyx'
172 source_and_output = open(
173 self.find_module_source_file(source_file), 'rU')
174 out = open(os.path.join(workdir, module + '.pyx'), 'w')
175 for line in source_and_output:
177 if line.startswith("_ERRORS"):
183 geterrors = out.geterrors
184 except AttributeError:
189 def run_cython(self, directory, module, targetdir, incdir, annotate):
190 include_dirs = INCLUDE_DIRS[:]
192 include_dirs.append(incdir)
193 source = self.find_module_source_file(
194 os.path.join(directory, module + '.pyx'))
195 target = os.path.join(targetdir, module + '.c')
196 options = CompilationOptions(
197 pyrex_default_options,
198 include_path = include_dirs,
199 output_file = target,
201 use_listing_file = False, cplus = False, generate_pxi = False)
202 cython_compile(source, options=options,
203 full_module_name=module)
205 def run_distutils(self, module, workdir, incdir):
209 build_extension = build_ext(distutils_distro)
210 build_extension.include_dirs = INCLUDE_DIRS[:]
212 build_extension.include_dirs.append(incdir)
213 build_extension.finalize_options()
215 extension = Extension(
217 sources = [module + '.c'],
218 extra_compile_args = CFLAGS,
220 build_extension.extensions = [extension]
221 build_extension.build_temp = workdir
222 build_extension.build_lib = workdir
223 build_extension.run()
227 def compile(self, directory, module, workdir, incdir,
228 expect_errors, annotate):
229 expected_errors = errors = ()
231 expected_errors = self.split_source_and_output(
232 directory, module, workdir)
236 old_stderr = sys.stderr
238 sys.stderr = ErrorWriter()
239 self.run_cython(directory, module, workdir, incdir, annotate)
240 errors = sys.stderr.geterrors()
242 sys.stderr = old_stderr
244 if errors or expected_errors:
245 for expected, error in zip(expected_errors, errors):
246 self.assertEquals(expected, error)
247 if len(errors) < len(expected_errors):
248 expected_error = expected_errors[len(errors)]
249 self.assertEquals(expected_error, None)
250 elif len(errors) > len(expected_errors):
251 unexpected_error = errors[len(expected_errors)]
252 self.assertEquals(None, unexpected_error)
254 if not self.cythononly:
255 self.run_distutils(module, workdir, incdir)
257 class CythonRunTestCase(CythonCompileTestCase):
258 def shortDescription(self):
259 return "compiling and running " + self.module
261 def run(self, result=None):
263 result = self.defaultTestResult()
264 result.startTest(self)
266 self.runCompileTest()
267 if not self.cythononly:
268 sys.stderr.write('running doctests in %s ...\n' % self.module)
269 doctest.DocTestSuite(self.module).run(result)
271 result.addError(self, sys.exc_info())
272 result.stopTest(self)
278 class CythonUnitTestCase(CythonCompileTestCase):
279 def shortDescription(self):
280 return "compiling tests in " + self.module
282 def run(self, result=None):
284 result = self.defaultTestResult()
285 result.startTest(self)
287 self.runCompileTest()
288 sys.stderr.write('running tests in %s ...\n' % self.module)
289 unittest.defaultTestLoader.loadTestsFromName(self.module).run(result)
291 result.addError(self, sys.exc_info())
292 result.stopTest(self)
298 def collect_unittests(path, module_prefix, suite, selectors):
299 def file_matches(filename):
300 return filename.startswith("Test") and filename.endswith(".py")
302 def package_matches(dirname):
303 return dirname == "Tests"
305 loader = unittest.TestLoader()
309 for dirpath, dirnames, filenames in os.walk(path):
310 if dirpath != path and "__init__.py" not in filenames:
311 skipped_dirs.append(dirpath + os.path.sep)
314 for dir in skipped_dirs:
315 if dirpath.startswith(dir):
319 parentname = os.path.split(dirpath)[-1]
320 if package_matches(parentname):
323 filepath = os.path.join(dirpath, f)[:-len(".py")]
324 modulename = module_prefix + filepath[len(path)+1:].replace(os.path.sep, '.')
325 if not [ 1 for match in selectors if match(modulename) ]:
327 module = __import__(modulename)
328 for x in modulename.split('.')[1:]:
329 module = getattr(module, x)
330 suite.addTests([loader.loadTestsFromModule(module)])
332 def collect_doctests(path, module_prefix, suite, selectors):
333 def package_matches(dirname):
334 return dirname not in ("Mac", "Distutils", "Plex")
335 def file_matches(filename):
336 return (filename.endswith(".py") and not ('~' in filename
337 or '#' in filename or filename.startswith('.')))
338 import doctest, types
339 for dirpath, dirnames, filenames in os.walk(path):
340 parentname = os.path.split(dirpath)[-1]
341 if package_matches(parentname):
344 if not f.endswith('.py'): continue
345 filepath = os.path.join(dirpath, f)[:-len(".py")]
346 modulename = module_prefix + filepath[len(path)+1:].replace(os.path.sep, '.')
347 if not [ 1 for match in selectors if match(modulename) ]:
349 module = __import__(modulename)
350 for x in modulename.split('.')[1:]:
351 module = getattr(module, x)
352 if hasattr(module, "__doc__") or hasattr(module, "__test__"):
354 suite.addTest(doctest.DocTestSuite(module))
355 except ValueError: # no tests
358 if __name__ == '__main__':
359 from optparse import OptionParser
360 parser = OptionParser()
361 parser.add_option("--no-cleanup", dest="cleanup_workdir",
362 action="store_false", default=True,
363 help="do not delete the generated C files (allows passing --no-cython on next run)")
364 parser.add_option("--no-cleanup-sharedlibs", dest="cleanup_sharedlibs",
365 action="store_false", default=True,
366 help="do not delete the generated shared libary files (allows manual module experimentation)")
367 parser.add_option("--no-cython", dest="with_cython",
368 action="store_false", default=True,
369 help="do not run the Cython compiler, only the C compiler")
370 parser.add_option("--no-unit", dest="unittests",
371 action="store_false", default=True,
372 help="do not run the unit tests")
373 parser.add_option("--no-doctest", dest="doctests",
374 action="store_false", default=True,
375 help="do not run the doctests")
376 parser.add_option("--no-file", dest="filetests",
377 action="store_false", default=True,
378 help="do not run the file based tests")
379 parser.add_option("--no-pyregr", dest="pyregr",
380 action="store_false", default=True,
381 help="do not run the regression tests of CPython in tests/pyregr/")
382 parser.add_option("--cython-only", dest="cythononly",
383 action="store_true", default=False,
384 help="only compile pyx to c, do not run C compiler or run the tests")
385 parser.add_option("--sys-pyregr", dest="system_pyregr",
386 action="store_true", default=False,
387 help="run the regression tests of the CPython installation")
388 parser.add_option("-C", "--coverage", dest="coverage",
389 action="store_true", default=False,
390 help="collect source coverage data for the Compiler")
391 parser.add_option("-A", "--annotate", dest="annotate_source",
392 action="store_true", default=False,
393 help="generate annotated HTML versions of the test source files")
394 parser.add_option("-v", "--verbose", dest="verbosity",
395 action="count", default=0,
396 help="display test progress, pass twice to print test names")
398 options, cmd_args = parser.parse_args()
400 if sys.version_info[0] >= 3:
401 # make sure we do not import (or run) Cython itself
402 options.doctests = False
403 options.with_cython = False
404 options.unittests = False
405 options.pyregr = False
412 WITH_CYTHON = options.with_cython
415 from Cython.Compiler.Main import \
416 CompilationOptions, \
417 default_options as pyrex_default_options, \
418 compile as cython_compile
419 from Cython.Compiler import Errors
420 Errors.LEVEL = 0 # show all warnings
423 ROOTDIR = os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]), 'tests')
424 WORKDIR = os.path.join(os.getcwd(), 'BUILD')
425 UNITTEST_MODULE = "Cython"
426 UNITTEST_ROOT = os.path.join(os.getcwd(), UNITTEST_MODULE)
428 if os.path.exists(WORKDIR):
429 shutil.rmtree(WORKDIR, ignore_errors=True)
430 if not os.path.exists(WORKDIR):
434 from Cython.Compiler.Version import version
435 sys.stderr.write("Running tests against Cython %s\n" % version)
437 sys.stderr.write("Running tests without Cython.\n")
438 sys.stderr.write("Python %s\n" % sys.version)
439 sys.stderr.write("\n")
442 selectors = [ re.compile(r, re.I|re.U).search for r in cmd_args ]
444 selectors = [ lambda x:True ]
446 test_suite = unittest.TestSuite()
448 if options.unittests:
449 collect_unittests(UNITTEST_ROOT, UNITTEST_MODULE + ".", test_suite, selectors)
452 collect_doctests(UNITTEST_ROOT, UNITTEST_MODULE + ".", test_suite, selectors)
454 if options.filetests:
455 filetests = TestBuilder(ROOTDIR, WORKDIR, selectors,
456 options.annotate_source, options.cleanup_workdir,
457 options.cleanup_sharedlibs, options.pyregr,
459 test_suite.addTest(filetests.build_suite())
461 if options.system_pyregr:
462 filetests = TestBuilder(ROOTDIR, WORKDIR, selectors,
463 options.annotate_source, options.cleanup_workdir,
464 options.cleanup_sharedlibs, True,
467 filetests.handle_directory(
468 os.path.join(sys.prefix, 'lib', 'python'+sys.version[:3], 'test'),
471 unittest.TextTestRunner(verbosity=options.verbosity).run(test_suite)
475 ignored_modules = ('Options', 'Version', 'DebugFlags')
476 modules = [ module for name, module in sys.modules.items()
477 if module is not None and
478 name.startswith('Cython.Compiler.') and
479 name[len('Cython.Compiler.'):] not in ignored_modules ]
480 coverage.report(modules, show_missing=0)