20 IS_PYPY
= platform
.python_implementation() == 'PyPy'
21 IS_CPYTHON
= platform
.python_implementation() == 'CPython'
22 except (ImportError, AttributeError):
27 from StringIO
import StringIO
29 from io
import StringIO
32 import cPickle
as pickle
37 from io
import open as io_open
39 from codecs
import open as io_open
43 except ImportError: # No threads, no problems
47 from collections
import defaultdict
49 class defaultdict(object):
50 def __init__(self
, default_factory
=lambda : None):
52 self
.default_factory
= default_factory
53 def __getitem__(self
, key
):
54 if key
not in self
._dict
:
55 self
._dict
[key
] = self
.default_factory()
56 return self
._dict
[key
]
57 def __setitem__(self
, key
, value
):
58 self
._dict
[key
] = value
59 def __contains__(self
, key
):
60 return key
in self
._dict
62 return repr(self
._dict
)
63 def __nonzero__(self
):
64 return bool(self
._dict
)
74 from distutils
.dist
import Distribution
75 from distutils
.core
import Extension
76 from distutils
.command
.build_ext
import build_ext
as _build_ext
77 from distutils
import sysconfig
78 distutils_distro
= Distribution()
81 if sys
.platform
== 'win32':
82 # TODO: Figure out why this hackery (see http://thread.gmane.org/gmane.comp.python.cython.devel/8280/).
83 config_files
= distutils_distro
.find_config_files()
84 try: config_files
.remove('setup.cfg')
85 except ValueError: pass
86 distutils_distro
.parse_config_files(config_files
)
88 cfgfiles
= distutils_distro
.find_config_files()
89 try: cfgfiles
.remove('setup.cfg')
90 except ValueError: pass
91 distutils_distro
.parse_config_files(cfgfiles
)
94 'tag:numpy' : 'numpy',
95 'tag:pstats': 'pstats',
96 'tag:posix' : 'posix',
97 'tag:array' : 'array',
100 def patch_inspect_isfunction():
102 orig_isfunction
= inspect
.isfunction
104 return orig_isfunction(obj
) or type(obj
).__name
__ == 'cython_function_or_method'
105 isfunction
._orig
_isfunction
= orig_isfunction
106 inspect
.isfunction
= isfunction
108 def unpatch_inspect_isfunction():
111 orig_isfunction
= inspect
.isfunction
._orig
_isfunction
112 except AttributeError:
115 inspect
.isfunction
= orig_isfunction
117 def update_linetrace_extension(ext
):
118 ext
.define_macros
.append(('CYTHON_TRACE', 1))
121 def update_numpy_extension(ext
):
123 from numpy
.distutils
.misc_util
import get_info
125 ext
.include_dirs
.append(numpy
.get_include())
127 # We need the npymath library for numpy.math.
128 # This is typically a static-only library.
129 for attr
, value
in get_info('npymath').items():
130 getattr(ext
, attr
).extend(value
)
132 def update_openmp_extension(ext
):
134 language
= ext
.language
136 if language
== 'cpp':
137 flags
= OPENMP_CPP_COMPILER_FLAGS
139 flags
= OPENMP_C_COMPILER_FLAGS
142 compile_flags
, link_flags
= flags
144 ext
.extra_compile_args
.extend(compile_flags
.split())
145 ext
.extra_link_args
.extend(link_flags
.split())
147 elif sys
.platform
== 'win32':
152 def get_openmp_compiler_flags(language
):
154 As of gcc 4.2, it supports OpenMP 2.5. Gcc 4.4 implements 3.0. We don't
155 (currently) check for other compilers.
157 returns a two-tuple of (CFLAGS, LDFLAGS) to build the OpenMP extension
159 if language
== 'cpp':
160 cc
= sysconfig
.get_config_var('CXX')
162 cc
= sysconfig
.get_config_var('CC')
165 if sys
.platform
== 'win32':
169 # For some reason, cc can be e.g. 'gcc -pthread'
172 # Force english output
173 env
= os
.environ
.copy()
174 env
['LC_MESSAGES'] = 'C'
176 matcher
= re
.compile(r
"gcc version (\d+\.\d+)").search
178 p
= subprocess
.Popen([cc
, "-v"], stderr
=subprocess
.PIPE
, env
=env
)
179 except EnvironmentError:
180 # Be compatible with Python 3
181 warnings
.warn("Unable to find the %s compiler: %s: %s" %
182 (language
, os
.strerror(sys
.exc_info()[1].errno
), cc
))
184 _
, output
= p
.communicate()
186 output
= output
.decode(locale
.getpreferredencoding() or 'ASCII', 'replace')
188 gcc_version
= matcher(output
)
190 return None # not gcc - FIXME: do something about other compilers
192 compiler_version
= gcc_version
.group(1)
193 if compiler_version
and compiler_version
.split('.') >= ['4', '2']:
194 return '-fopenmp', '-fopenmp'
197 locale
.setlocale(locale
.LC_ALL
, '')
201 OPENMP_C_COMPILER_FLAGS
= get_openmp_compiler_flags('c')
202 OPENMP_CPP_COMPILER_FLAGS
= get_openmp_compiler_flags('cpp')
204 # Return this from the EXT_EXTRAS matcher callback to exclude the extension
205 EXCLUDE_EXT
= object()
208 'tag:numpy' : update_numpy_extension
,
209 'tag:openmp': update_openmp_extension
,
210 'tag:trace' : update_linetrace_extension
,
214 def _is_py3_before_32(excluded
, version
):
215 return version
[0] >= 3 and version
< (3,2)
220 # tests are excluded if 'CurrentPythonVersion OP VersionTuple', i.e.
221 # (2,4) : (operator.lt, ...) excludes ... when PyVer < 2.4.x
222 (2,4) : (operator
.lt
, lambda x
: x
in ['run.extern_builtins_T258',
223 'run.builtin_sorted',
224 'run.reversed_iteration',
226 (2,5) : (operator
.lt
, lambda x
: x
in ['run.any',
228 'run.yield_from_pep380', # GeneratorExit
229 'run.generator_frame_cycle', # yield in try-finally
230 'run.generator_expressions_in_class',
231 'run.absolute_import',
232 'run.relativeimport_T542',
233 'run.relativeimport_star_T542',
234 'run.initial_file_path', # relative import
235 'run.pynumber_subtype_conversion', # bug in Py2.4
236 'build.cythonize_script', # python2.4 -m a.b.c
237 'build.cythonize_script_excludes', # python2.4 -m a.b.c
238 'build.cythonize_script_package', # python2.4 -m a.b.c
240 (2,6) : (operator
.lt
, lambda x
: x
in ['run.print_function',
241 'run.language_level', # print function
243 'run.property_decorator_T593', # prop.setter etc.
244 'run.generators_py', # generators, with statement
245 'run.pure_py', # decorators, with statement
247 'run.struct_conversion',
248 'run.bytearray_coercion',
249 'run.bytearraymethods',
250 'run.bytearray_ascii_auto_encoding',
251 'run.bytearray_default_auto_encoding',
252 # memory views require buffer protocol
253 'memoryview.relaxed_strides',
254 'memoryview.cythonarray',
255 'memoryview.memslice',
256 'memoryview.numpy_memoryview',
257 'memoryview.memoryviewattrs',
258 'memoryview.memoryview',
261 (2,7) : (operator
.lt
, lambda x
: x
in ['run.withstat_py27', # multi context with statement
262 'run.yield_inside_lambda',
263 'run.test_dictviews',
264 'run.pyclass_special_methods',
266 # The next line should start (3,); but this is a dictionary, so
267 # we can only have one (3,) key. Since 2.7 is supposed to be the
268 # last 2.x release, things would have to change drastically for this
270 (2,999): (operator
.lt
, lambda x
: x
in ['run.special_methods_T561_py3',
271 'run.test_raisefrom',
273 (3,): (operator
.ge
, lambda x
: x
in ['run.non_future_division',
274 'compile.extsetslice',
275 'compile.extdelslice',
276 'run.special_methods_T561_py2'
278 (3,1): (_is_py3_before_32
, lambda x
: x
in ['run.pyclass_special_methods',
280 (3,3) : (operator
.lt
, lambda x
: x
in ['build.package_compilation',
282 (3,4,0,'beta',3) : (operator
.le
, lambda x
: x
in ['run.py34_signature',
286 # files that should not be converted to Python 3 code with 2to3
288 os
.path
.join('Cython', 'Debugger', 'Tests', 'test_libcython_in_gdb.py'),
289 os
.path
.join('Cython', 'Debugger', 'Tests', 'test_libpython_in_gdb.py'),
290 os
.path
.join('Cython', 'Debugger', 'libcython.py'),
291 os
.path
.join('Cython', 'Debugger', 'libpython.py'),
295 INCLUDE_DIRS
= [ d
for d
in os
.getenv('INCLUDE', '').split(os
.pathsep
) if d
]
296 CFLAGS
= os
.getenv('CFLAGS', '').split()
297 CCACHE
= os
.getenv('CYTHON_RUNTESTS_CCACHE', '').split()
298 TEST_SUPPORT_DIR
= 'testsupport'
300 BACKENDS
= ['c', 'cpp']
302 UTF8_BOM_BYTES
= r
'\xef\xbb\xbf'.encode('ISO-8859-1').decode('unicode_escape')
306 uncomputed
= object()
309 res
= f
._cache
.get(args
, uncomputed
)
310 if res
is uncomputed
:
311 res
= f
._cache
[args
] = f(*args
)
317 def parse_tags(filepath
):
318 tags
= defaultdict(list)
319 parse_tag
= re
.compile(r
'#\s*(\w+)\s*:(.*)$').match
320 f
= io_open(filepath
, encoding
='ISO-8859-1', errors
='ignore')
323 # ignore BOM-like bytes and whitespace
324 line
= line
.lstrip(UTF8_BOM_BYTES
).strip()
327 break # assume all tags are in one block
332 parsed
= parse_tag(line
)
334 tag
, values
= parsed
.groups()
335 if tag
in ('coding', 'encoding'):
339 print("WARNING: test tags use the 'tag' directive, not 'tags' (%s)" % filepath
)
340 if tag
not in ('mode', 'tag', 'ticket', 'cython', 'distutils'):
341 print("WARNING: unknown test directive '%s' found (%s)" % (tag
, filepath
))
342 values
= values
.split(',')
343 tags
[tag
].extend(filter(None, [value
.strip() for value
in values
]))
345 break # assume all tags are in one block
351 list_unchanging_dir
= memoize(lambda x
: os
.listdir(x
))
355 def _list_pyregr_data_files(test_directory
):
356 is_data_file
= re
.compile('(?:[.](txt|pem|db|html)|^bad.*[.]py)$').search
357 return ['__init__.py'] + [
358 filename
for filename
in list_unchanging_dir(test_directory
)
359 if is_data_file(filename
)]
362 def import_ext(module_name
, file_path
=None):
365 return imp
.load_dynamic(module_name
, file_path
)
368 from importlib
import invalidate_caches
373 return __import__(module_name
, globals(), locals(), ['*'])
376 class build_ext(_build_ext
):
377 def build_extension(self
, ext
):
379 try: # Py2.7+ & Py3.2+
380 compiler_obj
= self
.compiler_obj
381 except AttributeError:
382 compiler_obj
= self
.compiler
383 if ext
.language
== 'c++':
384 compiler_obj
.compiler_so
.remove('-Wstrict-prototypes')
386 compiler_obj
.compiler_so
= CCACHE
+ compiler_obj
.compiler_so
387 if getattr(ext
, 'openmp', None) and compiler_obj
.compiler_type
== 'msvc':
388 ext
.extra_compile_args
.append('/openmp')
391 _build_ext
.build_extension(self
, ext
)
393 class ErrorWriter(object):
394 match_error
= re
.compile('(warning:)?(?:.*:)?\s*([-0-9]+)\s*:\s*([-0-9]+)\s*:\s*(.*)').match
397 self
.write
= self
.output
.append
399 def _collect(self
, collect_errors
, collect_warnings
):
400 s
= ''.join(self
.output
)
402 for line
in s
.split('\n'):
403 match
= self
.match_error(line
)
405 is_warning
, line
, column
, message
= match
.groups()
406 if (is_warning
and collect_warnings
) or \
407 (not is_warning
and collect_errors
):
408 result
.append( (int(line
), int(column
), message
.strip()) )
410 return [ "%d:%d: %s" % values
for values
in result
]
413 return self
._collect
(True, False)
415 def getwarnings(self
):
416 return self
._collect
(False, True)
419 return self
._collect
(True, True)
421 class TestBuilder(object):
422 def __init__(self
, rootdir
, workdir
, selectors
, exclude_selectors
, annotate
,
423 cleanup_workdir
, cleanup_sharedlibs
, cleanup_failures
,
424 with_pyregr
, cython_only
, languages
, test_bugs
, fork
, language_level
):
425 self
.rootdir
= rootdir
426 self
.workdir
= workdir
427 self
.selectors
= selectors
428 self
.exclude_selectors
= exclude_selectors
429 self
.annotate
= annotate
430 self
.cleanup_workdir
= cleanup_workdir
431 self
.cleanup_sharedlibs
= cleanup_sharedlibs
432 self
.cleanup_failures
= cleanup_failures
433 self
.with_pyregr
= with_pyregr
434 self
.cython_only
= cython_only
435 self
.languages
= languages
436 self
.test_bugs
= test_bugs
438 self
.language_level
= language_level
440 def build_suite(self
):
441 suite
= unittest
.TestSuite()
442 filenames
= os
.listdir(self
.rootdir
)
444 for filename
in filenames
:
445 path
= os
.path
.join(self
.rootdir
, filename
)
446 if os
.path
.isdir(path
) and filename
!= TEST_SUPPORT_DIR
:
447 if filename
== 'pyregr' and not self
.with_pyregr
:
449 if filename
== 'broken' and not self
.test_bugs
:
452 self
.handle_directory(path
, filename
))
453 if sys
.platform
not in ['win32']:
454 # Non-Windows makefile.
455 if [1 for selector
in self
.selectors
if selector("embedded")] \
456 and not [1 for selector
in self
.exclude_selectors
if selector("embedded")]:
457 suite
.addTest(unittest
.makeSuite(EmbedTest
))
460 def handle_directory(self
, path
, context
):
461 workdir
= os
.path
.join(self
.workdir
, context
)
462 if not os
.path
.exists(workdir
):
465 suite
= unittest
.TestSuite()
466 filenames
= list_unchanging_dir(path
)
468 for filename
in filenames
:
469 filepath
= os
.path
.join(path
, filename
)
470 module
, ext
= os
.path
.splitext(filename
)
471 if ext
not in ('.py', '.pyx', '.srctree'):
473 if filename
.startswith('.'):
474 continue # certain emacs backup files
475 if context
== 'pyregr':
476 tags
= defaultdict(list)
478 tags
= parse_tags(filepath
)
479 fqmodule
= "%s.%s" % (context
, module
)
480 if not [ 1 for match
in self
.selectors
481 if match(fqmodule
, tags
) ]:
483 if self
.exclude_selectors
:
484 if [1 for match
in self
.exclude_selectors
485 if match(fqmodule
, tags
)]:
488 mode
= 'run' # default
490 mode
= tags
['mode'][0]
491 elif context
== 'pyregr':
494 if ext
== '.srctree':
495 if 'cpp' not in tags
['tag'] or 'cpp' in self
.languages
:
496 suite
.addTest(EndToEndTest(filepath
, workdir
, self
.cleanup_workdir
))
499 # Choose the test suite.
501 if not filename
.startswith('test_'):
503 test_class
= CythonPyregrTestCase
505 if module
.startswith("test_"):
506 test_class
= CythonUnitTestCase
508 test_class
= CythonRunTestCase
510 test_class
= CythonCompileTestCase
512 for test
in self
.build_tests(test_class
, path
, workdir
,
513 module
, mode
== 'error', tags
):
515 if mode
== 'run' and ext
== '.py' and not self
.cython_only
:
516 # additionally test file in real Python
517 suite
.addTest(PureDoctestTestCase(module
, os
.path
.join(path
, filename
)))
521 def build_tests(self
, test_class
, path
, workdir
, module
, expect_errors
, tags
):
522 if 'werror' in tags
['tag']:
523 warning_errors
= True
525 warning_errors
= False
528 if 'cpp' in tags
['tag'] and 'cpp' in self
.languages
:
531 languages
= self
.languages
[:1]
533 languages
= self
.languages
535 if 'cpp' in tags
['tag'] and 'c' in languages
:
536 languages
= list(languages
)
537 languages
.remove('c')
538 elif 'no-cpp' in tags
['tag'] and 'cpp' in self
.languages
:
539 languages
= list(languages
)
540 languages
.remove('cpp')
541 tests
= [ self
.build_test(test_class
, path
, workdir
, module
, tags
,
542 language
, expect_errors
, warning_errors
)
543 for language
in languages
]
546 def build_test(self
, test_class
, path
, workdir
, module
, tags
,
547 language
, expect_errors
, warning_errors
):
548 language_workdir
= os
.path
.join(workdir
, language
)
549 if not os
.path
.exists(language_workdir
):
550 os
.makedirs(language_workdir
)
551 workdir
= os
.path
.join(language_workdir
, module
)
552 return test_class(path
, workdir
, module
, tags
,
554 expect_errors
=expect_errors
,
555 annotate
=self
.annotate
,
556 cleanup_workdir
=self
.cleanup_workdir
,
557 cleanup_sharedlibs
=self
.cleanup_sharedlibs
,
558 cleanup_failures
=self
.cleanup_failures
,
559 cython_only
=self
.cython_only
,
561 language_level
=self
.language_level
,
562 warning_errors
=warning_errors
)
564 class CythonCompileTestCase(unittest
.TestCase
):
565 def __init__(self
, test_directory
, workdir
, module
, tags
, language
='c',
566 expect_errors
=False, annotate
=False, cleanup_workdir
=True,
567 cleanup_sharedlibs
=True, cleanup_failures
=True, cython_only
=False,
568 fork
=True, language_level
=2, warning_errors
=False):
569 self
.test_directory
= test_directory
571 self
.workdir
= workdir
573 self
.language
= language
574 self
.expect_errors
= expect_errors
575 self
.annotate
= annotate
576 self
.cleanup_workdir
= cleanup_workdir
577 self
.cleanup_sharedlibs
= cleanup_sharedlibs
578 self
.cleanup_failures
= cleanup_failures
579 self
.cython_only
= cython_only
581 self
.language_level
= language_level
582 self
.warning_errors
= warning_errors
583 unittest
.TestCase
.__init
__(self
)
585 def shortDescription(self
):
586 return "compiling (%s) %s" % (self
.language
, self
.module
)
589 from Cython
.Compiler
import Options
590 self
._saved
_options
= [ (name
, getattr(Options
, name
))
591 for name
in ('warning_errors',
593 'error_on_unknown_names',
594 'error_on_uninitialized') ]
595 self
._saved
_default
_directives
= Options
.directive_defaults
.items()
596 Options
.warning_errors
= self
.warning_errors
597 if sys
.version_info
>= (3, 4):
598 Options
.directive_defaults
['autotestdict'] = False
600 if not os
.path
.exists(self
.workdir
):
601 os
.makedirs(self
.workdir
)
602 if self
.workdir
not in sys
.path
:
603 sys
.path
.insert(0, self
.workdir
)
606 from Cython
.Compiler
import Options
607 for name
, value
in self
._saved
_options
:
608 setattr(Options
, name
, value
)
609 Options
.directive_defaults
= dict(self
._saved
_default
_directives
)
610 unpatch_inspect_isfunction()
613 sys
.path
.remove(self
.workdir
)
617 del sys
.modules
[self
.module
]
620 cleanup
= self
.cleanup_failures
or self
.success
621 cleanup_c_files
= WITH_CYTHON
and self
.cleanup_workdir
and cleanup
622 cleanup_lib_files
= self
.cleanup_sharedlibs
and cleanup
623 if os
.path
.exists(self
.workdir
):
624 if cleanup_c_files
and cleanup_lib_files
:
625 shutil
.rmtree(self
.workdir
, ignore_errors
=True)
627 for rmfile
in os
.listdir(self
.workdir
):
628 if not cleanup_c_files
:
629 if (rmfile
[-2:] in (".c", ".h") or
630 rmfile
[-4:] == ".cpp" or
631 rmfile
.endswith(".html") and rmfile
.startswith(self
.module
)):
633 if not cleanup_lib_files
and (rmfile
.endswith(".so") or rmfile
.endswith(".dll")):
636 rmfile
= os
.path
.join(self
.workdir
, rmfile
)
637 if os
.path
.isdir(rmfile
):
638 shutil
.rmtree(rmfile
, ignore_errors
=True)
646 self
.runCompileTest()
649 def runCompileTest(self
):
651 self
.test_directory
, self
.module
, self
.workdir
,
652 self
.test_directory
, self
.expect_errors
, self
.annotate
)
654 def find_module_source_file(self
, source_file
):
655 if not os
.path
.exists(source_file
):
656 source_file
= source_file
[:-1]
659 def build_target_filename(self
, module_name
):
660 target
= '%s.%s' % (module_name
, self
.language
)
663 def related_files(self
, test_directory
, module_name
):
664 is_related
= re
.compile('%s_.*[.].*' % module_name
).match
665 return [filename
for filename
in list_unchanging_dir(test_directory
)
666 if is_related(filename
)]
668 def copy_files(self
, test_directory
, target_directory
, file_list
):
669 # use symlink on Unix, copy on Windows
672 except AttributeError:
676 for filename
in file_list
:
677 file_path
= join(test_directory
, filename
)
678 if os
.path
.exists(file_path
):
679 copy(file_path
, join(target_directory
, filename
))
681 def source_files(self
, workdir
, module_name
, file_list
):
682 return ([self
.build_target_filename(module_name
)] +
683 [filename
for filename
in file_list
684 if not os
.path
.isfile(os
.path
.join(workdir
, filename
))])
686 def split_source_and_output(self
, test_directory
, module
, workdir
):
687 source_file
= self
.find_module_source_file(os
.path
.join(test_directory
, module
) + '.pyx')
688 source_and_output
= io_open(source_file
, 'rU', encoding
='ISO-8859-1')
690 out
= io_open(os
.path
.join(workdir
, module
+ os
.path
.splitext(source_file
)[1]),
691 'w', encoding
='ISO-8859-1')
692 for line
in source_and_output
:
693 if line
.startswith("_ERRORS"):
699 source_and_output
.close()
701 geterrors
= out
.geterrors
702 except AttributeError:
708 def run_cython(self
, test_directory
, module
, targetdir
, incdir
, annotate
,
709 extra_compile_options
=None):
710 include_dirs
= INCLUDE_DIRS
+ [os
.path
.join(test_directory
, '..', TEST_SUPPORT_DIR
)]
712 include_dirs
.append(incdir
)
713 source
= self
.find_module_source_file(
714 os
.path
.join(test_directory
, module
+ '.pyx'))
715 target
= os
.path
.join(targetdir
, self
.build_target_filename(module
))
717 if extra_compile_options
is None:
718 extra_compile_options
= {}
723 from Cython
.Compiler
.Main
import CompilationOptions
724 from Cython
.Compiler
.Main
import compile as cython_compile
725 from Cython
.Compiler
.Main
import default_options
727 options
= CompilationOptions(
729 include_path
= include_dirs
,
730 output_file
= target
,
732 use_listing_file
= False,
733 cplus
= self
.language
== 'cpp',
734 language_level
= self
.language_level
,
735 generate_pxi
= False,
736 evaluate_tree_assertions
= True,
737 **extra_compile_options
739 cython_compile(source
, options
=options
,
740 full_module_name
=module
)
742 def run_distutils(self
, test_directory
, module
, workdir
, incdir
,
743 extra_extension_args
=None):
747 build_extension
= build_ext(distutils_distro
)
748 build_extension
.include_dirs
= INCLUDE_DIRS
[:]
750 build_extension
.include_dirs
.append(incdir
)
751 build_extension
.finalize_options()
753 build_extension
.compiler
= COMPILER
755 ext_compile_flags
= CFLAGS
[:]
756 compiler
= COMPILER
or sysconfig
.get_config_var('CC')
758 if self
.language
== 'c' and compiler
== 'gcc':
759 ext_compile_flags
.extend(['-std=c89', '-pedantic'])
760 if build_extension
.compiler
== 'mingw32':
761 ext_compile_flags
.append('-Wno-format')
762 if extra_extension_args
is None:
763 extra_extension_args
= {}
765 related_files
= self
.related_files(test_directory
, module
)
766 self
.copy_files(test_directory
, workdir
, related_files
)
767 extension
= Extension(
769 sources
= self
.source_files(workdir
, module
, related_files
),
770 extra_compile_args
= ext_compile_flags
,
771 **extra_extension_args
774 if self
.language
== 'cpp':
775 # Set the language now as the fixer might need it
776 extension
.language
= 'c++'
778 if 'distutils' in self
.tags
:
779 from Cython
.Build
.Dependencies
import DistutilsInfo
780 pyx_path
= os
.path
.join(self
.test_directory
, self
.module
+ ".pyx")
781 DistutilsInfo(open(pyx_path
)).apply(extension
)
783 for matcher
, fixer
in list(EXT_EXTRAS
.items()):
784 if isinstance(matcher
, str):
786 del EXT_EXTRAS
[matcher
]
787 matcher
= string_selector(matcher
)
788 EXT_EXTRAS
[matcher
] = fixer
789 if matcher(module
, self
.tags
):
790 newext
= fixer(extension
)
791 if newext
is EXCLUDE_EXT
:
793 extension
= newext
or extension
794 if self
.language
== 'cpp':
795 extension
.language
= 'c++'
796 build_extension
.extensions
= [extension
]
797 build_extension
.build_temp
= workdir
798 build_extension
.build_lib
= workdir
799 build_extension
.run()
804 get_ext_fullpath
= build_extension
.get_ext_fullpath
805 except AttributeError:
806 def get_ext_fullpath(ext_name
, self
=build_extension
):
807 # copied from distutils.command.build_ext (missing in Py2.[45])
808 fullname
= self
.get_ext_fullname(ext_name
)
809 modpath
= fullname
.split('.')
810 filename
= self
.get_ext_filename(modpath
[-1])
812 filename
= os
.path
.join(*modpath
[:-1]+[filename
])
813 return os
.path
.join(self
.build_lib
, filename
)
814 package
= '.'.join(modpath
[0:-1])
815 build_py
= self
.get_finalized_command('build_py')
816 package_dir
= os
.path
.abspath(build_py
.get_package_dir(package
))
817 return os
.path
.join(package_dir
, filename
)
819 return get_ext_fullpath(module
)
821 def compile(self
, test_directory
, module
, workdir
, incdir
,
822 expect_errors
, annotate
):
823 expected_errors
= errors
= ()
825 expected_errors
= self
.split_source_and_output(
826 test_directory
, module
, workdir
)
827 test_directory
= workdir
830 old_stderr
= sys
.stderr
832 sys
.stderr
= ErrorWriter()
833 self
.run_cython(test_directory
, module
, workdir
, incdir
, annotate
)
834 errors
= sys
.stderr
.geterrors()
836 sys
.stderr
= old_stderr
838 if errors
or expected_errors
:
840 for expected
, error
in zip(expected_errors
, errors
):
841 self
.assertEquals(expected
, error
)
842 if len(errors
) < len(expected_errors
):
843 expected_error
= expected_errors
[len(errors
)]
844 self
.assertEquals(expected_error
, None)
845 elif len(errors
) > len(expected_errors
):
846 unexpected_error
= errors
[len(expected_errors
)]
847 self
.assertEquals(None, unexpected_error
)
848 except AssertionError:
849 print("\n=== Expected errors: ===")
850 print('\n'.join(expected_errors
))
851 print("\n\n=== Got errors: ===")
852 print('\n'.join(errors
))
860 so_path
= self
.run_distutils(test_directory
, module
, workdir
, incdir
)
863 class CythonRunTestCase(CythonCompileTestCase
):
865 CythonCompileTestCase
.setUp(self
)
866 from Cython
.Compiler
import Options
867 Options
.clear_to_none
= False
869 def shortDescription(self
):
871 return CythonCompileTestCase
.shortDescription(self
)
873 return "compiling (%s) and running %s" % (self
.language
, self
.module
)
875 def run(self
, result
=None):
877 result
= self
.defaultTestResult()
878 result
.startTest(self
)
883 ext_so_path
= self
.runCompileTest()
884 failures
, errors
= len(result
.failures
), len(result
.errors
)
885 if not self
.cython_only
:
886 self
.run_tests(result
, ext_so_path
)
887 if failures
== len(result
.failures
) and errors
== len(result
.errors
):
891 check_thread_termination()
893 result
.addError(self
, sys
.exc_info())
894 result
.stopTest(self
)
900 def run_tests(self
, result
, ext_so_path
):
901 self
.run_doctests(self
.module
, result
, ext_so_path
)
903 def run_doctests(self
, module_or_name
, result
, ext_so_path
):
904 def run_test(result
):
905 if isinstance(module_or_name
, basestring
):
906 module
= import_ext(module_or_name
, ext_so_path
)
908 module
= module_or_name
909 tests
= doctest
.DocTestSuite(module
)
911 run_forked_test(result
, run_test
, self
.shortDescription(), self
.fork
)
913 def run_forked_test(result
, run_func
, test_name
, fork
=True):
914 if not fork
or sys
.version_info
[0] >= 3 or not hasattr(os
, 'fork'):
921 # fork to make sure we do not keep the tested module loaded
922 result_handle
, result_file
= tempfile
.mkstemp()
923 os
.close(result_handle
)
930 tests
= partial_result
= None
932 partial_result
= PartialTestResult(result
)
933 run_func(partial_result
)
939 if partial_result
is not None:
941 # importing failed, try to fake a test class
943 failureException
=sys
.exc_info()[1],
944 _shortDescription
=test_name
,
946 partial_result
.addError(tests
, sys
.exc_info())
947 output
= open(result_file
, 'wb')
948 pickle
.dump(partial_result
.data(), output
)
950 traceback
.print_exc()
952 try: sys
.stderr
.flush()
954 try: sys
.stdout
.flush()
957 if output
is not None:
961 os
._exit
(result_code
)
964 cid
, result_code
= os
.waitpid(child_id
, 0)
965 module_name
= test_name
.split()[-1]
966 # os.waitpid returns the child's result code in the
967 # upper byte of result_code, and the signal it was
968 # killed by in the lower byte
969 if result_code
& 255:
970 raise Exception("Tests in module '%s' were unexpectedly killed by signal %d"%
971 (module_name
, result_code
& 255))
973 if result_code
in (0,1):
974 input = open(result_file
, 'rb')
976 PartialTestResult
.join_results(result
, pickle
.load(input))
980 raise Exception("Tests in module '%s' exited with status %d" %
981 (module_name
, result_code
))
983 try: os
.unlink(result_file
)
986 class PureDoctestTestCase(unittest
.TestCase
):
987 def __init__(self
, module_name
, module_path
):
988 self
.module_name
= module_name
989 self
.module_path
= module_path
990 unittest
.TestCase
.__init
__(self
, 'run')
992 def shortDescription(self
):
993 return "running pure doctests in %s" % self
.module_name
995 def run(self
, result
=None):
997 result
= self
.defaultTestResult()
998 loaded_module_name
= 'pure_doctest__' + self
.module_name
999 result
.startTest(self
)
1004 m
= imp
.load_source(loaded_module_name
, self
.module_path
)
1006 doctest
.DocTestSuite(m
).run(result
)
1009 if loaded_module_name
in sys
.modules
:
1010 del sys
.modules
[loaded_module_name
]
1011 check_thread_termination()
1013 result
.addError(self
, sys
.exc_info())
1014 result
.stopTest(self
)
1020 is_private_field
= re
.compile('^_[^_]').match
1022 class _FakeClass(object):
1023 def __init__(self
, **kwargs
):
1024 self
._shortDescription
= kwargs
.get('module_name')
1025 self
.__dict
__.update(kwargs
)
1026 def shortDescription(self
):
1027 return self
._shortDescription
1029 try: # Py2.7+ and Py3.2+
1030 from unittest
.runner
import _TextTestResult
1032 from unittest
import _TextTestResult
1034 class PartialTestResult(_TextTestResult
):
1035 def __init__(self
, base_result
):
1036 _TextTestResult
.__init
__(
1037 self
, self
._StringIO
(), True,
1038 base_result
.dots
+ base_result
.showAll
*2)
1040 def strip_error_results(self
, results
):
1041 for test_case
, error
in results
:
1042 for attr_name
in filter(is_private_field
, dir(test_case
)):
1043 if attr_name
== '_dt_test':
1044 test_case
._dt
_test
= _FakeClass(
1045 name
=test_case
._dt
_test
.name
)
1046 elif attr_name
!= '_shortDescription':
1047 setattr(test_case
, attr_name
, None)
1050 self
.strip_error_results(self
.failures
)
1051 self
.strip_error_results(self
.errors
)
1052 return (self
.failures
, self
.errors
, self
.testsRun
,
1053 self
.stream
.getvalue())
1055 def join_results(result
, data
):
1056 """Static method for merging the result back into the main
1059 failures
, errors
, tests_run
, output
= data
1061 result
.stream
.write(output
)
1062 result
.errors
.extend(errors
)
1063 result
.failures
.extend(failures
)
1064 result
.testsRun
+= tests_run
1066 join_results
= staticmethod(join_results
)
1068 class _StringIO(StringIO
):
1069 def writeln(self
, line
):
1070 self
.write("%s\n" % line
)
1073 class CythonUnitTestCase(CythonRunTestCase
):
1074 def shortDescription(self
):
1075 return "compiling (%s) tests in %s" % (self
.language
, self
.module
)
1077 def run_tests(self
, result
, ext_so_path
):
1078 module
= import_ext(self
.module
, ext_so_path
)
1079 unittest
.defaultTestLoader
.loadTestsFromModule(module
).run(result
)
1082 class CythonPyregrTestCase(CythonRunTestCase
):
1084 CythonRunTestCase
.setUp(self
)
1085 from Cython
.Compiler
import Options
1086 Options
.error_on_unknown_names
= False
1087 Options
.error_on_uninitialized
= False
1088 Options
.directive_defaults
.update(dict(
1089 binding
=True, always_allow_keywords
=True,
1090 set_initial_path
="SOURCEFILE"))
1091 patch_inspect_isfunction()
1093 def related_files(self
, test_directory
, module_name
):
1094 return _list_pyregr_data_files(test_directory
)
1096 def _run_unittest(self
, result
, *classes
):
1097 """Run tests from unittest.TestCase-derived classes."""
1098 valid_types
= (unittest
.TestSuite
, unittest
.TestCase
)
1099 suite
= unittest
.TestSuite()
1101 if isinstance(cls
, str):
1102 if cls
in sys
.modules
:
1103 suite
.addTest(unittest
.findTestCases(sys
.modules
[cls
]))
1105 raise ValueError("str arguments must be keys in sys.modules")
1106 elif isinstance(cls
, valid_types
):
1109 suite
.addTest(unittest
.makeSuite(cls
))
1112 def _run_doctest(self
, result
, module
):
1113 self
.run_doctests(module
, result
, None)
1115 def run_tests(self
, result
, ext_so_path
):
1117 from test
import support
1118 except ImportError: # Python2.x
1119 from test
import test_support
as support
1121 def run_test(result
):
1122 def run_unittest(*classes
):
1123 return self
._run
_unittest
(result
, *classes
)
1124 def run_doctest(module
, verbosity
=None):
1125 return self
._run
_doctest
(result
, module
)
1127 backup
= (support
.run_unittest
, support
.run_doctest
)
1128 support
.run_unittest
= run_unittest
1129 support
.run_doctest
= run_doctest
1133 sys
.stdout
.flush() # helps in case of crashes
1134 module
= import_ext(self
.module
, ext_so_path
)
1135 sys
.stdout
.flush() # helps in case of crashes
1136 if hasattr(module
, 'test_main'):
1138 sys
.stdout
.flush() # helps in case of crashes
1139 except (unittest
.SkipTest
, support
.ResourceDenied
):
1140 result
.addSkip(self
, 'ok')
1142 support
.run_unittest
, support
.run_doctest
= backup
1144 run_forked_test(result
, run_test
, self
.shortDescription(), self
.fork
)
1146 include_debugger
= IS_CPYTHON
and sys
.version_info
[:2] > (2, 5)
1148 def collect_unittests(path
, module_prefix
, suite
, selectors
, exclude_selectors
):
1149 def file_matches(filename
):
1150 return filename
.startswith("Test") and filename
.endswith(".py")
1152 def package_matches(dirname
):
1153 return dirname
== "Tests"
1155 loader
= unittest
.TestLoader()
1157 if include_debugger
:
1160 skipped_dirs
= ['Cython' + os
.path
.sep
+ 'Debugger' + os
.path
.sep
]
1162 for dirpath
, dirnames
, filenames
in os
.walk(path
):
1163 if dirpath
!= path
and "__init__.py" not in filenames
:
1164 skipped_dirs
.append(dirpath
+ os
.path
.sep
)
1167 for dir in skipped_dirs
:
1168 if dirpath
.startswith(dir):
1172 parentname
= os
.path
.split(dirpath
)[-1]
1173 if package_matches(parentname
):
1176 filepath
= os
.path
.join(dirpath
, f
)[:-len(".py")]
1177 modulename
= module_prefix
+ filepath
[len(path
)+1:].replace(os
.path
.sep
, '.')
1178 if not [ 1 for match
in selectors
if match(modulename
) ]:
1180 if [ 1 for match
in exclude_selectors
if match(modulename
) ]:
1182 module
= __import__(modulename
)
1183 for x
in modulename
.split('.')[1:]:
1184 module
= getattr(module
, x
)
1185 suite
.addTests([loader
.loadTestsFromModule(module
)])
1189 def collect_doctests(path
, module_prefix
, suite
, selectors
, exclude_selectors
):
1190 def package_matches(dirname
):
1191 if dirname
== 'Debugger' and not include_debugger
:
1193 return dirname
not in ("Mac", "Distutils", "Plex")
1194 def file_matches(filename
):
1195 filename
, ext
= os
.path
.splitext(filename
)
1196 blacklist
= ['libcython', 'libpython', 'test_libcython_in_gdb',
1198 return (ext
== '.py' and not
1199 '~' in filename
and not
1200 '#' in filename
and not
1201 filename
.startswith('.') and not
1202 filename
in blacklist
)
1204 for dirpath
, dirnames
, filenames
in os
.walk(path
):
1205 for dir in list(dirnames
):
1206 if not package_matches(dir):
1207 dirnames
.remove(dir)
1210 if not f
.endswith('.py'): continue
1211 filepath
= os
.path
.join(dirpath
, f
)
1212 if os
.path
.getsize(filepath
) == 0: continue
1213 filepath
= filepath
[:-len(".py")]
1214 modulename
= module_prefix
+ filepath
[len(path
)+1:].replace(os
.path
.sep
, '.')
1215 if not [ 1 for match
in selectors
if match(modulename
) ]:
1217 if [ 1 for match
in exclude_selectors
if match(modulename
) ]:
1219 if 'in_gdb' in modulename
:
1220 # These should only be imported from gdb.
1222 module
= __import__(modulename
)
1223 for x
in modulename
.split('.')[1:]:
1224 module
= getattr(module
, x
)
1225 if hasattr(module
, "__doc__") or hasattr(module
, "__test__"):
1227 suite
.addTest(doctest
.DocTestSuite(module
))
1228 except ValueError: # no tests
1232 class EndToEndTest(unittest
.TestCase
):
1234 This is a test of build/*.srctree files, where srctree defines a full
1235 directory structure and its header gives a list of commands to run.
1237 cython_root
= os
.path
.dirname(os
.path
.abspath(__file__
))
1239 def __init__(self
, treefile
, workdir
, cleanup_workdir
=True):
1240 self
.name
= os
.path
.splitext(os
.path
.basename(treefile
))[0]
1241 self
.treefile
= treefile
1242 self
.workdir
= os
.path
.join(workdir
, self
.name
)
1243 self
.cleanup_workdir
= cleanup_workdir
1244 cython_syspath
= [self
.cython_root
]
1245 for path
in sys
.path
:
1246 if path
.startswith(self
.cython_root
) and path
not in cython_syspath
:
1247 # Py3 installation and refnanny build prepend their
1248 # fixed paths to sys.path => prefer that over the
1249 # generic one (cython_root itself goes last)
1250 cython_syspath
.append(path
)
1251 self
.cython_syspath
= os
.pathsep
.join(cython_syspath
[::-1])
1252 unittest
.TestCase
.__init
__(self
)
1254 def shortDescription(self
):
1255 return "End-to-end %s" % self
.name
1258 from Cython
.TestUtils
import unpack_source_tree
1259 _
, self
.commands
= unpack_source_tree(self
.treefile
, self
.workdir
)
1260 self
.old_dir
= os
.getcwd()
1261 os
.chdir(self
.workdir
)
1262 if self
.workdir
not in sys
.path
:
1263 sys
.path
.insert(0, self
.workdir
)
1266 if self
.cleanup_workdir
:
1267 for trial
in range(5):
1269 shutil
.rmtree(self
.workdir
)
1274 os
.chdir(self
.old_dir
)
1276 def _try_decode(self
, content
):
1278 return content
.decode()
1279 except UnicodeDecodeError:
1280 return content
.decode('iso-8859-1')
1283 self
.success
= False
1284 commands
= (self
.commands
1285 .replace("CYTHON", "PYTHON %s" % os
.path
.join(self
.cython_root
, 'cython.py'))
1286 .replace("PYTHON", sys
.executable
))
1287 old_path
= os
.environ
.get('PYTHONPATH')
1288 os
.environ
['PYTHONPATH'] = self
.cython_syspath
+ os
.pathsep
+ (old_path
or '')
1290 for command
in filter(None, commands
.splitlines()):
1291 p
= subprocess
.Popen(command
,
1292 stderr
=subprocess
.PIPE
,
1293 stdout
=subprocess
.PIPE
,
1295 out
, err
= p
.communicate()
1299 print(self
._try
_decode
(out
))
1300 print(self
._try
_decode
(err
))
1301 self
.assertEqual(0, res
, "non-zero exit status")
1304 os
.environ
['PYTHONPATH'] = old_path
1306 del os
.environ
['PYTHONPATH']
1310 # TODO: Support cython_freeze needed here as well.
1311 # TODO: Windows support.
1313 class EmbedTest(unittest
.TestCase
):
1315 working_dir
= "Demos/embed"
1318 self
.old_dir
= os
.getcwd()
1319 os
.chdir(self
.working_dir
)
1321 "make PYTHON='%s' clean > /dev/null" % sys
.executable
)
1326 "make PYTHON='%s' clean > /dev/null" % sys
.executable
)
1329 os
.chdir(self
.old_dir
)
1331 def test_embed(self
):
1332 from distutils
import sysconfig
1333 libname
= sysconfig
.get_config_var('LIBRARY')
1334 libdir
= sysconfig
.get_config_var('LIBDIR')
1335 if not os
.path
.isdir(libdir
) or libname
not in os
.listdir(libdir
):
1336 libdir
= os
.path
.join(os
.path
.dirname(sys
.executable
), '..', 'lib')
1337 if not os
.path
.isdir(libdir
) or libname
not in os
.listdir(libdir
):
1338 libdir
= os
.path
.join(libdir
, 'python%d.%d' % sys
.version_info
[:2], 'config')
1339 if not os
.path
.isdir(libdir
) or libname
not in os
.listdir(libdir
):
1340 # report the error for the original directory
1341 libdir
= sysconfig
.get_config_var('LIBDIR')
1342 cython
= 'cython.py'
1343 if sys
.version_info
[0] >=3 and CY3_DIR
:
1344 cython
= os
.path
.join(CY3_DIR
, cython
)
1345 cython
= os
.path
.abspath(os
.path
.join('..', '..', cython
))
1346 self
.assert_(os
.system(
1347 "make PYTHON='%s' CYTHON='%s' LIBDIR1='%s' test > make.output" % (sys
.executable
, cython
, libdir
)) == 0)
1349 os
.remove('make.output')
1353 class MissingDependencyExcluder
:
1354 def __init__(self
, deps
):
1355 # deps: { matcher func : module name }
1356 self
.exclude_matchers
= []
1357 for matcher
, mod
in deps
.items():
1361 self
.exclude_matchers
.append(string_selector(matcher
))
1362 self
.tests_missing_deps
= []
1363 def __call__(self
, testname
, tags
=None):
1364 for matcher
in self
.exclude_matchers
:
1365 if matcher(testname
, tags
):
1366 self
.tests_missing_deps
.append(testname
)
1370 class VersionDependencyExcluder
:
1371 def __init__(self
, deps
):
1372 # deps: { version : matcher func }
1373 from sys
import version_info
1374 self
.exclude_matchers
= []
1375 for ver
, (compare
, matcher
) in deps
.items():
1376 if compare(version_info
, ver
):
1377 self
.exclude_matchers
.append(matcher
)
1378 self
.tests_missing_deps
= []
1379 def __call__(self
, testname
, tags
=None):
1380 for matcher
in self
.exclude_matchers
:
1381 if matcher(testname
):
1382 self
.tests_missing_deps
.append(testname
)
1386 class FileListExcluder
:
1388 def __init__(self
, list_file
):
1392 for line
in f
.readlines():
1394 if line
and line
[0] != '#':
1395 self
.excludes
[line
.split()[0]] = True
1399 def __call__(self
, testname
, tags
=None):
1400 return testname
in self
.excludes
or testname
.split('.')[-1] in self
.excludes
1404 def __init__(self
, tag
, value
):
1408 def __call__(self
, testname
, tags
=None):
1412 return self
.value
in tags
[self
.tag
]
1414 class RegExSelector
:
1416 def __init__(self
, pattern_string
):
1418 self
.pattern
= re
.compile(pattern_string
, re
.I|re
.U
)
1420 print('Invalid pattern: %r' % pattern_string
)
1423 def __call__(self
, testname
, tags
=None):
1424 return self
.pattern
.search(testname
)
1426 def string_selector(s
):
1429 return RegExSelector(s
)
1431 return TagsSelector(s
[:ix
], s
[ix
+1:])
1433 class ShardExcludeSelector
:
1434 # This is an exclude selector so it can override the (include) selectors.
1435 # It may not provide uniform distribution (in time or count), but is a
1436 # determanistic partition of the tests which is important.
1437 def __init__(self
, shard_num
, shard_count
):
1438 self
.shard_num
= shard_num
1439 self
.shard_count
= shard_count
1441 def __call__(self
, testname
, tags
=None):
1442 return abs(hash(testname
)) % self
.shard_count
!= self
.shard_num
1445 def refactor_for_py3(distdir
, cy3_dir
):
1446 # need to convert Cython sources first
1447 import lib2to3
.refactor
1448 from distutils
.util
import copydir_run_2to3
1449 fixers
= [ fix
for fix
in lib2to3
.refactor
.get_fixers_from_package("lib2to3.fixes")
1450 if fix
.split('fix_')[-1] not in ('next',)
1452 if not os
.path
.exists(cy3_dir
):
1453 os
.makedirs(cy3_dir
)
1454 import distutils
.log
as dlog
1455 dlog
.set_threshold(dlog
.INFO
)
1456 copydir_run_2to3(distdir
, cy3_dir
, fixer_names
=fixers
,
1460 recursive-exclude Cython *
1461 recursive-include Cython *.py *.pyx *.pxd
1462 recursive-include Cython/Debugger/Tests *
1463 recursive-include Cython/Utility *
1464 recursive-exclude pyximport test
1465 include pyximport/*.py
1469 sys
.path
.insert(0, cy3_dir
)
1471 for keep_2x_file
in KEEP_2X_FILES
:
1472 destfile
= os
.path
.join(cy3_dir
, keep_2x_file
)
1473 shutil
.copy(keep_2x_file
, destfile
)
1475 class PendingThreadsError(RuntimeError):
1480 def check_thread_termination(ignore_seen
=True):
1481 if threading
is None: # no threading enabled in CPython
1483 current
= threading
.currentThread()
1484 blocking_threads
= []
1485 for t
in threading
.enumerate():
1486 if not t
.isAlive() or t
== current
:
1491 blocking_threads
.append(t
)
1493 for seen
in threads_seen
:
1497 threads_seen
.append(t
)
1498 blocking_threads
.append(t
)
1499 if not blocking_threads
:
1501 sys
.stderr
.write("warning: left-over threads found after running test:\n")
1502 for t
in blocking_threads
:
1503 sys
.stderr
.write('...%s\n' % repr(t
))
1504 raise PendingThreadsError("left-over threads found after running test")
1506 def subprocess_output(cmd
):
1508 p
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.STDOUT
)
1509 return p
.communicate()[0].decode('UTF-8')
1514 from Cython
.Compiler
.Version
import version
as cython_version
1515 full_version
= cython_version
1516 top
= os
.path
.dirname(os
.path
.abspath(__file__
))
1517 if os
.path
.exists(os
.path
.join(top
, '.git')):
1518 old_dir
= os
.getcwd()
1521 head_commit
= subprocess_output(['git', 'rev-parse', 'HEAD']).strip()
1522 version_commit
= subprocess_output(['git', 'rev-parse', cython_version
]).strip()
1523 diff
= subprocess_output(['git', 'diff', '--stat']).strip()
1524 if head_commit
!= version_commit
:
1525 full_version
+= " " + head_commit
1527 full_version
+= ' + uncommitted changes'
1532 _orig_stdout
, _orig_stderr
= sys
.stdout
, sys
.stderr
1533 def flush_and_terminate(status
):
1535 _orig_stdout
.flush()
1536 _orig_stderr
.flush()
1542 global DISTDIR
, WITH_CYTHON
1543 DISTDIR
= os
.path
.join(os
.getcwd(), os
.path
.dirname(sys
.argv
[0]))
1545 from optparse
import OptionParser
1546 parser
= OptionParser()
1547 parser
.add_option("--no-cleanup", dest
="cleanup_workdir",
1548 action
="store_false", default
=True,
1549 help="do not delete the generated C files (allows passing --no-cython on next run)")
1550 parser
.add_option("--no-cleanup-sharedlibs", dest
="cleanup_sharedlibs",
1551 action
="store_false", default
=True,
1552 help="do not delete the generated shared libary files (allows manual module experimentation)")
1553 parser
.add_option("--no-cleanup-failures", dest
="cleanup_failures",
1554 action
="store_false", default
=True,
1555 help="enable --no-cleanup and --no-cleanup-sharedlibs for failed tests only")
1556 parser
.add_option("--no-cython", dest
="with_cython",
1557 action
="store_false", default
=True,
1558 help="do not run the Cython compiler, only the C compiler")
1559 parser
.add_option("--compiler", dest
="compiler", default
=None,
1560 help="C compiler type")
1561 backend_list
= ','.join(BACKENDS
)
1562 parser
.add_option("--backends", dest
="backends", default
=backend_list
,
1563 help="select backends to test (default: %s)" % backend_list
)
1564 parser
.add_option("--no-c", dest
="use_c",
1565 action
="store_false", default
=True,
1566 help="do not test C compilation backend")
1567 parser
.add_option("--no-cpp", dest
="use_cpp",
1568 action
="store_false", default
=True,
1569 help="do not test C++ compilation backend")
1570 parser
.add_option("--no-unit", dest
="unittests",
1571 action
="store_false", default
=True,
1572 help="do not run the unit tests")
1573 parser
.add_option("--no-doctest", dest
="doctests",
1574 action
="store_false", default
=True,
1575 help="do not run the doctests")
1576 parser
.add_option("--no-file", dest
="filetests",
1577 action
="store_false", default
=True,
1578 help="do not run the file based tests")
1579 parser
.add_option("--no-pyregr", dest
="pyregr",
1580 action
="store_false", default
=True,
1581 help="do not run the regression tests of CPython in tests/pyregr/")
1582 parser
.add_option("--cython-only", dest
="cython_only",
1583 action
="store_true", default
=False,
1584 help="only compile pyx to c, do not run C compiler or run the tests")
1585 parser
.add_option("--no-refnanny", dest
="with_refnanny",
1586 action
="store_false", default
=True,
1587 help="do not regression test reference counting")
1588 parser
.add_option("--no-fork", dest
="fork",
1589 action
="store_false", default
=True,
1590 help="do not fork to run tests")
1591 parser
.add_option("--sys-pyregr", dest
="system_pyregr",
1592 action
="store_true", default
=False,
1593 help="run the regression tests of the CPython installation")
1594 parser
.add_option("-x", "--exclude", dest
="exclude",
1595 action
="append", metavar
="PATTERN",
1596 help="exclude tests matching the PATTERN")
1597 parser
.add_option("--shard_count", dest
="shard_count", metavar
="N",
1598 type=int, default
=1,
1599 help="shard this run into several parallel runs")
1600 parser
.add_option("--shard_num", dest
="shard_num", metavar
="K",
1601 type=int, default
=-1,
1602 help="test only this single shard")
1603 parser
.add_option("-C", "--coverage", dest
="coverage",
1604 action
="store_true", default
=False,
1605 help="collect source coverage data for the Compiler")
1606 parser
.add_option("--coverage-xml", dest
="coverage_xml",
1607 action
="store_true", default
=False,
1608 help="collect source coverage data for the Compiler in XML format")
1609 parser
.add_option("--coverage-html", dest
="coverage_html",
1610 action
="store_true", default
=False,
1611 help="collect source coverage data for the Compiler in HTML format")
1612 parser
.add_option("-A", "--annotate", dest
="annotate_source",
1613 action
="store_true", default
=True,
1614 help="generate annotated HTML versions of the test source files")
1615 parser
.add_option("--no-annotate", dest
="annotate_source",
1616 action
="store_false",
1617 help="do not generate annotated HTML versions of the test source files")
1618 parser
.add_option("-v", "--verbose", dest
="verbosity",
1619 action
="count", default
=0,
1620 help="display test progress, pass twice to print test names")
1621 parser
.add_option("-T", "--ticket", dest
="tickets",
1623 help="a bug ticket number to run the respective test in 'tests/*'")
1624 parser
.add_option("-3", dest
="language_level",
1625 action
="store_const", const
=3, default
=2,
1626 help="set language level to Python 3 (useful for running the CPython regression tests)'")
1627 parser
.add_option("--xml-output", dest
="xml_output_dir", metavar
="DIR",
1628 help="write test results in XML to directory DIR")
1629 parser
.add_option("--exit-ok", dest
="exit_ok", default
=False,
1630 action
="store_true",
1631 help="exit without error code even on test failures")
1632 parser
.add_option("--root-dir", dest
="root_dir", default
=os
.path
.join(DISTDIR
, 'tests'),
1633 help="working directory")
1634 parser
.add_option("--work-dir", dest
="work_dir", default
=os
.path
.join(os
.getcwd(), 'BUILD'),
1635 help="working directory")
1636 parser
.add_option("--cython-dir", dest
="cython_dir", default
=os
.getcwd(),
1637 help="Cython installation directory (default: use local source version)")
1638 parser
.add_option("--debug", dest
="for_debugging", default
=False, action
="store_true",
1639 help="configure for easier use with a debugger (e.g. gdb)")
1640 parser
.add_option("--pyximport-py", dest
="pyximport_py", default
=False, action
="store_true",
1641 help="use pyximport to automatically compile imported .pyx and .py files")
1642 parser
.add_option("--watermark", dest
="watermark", default
=None,
1643 help="deterministic generated by string")
1645 options
, cmd_args
= parser
.parse_args()
1647 WORKDIR
= os
.path
.abspath(options
.work_dir
)
1649 if sys
.version_info
[0] >= 3:
1650 options
.doctests
= False
1651 if options
.with_cython
:
1652 sys
.path
.insert(0, options
.cython_dir
)
1654 # try if Cython is installed in a Py3 version
1655 import Cython
.Compiler
.Main
1657 # back out anything the import process loaded, then
1658 # 2to3 the Cython sources to make them re-importable
1659 cy_modules
= [ name
for name
in sys
.modules
1660 if name
== 'Cython' or name
.startswith('Cython.') ]
1661 for name
in cy_modules
:
1662 del sys
.modules
[name
]
1663 # hasn't been refactored yet - do it now
1665 CY3_DIR
= cy3_dir
= os
.path
.join(WORKDIR
, 'Cy3')
1666 if sys
.version_info
>= (3,1):
1667 refactor_for_py3(DISTDIR
, cy3_dir
)
1668 elif os
.path
.isdir(cy3_dir
):
1669 sys
.path
.insert(0, cy3_dir
)
1671 options
.with_cython
= False
1673 if options
.watermark
:
1674 import Cython
.Compiler
.Version
1675 Cython
.Compiler
.Version
.watermark
= options
.watermark
1677 WITH_CYTHON
= options
.with_cython
1680 if options
.coverage
or options
.coverage_xml
or options
.coverage_html
:
1681 if options
.shard_count
<= 1 and options
.shard_num
< 0:
1683 options
.coverage
= options
.coverage_xml
= options
.coverage_html
= False
1685 print("Enabling coverage analysis")
1686 from coverage
import coverage
as _coverage
1687 coverage
= _coverage(branch
=True, omit
=['Test*'])
1692 global CompilationOptions
, pyrex_default_options
, cython_compile
1693 from Cython
.Compiler
.Main
import \
1694 CompilationOptions
, \
1695 default_options
as pyrex_default_options
, \
1696 compile as cython_compile
1697 from Cython
.Compiler
import Errors
1698 Errors
.LEVEL
= 0 # show all warnings
1699 from Cython
.Compiler
import Options
1700 Options
.generate_cleanup_code
= 3 # complete cleanup code
1701 from Cython
.Compiler
import DebugFlags
1702 DebugFlags
.debug_temp_code_comments
= 1
1704 if options
.shard_count
> 1 and options
.shard_num
== -1:
1705 import multiprocessing
1706 pool
= multiprocessing
.Pool(options
.shard_count
)
1707 tasks
= [(options
, cmd_args
, shard_num
) for shard_num
in range(options
.shard_count
)]
1709 for shard_num
, return_code
in pool
.imap_unordered(runtests_callback
, tasks
):
1710 if return_code
!= 0:
1711 errors
.append(shard_num
)
1712 print("FAILED (%s/%s)" % (shard_num
, options
.shard_count
))
1713 print("ALL DONE (%s/%s)" % (shard_num
, options
.shard_count
))
1717 print("Errors for shards %s" % ", ".join([str(e
) for e
in errors
]))
1722 _
, return_code
= runtests(options
, cmd_args
, coverage
)
1726 check_thread_termination(ignore_seen
=False)
1727 except PendingThreadsError
:
1728 # normal program exit won't kill the threads, do it the hard way here
1729 flush_and_terminate(return_code
)
1731 sys
.exit(return_code
)
1734 def runtests_callback(args
):
1735 options
, cmd_args
, shard_num
= args
1736 options
.shard_num
= shard_num
1737 return runtests(options
, cmd_args
)
1739 def runtests(options
, cmd_args
, coverage
=None):
1741 WITH_CYTHON
= options
.with_cython
1742 ROOTDIR
= os
.path
.abspath(options
.root_dir
)
1743 WORKDIR
= os
.path
.abspath(options
.work_dir
)
1745 if options
.shard_num
> -1:
1746 WORKDIR
= os
.path
.join(WORKDIR
, str(options
.shard_num
))
1749 UNITTEST_MODULE
= "Cython"
1750 UNITTEST_ROOT
= os
.path
.join(os
.path
.dirname(__file__
), UNITTEST_MODULE
)
1752 if os
.path
.exists(WORKDIR
):
1753 for path
in os
.listdir(WORKDIR
):
1754 if path
in ("support", "Cy3"): continue
1755 shutil
.rmtree(os
.path
.join(WORKDIR
, path
), ignore_errors
=True)
1756 if not os
.path
.exists(WORKDIR
):
1757 os
.makedirs(WORKDIR
)
1759 if options
.shard_num
<= 0:
1760 sys
.stderr
.write("Python %s\n" % sys
.version
)
1761 sys
.stderr
.write("\n")
1763 sys
.stderr
.write("Running tests against Cython %s\n" % get_version())
1765 sys
.stderr
.write("Running tests without Cython.\n")
1767 if options
.for_debugging
:
1768 options
.cleanup_workdir
= False
1769 options
.cleanup_sharedlibs
= False
1770 options
.fork
= False
1771 if WITH_CYTHON
and include_debugger
:
1772 from Cython
.Compiler
.Main
import default_options
as compiler_default_options
1773 compiler_default_options
['gdb_debug'] = True
1774 compiler_default_options
['output_dir'] = os
.getcwd()
1776 if options
.with_refnanny
:
1777 from pyximport
.pyxbuild
import pyx_to_dll
1778 libpath
= pyx_to_dll(os
.path
.join("Cython", "Runtime", "refnanny.pyx"),
1780 pyxbuild_dir
=os
.path
.join(WORKDIR
, "support"))
1781 sys
.path
.insert(0, os
.path
.split(libpath
)[0])
1782 CFLAGS
.append("-DCYTHON_REFNANNY=1")
1784 if options
.xml_output_dir
and options
.fork
:
1785 # doesn't currently work together
1786 sys
.stderr
.write("Disabling forked testing to support XML test output\n")
1787 options
.fork
= False
1789 if WITH_CYTHON
and options
.language_level
== 3:
1790 sys
.stderr
.write("Using Cython language level 3.\n")
1794 for ticket_number
in options
.tickets
:
1796 cmd_args
.append('ticket:%s' % ticket_number
)
1798 for selector
in cmd_args
:
1799 if selector
.startswith('bugs'):
1802 selectors
= [ string_selector(r
) for r
in cmd_args
]
1804 selectors
= [ lambda x
, tags
=None: True ]
1806 # Chech which external modules are not present and exclude tests
1807 # which depends on them (by prefix)
1809 missing_dep_excluder
= MissingDependencyExcluder(EXT_DEP_MODULES
)
1810 version_dep_excluder
= VersionDependencyExcluder(VER_DEP_MODULES
)
1811 exclude_selectors
= [missing_dep_excluder
, version_dep_excluder
] # want to print msg at exit
1814 exclude_selectors
+= [ string_selector(r
) for r
in options
.exclude
]
1816 if options
.shard_num
> -1:
1817 exclude_selectors
.append(ShardExcludeSelector(options
.shard_num
, options
.shard_count
))
1820 exclude_selectors
+= [ FileListExcluder(os
.path
.join(ROOTDIR
, "bugs.txt")) ]
1822 if sys
.platform
in ['win32', 'cygwin'] and sys
.version_info
< (2,6):
1823 exclude_selectors
+= [ lambda x
: x
== "run.specialfloat" ]
1826 if options
.compiler
:
1827 COMPILER
= options
.compiler
1829 selected_backends
= [ name
.strip() for name
in options
.backends
.split(',') if name
.strip() ]
1831 for backend
in selected_backends
:
1832 if backend
== 'c' and not options
.use_c
:
1834 elif backend
== 'cpp' and not options
.use_cpp
:
1836 elif backend
not in BACKENDS
:
1837 sys
.stderr
.write("Unknown backend requested: '%s' not one of [%s]\n" % (
1838 backend
, ','.join(BACKENDS
)))
1840 backends
.append(backend
)
1841 if options
.shard_num
<= 0:
1842 sys
.stderr
.write("Backends: %s\n" % ','.join(backends
))
1843 languages
= backends
1845 sys
.stderr
.write("\n")
1847 test_suite
= unittest
.TestSuite()
1849 if options
.unittests
:
1850 collect_unittests(UNITTEST_ROOT
, UNITTEST_MODULE
+ ".", test_suite
, selectors
, exclude_selectors
)
1852 if options
.doctests
:
1853 collect_doctests(UNITTEST_ROOT
, UNITTEST_MODULE
+ ".", test_suite
, selectors
, exclude_selectors
)
1855 if options
.filetests
and languages
:
1856 filetests
= TestBuilder(ROOTDIR
, WORKDIR
, selectors
, exclude_selectors
,
1857 options
.annotate_source
, options
.cleanup_workdir
,
1858 options
.cleanup_sharedlibs
, options
.cleanup_failures
,
1860 options
.cython_only
, languages
, test_bugs
,
1861 options
.fork
, options
.language_level
)
1862 test_suite
.addTest(filetests
.build_suite())
1864 if options
.system_pyregr
and languages
:
1865 sys_pyregr_dir
= os
.path
.join(sys
.prefix
, 'lib', 'python'+sys
.version
[:3], 'test')
1866 if os
.path
.isdir(sys_pyregr_dir
):
1867 filetests
= TestBuilder(ROOTDIR
, WORKDIR
, selectors
, exclude_selectors
,
1868 options
.annotate_source
, options
.cleanup_workdir
,
1869 options
.cleanup_sharedlibs
, options
.cleanup_failures
,
1871 options
.cython_only
, languages
, test_bugs
,
1872 options
.fork
, sys
.version_info
[0])
1873 sys
.stderr
.write("Including CPython regression tests in %s\n" % sys_pyregr_dir
)
1874 test_suite
.addTest(filetests
.handle_directory(sys_pyregr_dir
, 'pyregr'))
1876 if options
.xml_output_dir
:
1877 from Cython
.Tests
.xmlrunner
import XMLTestRunner
1878 test_runner
= XMLTestRunner(output
=options
.xml_output_dir
,
1879 verbose
=options
.verbosity
> 0)
1881 test_runner
= unittest
.TextTestRunner(verbosity
=options
.verbosity
)
1883 if options
.pyximport_py
:
1884 from pyximport
import pyximport
1885 pyximport
.install(pyimport
=True, build_dir
=os
.path
.join(WORKDIR
, '_pyximport'),
1886 load_py_module_on_import_failure
=True, inplace
=True)
1888 result
= test_runner
.run(test_suite
)
1890 if coverage
is not None:
1892 ignored_modules
= ('Options', 'Version', 'DebugFlags', 'CmdLine')
1893 modules
= [ module
for name
, module
in sys
.modules
.items()
1894 if module
is not None and
1895 name
.startswith('Cython.Compiler.') and
1896 name
[len('Cython.Compiler.'):] not in ignored_modules
]
1897 if options
.coverage
:
1898 coverage
.report(modules
, show_missing
=0)
1899 if options
.coverage_xml
:
1900 coverage
.xml_report(modules
, outfile
="coverage-report.xml")
1901 if options
.coverage_html
:
1902 coverage
.html_report(modules
, directory
="coverage-report-html")
1904 if missing_dep_excluder
.tests_missing_deps
:
1905 sys
.stderr
.write("Following tests excluded because of missing dependencies on your system:\n")
1906 for test
in missing_dep_excluder
.tests_missing_deps
:
1907 sys
.stderr
.write(" %s\n" % test
)
1909 if options
.with_refnanny
:
1911 sys
.stderr
.write("\n".join([repr(x
) for x
in refnanny
.reflog
]))
1914 return options
.shard_num
, 0
1916 return options
.shard_num
, not result
.wasSuccessful()
1919 if __name__
== '__main__':
1922 except SystemExit: # <= Py2.4 ...
1925 traceback
.print_exc()
1927 check_thread_termination(ignore_seen
=False)
1928 except PendingThreadsError
:
1929 # normal program exit won't kill the threads, do it the hard way here
1930 flush_and_terminate(1)