580 lines
21 KiB
Python
580 lines
21 KiB
Python
import os
|
|
import sys
|
|
import re
|
|
from unittest import TestCase
|
|
try:
|
|
from unittest.mock import patch, Mock
|
|
except ImportError: # Py2
|
|
from mock import patch, Mock
|
|
try:
|
|
from StringIO import StringIO
|
|
except ImportError:
|
|
from io import StringIO # doesn't accept 'str' in Py2
|
|
|
|
from .. import Options
|
|
from ..CmdLine import parse_command_line
|
|
|
|
from .Utils import backup_Options, restore_Options, check_global_options
|
|
|
|
unpatched_exists = os.path.exists
|
|
|
|
def patched_exists(path):
|
|
# avoid the Cython command raising a file not found error
|
|
if path in (
|
|
'source.pyx',
|
|
os.path.join('/work/dir', 'source.pyx'),
|
|
os.path.join('my_working_path', 'source.pyx'),
|
|
'file.pyx',
|
|
'file1.pyx',
|
|
'file2.pyx',
|
|
'file3.pyx',
|
|
'foo.pyx',
|
|
'bar.pyx',
|
|
):
|
|
return True
|
|
return unpatched_exists(path)
|
|
|
|
@patch('os.path.exists', new=Mock(side_effect=patched_exists))
|
|
class CmdLineParserTest(TestCase):
|
|
def setUp(self):
|
|
self._options_backup = backup_Options()
|
|
|
|
def tearDown(self):
|
|
restore_Options(self._options_backup)
|
|
|
|
def check_default_global_options(self, white_list=[]):
|
|
self.assertEqual(check_global_options(self._options_backup, white_list), "")
|
|
|
|
def check_default_options(self, options, white_list=[]):
|
|
default_options = Options.CompilationOptions(Options.default_options)
|
|
no_value = object()
|
|
for name in default_options.__dict__.keys():
|
|
if name not in white_list:
|
|
self.assertEqual(getattr(options, name, no_value), getattr(default_options, name), msg="error in option " + name)
|
|
|
|
def test_short_options(self):
|
|
options, sources = parse_command_line([
|
|
'-V', '-l', '-+', '-t', '-v', '-v', '-v', '-p', '-D', '-a', '-3',
|
|
])
|
|
self.assertFalse(sources)
|
|
self.assertTrue(options.show_version)
|
|
self.assertTrue(options.use_listing_file)
|
|
self.assertTrue(options.cplus)
|
|
self.assertTrue(options.timestamps)
|
|
self.assertTrue(options.verbose >= 3)
|
|
self.assertTrue(Options.embed_pos_in_docstring)
|
|
self.assertFalse(Options.docstrings)
|
|
self.assertTrue(Options.annotate)
|
|
self.assertEqual(options.language_level, 3)
|
|
|
|
options, sources = parse_command_line([
|
|
'-f', '-2', 'source.pyx',
|
|
])
|
|
self.assertTrue(sources)
|
|
self.assertTrue(len(sources) == 1)
|
|
self.assertFalse(options.timestamps)
|
|
self.assertEqual(options.language_level, 2)
|
|
|
|
def test_long_options(self):
|
|
options, sources = parse_command_line([
|
|
'--version', '--create-listing', '--cplus', '--embed', '--timestamps',
|
|
'--verbose', '--verbose', '--verbose',
|
|
'--embed-positions', '--no-docstrings', '--annotate', '--lenient',
|
|
])
|
|
self.assertFalse(sources)
|
|
self.assertTrue(options.show_version)
|
|
self.assertTrue(options.use_listing_file)
|
|
self.assertTrue(options.cplus)
|
|
self.assertEqual(Options.embed, 'main')
|
|
self.assertTrue(options.timestamps)
|
|
self.assertTrue(options.verbose >= 3)
|
|
self.assertTrue(Options.embed_pos_in_docstring)
|
|
self.assertFalse(Options.docstrings)
|
|
self.assertTrue(Options.annotate)
|
|
self.assertFalse(Options.error_on_unknown_names)
|
|
self.assertFalse(Options.error_on_uninitialized)
|
|
|
|
options, sources = parse_command_line([
|
|
'--force', 'source.pyx',
|
|
])
|
|
self.assertTrue(sources)
|
|
self.assertTrue(len(sources) == 1)
|
|
self.assertFalse(options.timestamps)
|
|
|
|
def test_options_with_values(self):
|
|
options, sources = parse_command_line([
|
|
'--embed=huhu',
|
|
'-I/test/include/dir1', '--include-dir=/test/include/dir2',
|
|
'--include-dir', '/test/include/dir3',
|
|
'--working=/work/dir',
|
|
'source.pyx',
|
|
'--output-file=/output/dir',
|
|
'--pre-import=/pre/import',
|
|
'--cleanup=3',
|
|
'--annotate-coverage=cov.xml',
|
|
'--gdb-outdir=/gdb/outdir',
|
|
'--directive=wraparound=false',
|
|
])
|
|
self.assertEqual(sources, ['source.pyx'])
|
|
self.assertEqual(Options.embed, 'huhu')
|
|
self.assertEqual(options.include_path, ['/test/include/dir1', '/test/include/dir2', '/test/include/dir3'])
|
|
self.assertEqual(options.working_path, '/work/dir')
|
|
self.assertEqual(options.output_file, '/output/dir')
|
|
self.assertEqual(Options.pre_import, '/pre/import')
|
|
self.assertEqual(Options.generate_cleanup_code, 3)
|
|
self.assertTrue(Options.annotate)
|
|
self.assertEqual(Options.annotate_coverage_xml, 'cov.xml')
|
|
self.assertTrue(options.gdb_debug)
|
|
self.assertEqual(options.output_dir, '/gdb/outdir')
|
|
self.assertEqual(options.compiler_directives['wraparound'], False)
|
|
|
|
def test_embed_before_positional(self):
|
|
options, sources = parse_command_line([
|
|
'--embed',
|
|
'source.pyx',
|
|
])
|
|
self.assertEqual(sources, ['source.pyx'])
|
|
self.assertEqual(Options.embed, 'main')
|
|
|
|
def test_two_embeds(self):
|
|
options, sources = parse_command_line([
|
|
'--embed', '--embed=huhu',
|
|
'source.pyx',
|
|
])
|
|
self.assertEqual(sources, ['source.pyx'])
|
|
self.assertEqual(Options.embed, 'huhu')
|
|
|
|
def test_two_embeds2(self):
|
|
options, sources = parse_command_line([
|
|
'--embed=huhu', '--embed',
|
|
'source.pyx',
|
|
])
|
|
self.assertEqual(sources, ['source.pyx'])
|
|
self.assertEqual(Options.embed, 'main')
|
|
|
|
def test_no_annotate(self):
|
|
options, sources = parse_command_line([
|
|
'--embed=huhu', 'source.pyx'
|
|
])
|
|
self.assertFalse(Options.annotate)
|
|
|
|
def test_annotate_short(self):
|
|
options, sources = parse_command_line([
|
|
'-a',
|
|
'source.pyx',
|
|
])
|
|
self.assertEqual(Options.annotate, 'default')
|
|
|
|
def test_annotate_long(self):
|
|
options, sources = parse_command_line([
|
|
'--annotate',
|
|
'source.pyx',
|
|
])
|
|
self.assertEqual(Options.annotate, 'default')
|
|
|
|
def test_annotate_fullc(self):
|
|
options, sources = parse_command_line([
|
|
'--annotate-fullc',
|
|
'source.pyx',
|
|
])
|
|
self.assertEqual(Options.annotate, 'fullc')
|
|
|
|
def test_short_w(self):
|
|
options, sources = parse_command_line([
|
|
'-w', 'my_working_path',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.working_path, 'my_working_path')
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['working_path'])
|
|
|
|
def test_short_o(self):
|
|
options, sources = parse_command_line([
|
|
'-o', 'my_output',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.output_file, 'my_output')
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['output_file'])
|
|
|
|
def test_short_z(self):
|
|
options, sources = parse_command_line([
|
|
'-z', 'my_preimport',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(Options.pre_import, 'my_preimport')
|
|
self.check_default_global_options(['pre_import'])
|
|
self.check_default_options(options)
|
|
|
|
def test_convert_range(self):
|
|
options, sources = parse_command_line([
|
|
'--convert-range',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(Options.convert_range, True)
|
|
self.check_default_global_options(['convert_range'])
|
|
self.check_default_options(options)
|
|
|
|
def test_line_directives(self):
|
|
options, sources = parse_command_line([
|
|
'--line-directives',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.emit_linenums, True)
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['emit_linenums'])
|
|
|
|
def test_no_c_in_traceback(self):
|
|
options, sources = parse_command_line([
|
|
'--no-c-in-traceback',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.c_line_in_traceback, False)
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['c_line_in_traceback'])
|
|
|
|
def test_gdb(self):
|
|
options, sources = parse_command_line([
|
|
'--gdb',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.gdb_debug, True)
|
|
self.assertEqual(options.output_dir, os.curdir)
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['gdb_debug', 'output_dir'])
|
|
|
|
def test_3str(self):
|
|
options, sources = parse_command_line([
|
|
'--3str',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.language_level, '3str')
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['language_level'])
|
|
|
|
def test_capi_reexport_cincludes(self):
|
|
options, sources = parse_command_line([
|
|
'--capi-reexport-cincludes',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.capi_reexport_cincludes, True)
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['capi_reexport_cincludes'])
|
|
|
|
def test_fast_fail(self):
|
|
options, sources = parse_command_line([
|
|
'--fast-fail',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(Options.fast_fail, True)
|
|
self.check_default_global_options(['fast_fail'])
|
|
self.check_default_options(options)
|
|
|
|
def test_cimport_from_pyx(self):
|
|
options, sources = parse_command_line([
|
|
'--cimport-from-pyx',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(Options.cimport_from_pyx, True)
|
|
self.check_default_global_options(['cimport_from_pyx'])
|
|
self.check_default_options(options)
|
|
|
|
def test_Werror(self):
|
|
options, sources = parse_command_line([
|
|
'-Werror',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(Options.warning_errors, True)
|
|
self.check_default_global_options(['warning_errors'])
|
|
self.check_default_options(options)
|
|
|
|
def test_warning_errors(self):
|
|
options, sources = parse_command_line([
|
|
'--warning-errors',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(Options.warning_errors, True)
|
|
self.check_default_global_options(['warning_errors'])
|
|
self.check_default_options(options)
|
|
|
|
def test_Wextra(self):
|
|
options, sources = parse_command_line([
|
|
'-Wextra',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.compiler_directives, Options.extra_warnings)
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['compiler_directives'])
|
|
|
|
def test_warning_extra(self):
|
|
options, sources = parse_command_line([
|
|
'--warning-extra',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.compiler_directives, Options.extra_warnings)
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['compiler_directives'])
|
|
|
|
def test_old_style_globals(self):
|
|
options, sources = parse_command_line([
|
|
'--old-style-globals',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(Options.old_style_globals, True)
|
|
self.check_default_global_options(['old_style_globals'])
|
|
self.check_default_options(options)
|
|
|
|
def test_directive_multiple(self):
|
|
options, source = parse_command_line([
|
|
'-X', 'cdivision=True',
|
|
'-X', 'c_string_type=bytes',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.compiler_directives['cdivision'], True)
|
|
self.assertEqual(options.compiler_directives['c_string_type'], 'bytes')
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['compiler_directives'])
|
|
|
|
def test_directive_multiple_v2(self):
|
|
options, source = parse_command_line([
|
|
'-X', 'cdivision=True,c_string_type=bytes',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.compiler_directives['cdivision'], True)
|
|
self.assertEqual(options.compiler_directives['c_string_type'], 'bytes')
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['compiler_directives'])
|
|
|
|
def test_directive_value_yes(self):
|
|
options, source = parse_command_line([
|
|
'-X', 'cdivision=YeS',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.compiler_directives['cdivision'], True)
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['compiler_directives'])
|
|
|
|
def test_directive_value_no(self):
|
|
options, source = parse_command_line([
|
|
'-X', 'cdivision=no',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.compiler_directives['cdivision'], False)
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['compiler_directives'])
|
|
|
|
def test_directive_value_invalid(self):
|
|
self.assertRaises(ValueError, parse_command_line, [
|
|
'-X', 'cdivision=sadfasd',
|
|
'source.pyx'
|
|
])
|
|
|
|
def test_directive_key_invalid(self):
|
|
self.assertRaises(ValueError, parse_command_line, [
|
|
'-X', 'abracadabra',
|
|
'source.pyx'
|
|
])
|
|
|
|
def test_directive_no_value(self):
|
|
self.assertRaises(ValueError, parse_command_line, [
|
|
'-X', 'cdivision',
|
|
'source.pyx'
|
|
])
|
|
|
|
def test_compile_time_env_short(self):
|
|
options, source = parse_command_line([
|
|
'-E', 'MYSIZE=10',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.compile_time_env['MYSIZE'], 10)
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['compile_time_env'])
|
|
|
|
def test_compile_time_env_long(self):
|
|
options, source = parse_command_line([
|
|
'--compile-time-env', 'MYSIZE=10',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.compile_time_env['MYSIZE'], 10)
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['compile_time_env'])
|
|
|
|
def test_compile_time_env_multiple(self):
|
|
options, source = parse_command_line([
|
|
'-E', 'MYSIZE=10', '-E', 'ARRSIZE=11',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.compile_time_env['MYSIZE'], 10)
|
|
self.assertEqual(options.compile_time_env['ARRSIZE'], 11)
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['compile_time_env'])
|
|
|
|
def test_compile_time_env_multiple_v2(self):
|
|
options, source = parse_command_line([
|
|
'-E', 'MYSIZE=10,ARRSIZE=11',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.compile_time_env['MYSIZE'], 10)
|
|
self.assertEqual(options.compile_time_env['ARRSIZE'], 11)
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['compile_time_env'])
|
|
|
|
def test_option_first(self):
|
|
options, sources = parse_command_line(['-V', 'file.pyx'])
|
|
self.assertEqual(sources, ['file.pyx'])
|
|
|
|
def test_file_inbetween(self):
|
|
options, sources = parse_command_line(['-V', 'file.pyx', '-a'])
|
|
self.assertEqual(sources, ['file.pyx'])
|
|
|
|
def test_option_trailing(self):
|
|
options, sources = parse_command_line(['file.pyx', '-V'])
|
|
self.assertEqual(sources, ['file.pyx'])
|
|
|
|
def test_multiple_files(self):
|
|
options, sources = parse_command_line([
|
|
'file1.pyx', '-V',
|
|
'file2.pyx', '-a',
|
|
'file3.pyx'
|
|
])
|
|
self.assertEqual(sources, ['file1.pyx', 'file2.pyx', 'file3.pyx'])
|
|
|
|
def test_debug_flags(self):
|
|
options, sources = parse_command_line([
|
|
'--debug-disposal-code', '--debug-coercion',
|
|
'file3.pyx'
|
|
])
|
|
from Cython.Compiler import DebugFlags
|
|
for name in ['debug_disposal_code', 'debug_temp_alloc', 'debug_coercion']:
|
|
self.assertEqual(getattr(DebugFlags, name), name in ['debug_disposal_code', 'debug_coercion'])
|
|
setattr(DebugFlags, name, 0) # restore original value
|
|
|
|
def test_gdb_overwrites_gdb_outdir(self):
|
|
options, sources = parse_command_line([
|
|
'--gdb-outdir=my_dir', '--gdb',
|
|
'file3.pyx'
|
|
])
|
|
self.assertEqual(options.gdb_debug, True)
|
|
self.assertEqual(options.output_dir, os.curdir)
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['gdb_debug', 'output_dir'])
|
|
|
|
def test_gdb_first(self):
|
|
options, sources = parse_command_line([
|
|
'--gdb', '--gdb-outdir=my_dir',
|
|
'file3.pyx'
|
|
])
|
|
self.assertEqual(options.gdb_debug, True)
|
|
self.assertEqual(options.output_dir, 'my_dir')
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['gdb_debug', 'output_dir'])
|
|
|
|
def test_coverage_overwrites_annotation(self):
|
|
options, sources = parse_command_line([
|
|
'--annotate-fullc', '--annotate-coverage=my.xml',
|
|
'file3.pyx'
|
|
])
|
|
self.assertEqual(Options.annotate, True)
|
|
self.assertEqual(Options.annotate_coverage_xml, 'my.xml')
|
|
self.check_default_global_options(['annotate', 'annotate_coverage_xml'])
|
|
self.check_default_options(options)
|
|
|
|
def test_coverage_first(self):
|
|
options, sources = parse_command_line([
|
|
'--annotate-coverage=my.xml', '--annotate-fullc',
|
|
'file3.pyx'
|
|
])
|
|
self.assertEqual(Options.annotate, 'fullc')
|
|
self.assertEqual(Options.annotate_coverage_xml, 'my.xml')
|
|
self.check_default_global_options(['annotate', 'annotate_coverage_xml'])
|
|
self.check_default_options(options)
|
|
|
|
def test_annotate_first_fullc_second(self):
|
|
options, sources = parse_command_line([
|
|
'--annotate', '--annotate-fullc',
|
|
'file3.pyx'
|
|
])
|
|
self.assertEqual(Options.annotate, 'fullc')
|
|
self.check_default_global_options(['annotate'])
|
|
self.check_default_options(options)
|
|
|
|
def test_annotate_fullc_first(self):
|
|
options, sources = parse_command_line([
|
|
'--annotate-fullc', '--annotate',
|
|
'file3.pyx'
|
|
])
|
|
self.assertEqual(Options.annotate, 'default')
|
|
self.check_default_global_options(['annotate'])
|
|
self.check_default_options(options)
|
|
|
|
def test_warning_extra_dont_overwrite(self):
|
|
options, sources = parse_command_line([
|
|
'-X', 'cdivision=True',
|
|
'--warning-extra',
|
|
'-X', 'c_string_type=bytes',
|
|
'source.pyx'
|
|
])
|
|
self.assertTrue(len(options.compiler_directives), len(Options.extra_warnings) + 1)
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['compiler_directives'])
|
|
|
|
def test_module_name(self):
|
|
options, sources = parse_command_line([
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.module_name, None)
|
|
self.check_default_global_options()
|
|
self.check_default_options(options)
|
|
options, sources = parse_command_line([
|
|
'--module-name', 'foo.bar',
|
|
'source.pyx'
|
|
])
|
|
self.assertEqual(options.module_name, 'foo.bar')
|
|
self.check_default_global_options()
|
|
self.check_default_options(options, ['module_name'])
|
|
|
|
def test_errors(self):
|
|
def error(args, regex=None):
|
|
old_stderr = sys.stderr
|
|
stderr = sys.stderr = StringIO()
|
|
try:
|
|
self.assertRaises(SystemExit, parse_command_line, list(args))
|
|
finally:
|
|
sys.stderr = old_stderr
|
|
msg = stderr.getvalue()
|
|
err_msg = 'Message "{}"'.format(msg.strip())
|
|
self.assertTrue(msg.startswith('usage: '),
|
|
'%s does not start with "usage :"' % err_msg)
|
|
self.assertTrue(': error: ' in msg,
|
|
'%s does not contain ": error :"' % err_msg)
|
|
if regex:
|
|
self.assertTrue(re.search(regex, msg),
|
|
'%s does not match search "%s"' %
|
|
(err_msg, regex))
|
|
|
|
error(['-1'],
|
|
'unknown option -1')
|
|
error(['-I'],
|
|
'argument -I/--include-dir: expected one argument')
|
|
error(['--version=-a'],
|
|
"argument -V/--version: ignored explicit argument '-a'")
|
|
error(['--version=--annotate=true'],
|
|
"argument -V/--version: ignored explicit argument "
|
|
"'--annotate=true'")
|
|
error(['--working'],
|
|
"argument -w/--working: expected one argument")
|
|
error(['--verbose=1'],
|
|
"argument -v/--verbose: ignored explicit argument '1'")
|
|
error(['--cleanup'],
|
|
"argument --cleanup: expected one argument")
|
|
error(['--debug-disposal-code-wrong-name', 'file3.pyx'],
|
|
"unknown option --debug-disposal-code-wrong-name")
|
|
error(['--module-name', 'foo.pyx'],
|
|
"Need at least one source file")
|
|
error(['--module-name', 'foo.bar'],
|
|
"Need at least one source file")
|
|
error(['--module-name', 'foo.bar', 'foo.pyx', 'bar.pyx'],
|
|
"Only one source file allowed when using --module-name")
|
|
error(['--module-name', 'foo.bar', '--timestamps', 'foo.pyx'],
|
|
"Cannot use --module-name with --timestamps")
|