824 lines
22 KiB
Python
824 lines
22 KiB
Python
|
# Argument Clinic
|
||
|
# Copyright 2012-2013 by Larry Hastings.
|
||
|
# Licensed to the PSF under a contributor agreement.
|
||
|
|
||
|
from test import support, test_tools
|
||
|
from unittest import TestCase
|
||
|
import collections
|
||
|
import inspect
|
||
|
import os.path
|
||
|
import sys
|
||
|
import unittest
|
||
|
|
||
|
test_tools.skip_if_missing('clinic')
|
||
|
with test_tools.imports_under_tool('clinic'):
|
||
|
import clinic
|
||
|
from clinic import DSLParser
|
||
|
|
||
|
|
||
|
class FakeConverter:
|
||
|
def __init__(self, name, args):
|
||
|
self.name = name
|
||
|
self.args = args
|
||
|
|
||
|
|
||
|
class FakeConverterFactory:
|
||
|
def __init__(self, name):
|
||
|
self.name = name
|
||
|
|
||
|
def __call__(self, name, default, **kwargs):
|
||
|
return FakeConverter(self.name, kwargs)
|
||
|
|
||
|
|
||
|
class FakeConvertersDict:
|
||
|
def __init__(self):
|
||
|
self.used_converters = {}
|
||
|
|
||
|
def get(self, name, default):
|
||
|
return self.used_converters.setdefault(name, FakeConverterFactory(name))
|
||
|
|
||
|
c = clinic.Clinic(language='C', filename = "file")
|
||
|
|
||
|
class FakeClinic:
|
||
|
def __init__(self):
|
||
|
self.converters = FakeConvertersDict()
|
||
|
self.legacy_converters = FakeConvertersDict()
|
||
|
self.language = clinic.CLanguage(None)
|
||
|
self.filename = None
|
||
|
self.destination_buffers = {}
|
||
|
self.block_parser = clinic.BlockParser('', self.language)
|
||
|
self.modules = collections.OrderedDict()
|
||
|
self.classes = collections.OrderedDict()
|
||
|
clinic.clinic = self
|
||
|
self.name = "FakeClinic"
|
||
|
self.line_prefix = self.line_suffix = ''
|
||
|
self.destinations = {}
|
||
|
self.add_destination("block", "buffer")
|
||
|
self.add_destination("file", "buffer")
|
||
|
self.add_destination("suppress", "suppress")
|
||
|
d = self.destinations.get
|
||
|
self.field_destinations = collections.OrderedDict((
|
||
|
('docstring_prototype', d('suppress')),
|
||
|
('docstring_definition', d('block')),
|
||
|
('methoddef_define', d('block')),
|
||
|
('impl_prototype', d('block')),
|
||
|
('parser_prototype', d('suppress')),
|
||
|
('parser_definition', d('block')),
|
||
|
('impl_definition', d('block')),
|
||
|
))
|
||
|
|
||
|
def get_destination(self, name):
|
||
|
d = self.destinations.get(name)
|
||
|
if not d:
|
||
|
sys.exit("Destination does not exist: " + repr(name))
|
||
|
return d
|
||
|
|
||
|
def add_destination(self, name, type, *args):
|
||
|
if name in self.destinations:
|
||
|
sys.exit("Destination already exists: " + repr(name))
|
||
|
self.destinations[name] = clinic.Destination(name, type, self, *args)
|
||
|
|
||
|
def is_directive(self, name):
|
||
|
return name == "module"
|
||
|
|
||
|
def directive(self, name, args):
|
||
|
self.called_directives[name] = args
|
||
|
|
||
|
_module_and_class = clinic.Clinic._module_and_class
|
||
|
|
||
|
class ClinicWholeFileTest(TestCase):
|
||
|
def test_eol(self):
|
||
|
# regression test:
|
||
|
# clinic's block parser didn't recognize
|
||
|
# the "end line" for the block if it
|
||
|
# didn't end in "\n" (as in, the last)
|
||
|
# byte of the file was '/'.
|
||
|
# so it would spit out an end line for you.
|
||
|
# and since you really already had one,
|
||
|
# the last line of the block got corrupted.
|
||
|
c = clinic.Clinic(clinic.CLanguage(None), filename="file")
|
||
|
raw = "/*[clinic]\nfoo\n[clinic]*/"
|
||
|
cooked = c.parse(raw).splitlines()
|
||
|
end_line = cooked[2].rstrip()
|
||
|
# this test is redundant, it's just here explicitly to catch
|
||
|
# the regression test so we don't forget what it looked like
|
||
|
self.assertNotEqual(end_line, "[clinic]*/[clinic]*/")
|
||
|
self.assertEqual(end_line, "[clinic]*/")
|
||
|
|
||
|
|
||
|
|
||
|
class ClinicGroupPermuterTest(TestCase):
|
||
|
def _test(self, l, m, r, output):
|
||
|
computed = clinic.permute_optional_groups(l, m, r)
|
||
|
self.assertEqual(output, computed)
|
||
|
|
||
|
def test_range(self):
|
||
|
self._test([['start']], ['stop'], [['step']],
|
||
|
(
|
||
|
('stop',),
|
||
|
('start', 'stop',),
|
||
|
('start', 'stop', 'step',),
|
||
|
))
|
||
|
|
||
|
def test_add_window(self):
|
||
|
self._test([['x', 'y']], ['ch'], [['attr']],
|
||
|
(
|
||
|
('ch',),
|
||
|
('ch', 'attr'),
|
||
|
('x', 'y', 'ch',),
|
||
|
('x', 'y', 'ch', 'attr'),
|
||
|
))
|
||
|
|
||
|
def test_ludicrous(self):
|
||
|
self._test([['a1', 'a2', 'a3'], ['b1', 'b2']], ['c1'], [['d1', 'd2'], ['e1', 'e2', 'e3']],
|
||
|
(
|
||
|
('c1',),
|
||
|
('b1', 'b2', 'c1'),
|
||
|
('b1', 'b2', 'c1', 'd1', 'd2'),
|
||
|
('a1', 'a2', 'a3', 'b1', 'b2', 'c1'),
|
||
|
('a1', 'a2', 'a3', 'b1', 'b2', 'c1', 'd1', 'd2'),
|
||
|
('a1', 'a2', 'a3', 'b1', 'b2', 'c1', 'd1', 'd2', 'e1', 'e2', 'e3'),
|
||
|
))
|
||
|
|
||
|
def test_right_only(self):
|
||
|
self._test([], [], [['a'],['b'],['c']],
|
||
|
(
|
||
|
(),
|
||
|
('a',),
|
||
|
('a', 'b'),
|
||
|
('a', 'b', 'c')
|
||
|
))
|
||
|
|
||
|
def test_have_left_options_but_required_is_empty(self):
|
||
|
def fn():
|
||
|
clinic.permute_optional_groups(['a'], [], [])
|
||
|
self.assertRaises(AssertionError, fn)
|
||
|
|
||
|
|
||
|
class ClinicLinearFormatTest(TestCase):
|
||
|
def _test(self, input, output, **kwargs):
|
||
|
computed = clinic.linear_format(input, **kwargs)
|
||
|
self.assertEqual(output, computed)
|
||
|
|
||
|
def test_empty_strings(self):
|
||
|
self._test('', '')
|
||
|
|
||
|
def test_solo_newline(self):
|
||
|
self._test('\n', '\n')
|
||
|
|
||
|
def test_no_substitution(self):
|
||
|
self._test("""
|
||
|
abc
|
||
|
""", """
|
||
|
abc
|
||
|
""")
|
||
|
|
||
|
def test_empty_substitution(self):
|
||
|
self._test("""
|
||
|
abc
|
||
|
{name}
|
||
|
def
|
||
|
""", """
|
||
|
abc
|
||
|
def
|
||
|
""", name='')
|
||
|
|
||
|
def test_single_line_substitution(self):
|
||
|
self._test("""
|
||
|
abc
|
||
|
{name}
|
||
|
def
|
||
|
""", """
|
||
|
abc
|
||
|
GARGLE
|
||
|
def
|
||
|
""", name='GARGLE')
|
||
|
|
||
|
def test_multiline_substitution(self):
|
||
|
self._test("""
|
||
|
abc
|
||
|
{name}
|
||
|
def
|
||
|
""", """
|
||
|
abc
|
||
|
bingle
|
||
|
bungle
|
||
|
|
||
|
def
|
||
|
""", name='bingle\nbungle\n')
|
||
|
|
||
|
class InertParser:
|
||
|
def __init__(self, clinic):
|
||
|
pass
|
||
|
|
||
|
def parse(self, block):
|
||
|
pass
|
||
|
|
||
|
class CopyParser:
|
||
|
def __init__(self, clinic):
|
||
|
pass
|
||
|
|
||
|
def parse(self, block):
|
||
|
block.output = block.input
|
||
|
|
||
|
|
||
|
class ClinicBlockParserTest(TestCase):
|
||
|
def _test(self, input, output):
|
||
|
language = clinic.CLanguage(None)
|
||
|
|
||
|
blocks = list(clinic.BlockParser(input, language))
|
||
|
writer = clinic.BlockPrinter(language)
|
||
|
for block in blocks:
|
||
|
writer.print_block(block)
|
||
|
output = writer.f.getvalue()
|
||
|
assert output == input, "output != input!\n\noutput " + repr(output) + "\n\n input " + repr(input)
|
||
|
|
||
|
def round_trip(self, input):
|
||
|
return self._test(input, input)
|
||
|
|
||
|
def test_round_trip_1(self):
|
||
|
self.round_trip("""
|
||
|
verbatim text here
|
||
|
lah dee dah
|
||
|
""")
|
||
|
def test_round_trip_2(self):
|
||
|
self.round_trip("""
|
||
|
verbatim text here
|
||
|
lah dee dah
|
||
|
/*[inert]
|
||
|
abc
|
||
|
[inert]*/
|
||
|
def
|
||
|
/*[inert checksum: 7b18d017f89f61cf17d47f92749ea6930a3f1deb]*/
|
||
|
xyz
|
||
|
""")
|
||
|
|
||
|
def _test_clinic(self, input, output):
|
||
|
language = clinic.CLanguage(None)
|
||
|
c = clinic.Clinic(language, filename="file")
|
||
|
c.parsers['inert'] = InertParser(c)
|
||
|
c.parsers['copy'] = CopyParser(c)
|
||
|
computed = c.parse(input)
|
||
|
self.assertEqual(output, computed)
|
||
|
|
||
|
def test_clinic_1(self):
|
||
|
self._test_clinic("""
|
||
|
verbatim text here
|
||
|
lah dee dah
|
||
|
/*[copy input]
|
||
|
def
|
||
|
[copy start generated code]*/
|
||
|
abc
|
||
|
/*[copy end generated code: output=03cfd743661f0797 input=7b18d017f89f61cf]*/
|
||
|
xyz
|
||
|
""", """
|
||
|
verbatim text here
|
||
|
lah dee dah
|
||
|
/*[copy input]
|
||
|
def
|
||
|
[copy start generated code]*/
|
||
|
def
|
||
|
/*[copy end generated code: output=7b18d017f89f61cf input=7b18d017f89f61cf]*/
|
||
|
xyz
|
||
|
""")
|
||
|
|
||
|
|
||
|
class ClinicParserTest(TestCase):
|
||
|
def test_trivial(self):
|
||
|
parser = DSLParser(FakeClinic())
|
||
|
block = clinic.Block("module os\nos.access")
|
||
|
parser.parse(block)
|
||
|
module, function = block.signatures
|
||
|
self.assertEqual("access", function.name)
|
||
|
self.assertEqual("os", module.name)
|
||
|
|
||
|
def test_ignore_line(self):
|
||
|
block = self.parse("#\nmodule os\nos.access")
|
||
|
module, function = block.signatures
|
||
|
self.assertEqual("access", function.name)
|
||
|
self.assertEqual("os", module.name)
|
||
|
|
||
|
def test_param(self):
|
||
|
function = self.parse_function("module os\nos.access\n path: int")
|
||
|
self.assertEqual("access", function.name)
|
||
|
self.assertEqual(2, len(function.parameters))
|
||
|
p = function.parameters['path']
|
||
|
self.assertEqual('path', p.name)
|
||
|
self.assertIsInstance(p.converter, clinic.int_converter)
|
||
|
|
||
|
def test_param_default(self):
|
||
|
function = self.parse_function("module os\nos.access\n follow_symlinks: bool = True")
|
||
|
p = function.parameters['follow_symlinks']
|
||
|
self.assertEqual(True, p.default)
|
||
|
|
||
|
def test_param_with_continuations(self):
|
||
|
function = self.parse_function("module os\nos.access\n follow_symlinks: \\\n bool \\\n =\\\n True")
|
||
|
p = function.parameters['follow_symlinks']
|
||
|
self.assertEqual(True, p.default)
|
||
|
|
||
|
def test_param_default_expression(self):
|
||
|
function = self.parse_function("module os\nos.access\n follow_symlinks: int(c_default='MAXSIZE') = sys.maxsize")
|
||
|
p = function.parameters['follow_symlinks']
|
||
|
self.assertEqual(sys.maxsize, p.default)
|
||
|
self.assertEqual("MAXSIZE", p.converter.c_default)
|
||
|
|
||
|
s = self.parse_function_should_fail("module os\nos.access\n follow_symlinks: int = sys.maxsize")
|
||
|
self.assertEqual(s, "Error on line 0:\nWhen you specify a named constant ('sys.maxsize') as your default value,\nyou MUST specify a valid c_default.\n")
|
||
|
|
||
|
def test_param_no_docstring(self):
|
||
|
function = self.parse_function("""
|
||
|
module os
|
||
|
os.access
|
||
|
follow_symlinks: bool = True
|
||
|
something_else: str = ''""")
|
||
|
p = function.parameters['follow_symlinks']
|
||
|
self.assertEqual(3, len(function.parameters))
|
||
|
self.assertIsInstance(function.parameters['something_else'].converter, clinic.str_converter)
|
||
|
|
||
|
def test_param_default_parameters_out_of_order(self):
|
||
|
s = self.parse_function_should_fail("""
|
||
|
module os
|
||
|
os.access
|
||
|
follow_symlinks: bool = True
|
||
|
something_else: str""")
|
||
|
self.assertEqual(s, """Error on line 0:
|
||
|
Can't have a parameter without a default ('something_else')
|
||
|
after a parameter with a default!
|
||
|
""")
|
||
|
|
||
|
def disabled_test_converter_arguments(self):
|
||
|
function = self.parse_function("module os\nos.access\n path: path_t(allow_fd=1)")
|
||
|
p = function.parameters['path']
|
||
|
self.assertEqual(1, p.converter.args['allow_fd'])
|
||
|
|
||
|
def test_function_docstring(self):
|
||
|
function = self.parse_function("""
|
||
|
module os
|
||
|
os.stat as os_stat_fn
|
||
|
|
||
|
path: str
|
||
|
Path to be examined
|
||
|
|
||
|
Perform a stat system call on the given path.""")
|
||
|
self.assertEqual("""
|
||
|
stat($module, /, path)
|
||
|
--
|
||
|
|
||
|
Perform a stat system call on the given path.
|
||
|
|
||
|
path
|
||
|
Path to be examined
|
||
|
""".strip(), function.docstring)
|
||
|
|
||
|
def test_explicit_parameters_in_docstring(self):
|
||
|
function = self.parse_function("""
|
||
|
module foo
|
||
|
foo.bar
|
||
|
x: int
|
||
|
Documentation for x.
|
||
|
y: int
|
||
|
|
||
|
This is the documentation for foo.
|
||
|
|
||
|
Okay, we're done here.
|
||
|
""")
|
||
|
self.assertEqual("""
|
||
|
bar($module, /, x, y)
|
||
|
--
|
||
|
|
||
|
This is the documentation for foo.
|
||
|
|
||
|
x
|
||
|
Documentation for x.
|
||
|
|
||
|
Okay, we're done here.
|
||
|
""".strip(), function.docstring)
|
||
|
|
||
|
def test_parser_regression_special_character_in_parameter_column_of_docstring_first_line(self):
|
||
|
function = self.parse_function("""
|
||
|
module os
|
||
|
os.stat
|
||
|
path: str
|
||
|
This/used to break Clinic!
|
||
|
""")
|
||
|
self.assertEqual("stat($module, /, path)\n--\n\nThis/used to break Clinic!", function.docstring)
|
||
|
|
||
|
def test_c_name(self):
|
||
|
function = self.parse_function("module os\nos.stat as os_stat_fn")
|
||
|
self.assertEqual("os_stat_fn", function.c_basename)
|
||
|
|
||
|
def test_return_converter(self):
|
||
|
function = self.parse_function("module os\nos.stat -> int")
|
||
|
self.assertIsInstance(function.return_converter, clinic.int_return_converter)
|
||
|
|
||
|
def test_star(self):
|
||
|
function = self.parse_function("module os\nos.access\n *\n follow_symlinks: bool = True")
|
||
|
p = function.parameters['follow_symlinks']
|
||
|
self.assertEqual(inspect.Parameter.KEYWORD_ONLY, p.kind)
|
||
|
self.assertEqual(0, p.group)
|
||
|
|
||
|
def test_group(self):
|
||
|
function = self.parse_function("module window\nwindow.border\n [\n ls : int\n ]\n /\n")
|
||
|
p = function.parameters['ls']
|
||
|
self.assertEqual(1, p.group)
|
||
|
|
||
|
def test_left_group(self):
|
||
|
function = self.parse_function("""
|
||
|
module curses
|
||
|
curses.addch
|
||
|
[
|
||
|
y: int
|
||
|
Y-coordinate.
|
||
|
x: int
|
||
|
X-coordinate.
|
||
|
]
|
||
|
ch: char
|
||
|
Character to add.
|
||
|
[
|
||
|
attr: long
|
||
|
Attributes for the character.
|
||
|
]
|
||
|
/
|
||
|
""")
|
||
|
for name, group in (
|
||
|
('y', -1), ('x', -1),
|
||
|
('ch', 0),
|
||
|
('attr', 1),
|
||
|
):
|
||
|
p = function.parameters[name]
|
||
|
self.assertEqual(p.group, group)
|
||
|
self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY)
|
||
|
self.assertEqual(function.docstring.strip(), """
|
||
|
addch([y, x,] ch, [attr])
|
||
|
|
||
|
|
||
|
y
|
||
|
Y-coordinate.
|
||
|
x
|
||
|
X-coordinate.
|
||
|
ch
|
||
|
Character to add.
|
||
|
attr
|
||
|
Attributes for the character.
|
||
|
""".strip())
|
||
|
|
||
|
def test_nested_groups(self):
|
||
|
function = self.parse_function("""
|
||
|
module curses
|
||
|
curses.imaginary
|
||
|
[
|
||
|
[
|
||
|
y1: int
|
||
|
Y-coordinate.
|
||
|
y2: int
|
||
|
Y-coordinate.
|
||
|
]
|
||
|
x1: int
|
||
|
X-coordinate.
|
||
|
x2: int
|
||
|
X-coordinate.
|
||
|
]
|
||
|
ch: char
|
||
|
Character to add.
|
||
|
[
|
||
|
attr1: long
|
||
|
Attributes for the character.
|
||
|
attr2: long
|
||
|
Attributes for the character.
|
||
|
attr3: long
|
||
|
Attributes for the character.
|
||
|
[
|
||
|
attr4: long
|
||
|
Attributes for the character.
|
||
|
attr5: long
|
||
|
Attributes for the character.
|
||
|
attr6: long
|
||
|
Attributes for the character.
|
||
|
]
|
||
|
]
|
||
|
/
|
||
|
""")
|
||
|
for name, group in (
|
||
|
('y1', -2), ('y2', -2),
|
||
|
('x1', -1), ('x2', -1),
|
||
|
('ch', 0),
|
||
|
('attr1', 1), ('attr2', 1), ('attr3', 1),
|
||
|
('attr4', 2), ('attr5', 2), ('attr6', 2),
|
||
|
):
|
||
|
p = function.parameters[name]
|
||
|
self.assertEqual(p.group, group)
|
||
|
self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY)
|
||
|
|
||
|
self.assertEqual(function.docstring.strip(), """
|
||
|
imaginary([[y1, y2,] x1, x2,] ch, [attr1, attr2, attr3, [attr4, attr5,
|
||
|
attr6]])
|
||
|
|
||
|
|
||
|
y1
|
||
|
Y-coordinate.
|
||
|
y2
|
||
|
Y-coordinate.
|
||
|
x1
|
||
|
X-coordinate.
|
||
|
x2
|
||
|
X-coordinate.
|
||
|
ch
|
||
|
Character to add.
|
||
|
attr1
|
||
|
Attributes for the character.
|
||
|
attr2
|
||
|
Attributes for the character.
|
||
|
attr3
|
||
|
Attributes for the character.
|
||
|
attr4
|
||
|
Attributes for the character.
|
||
|
attr5
|
||
|
Attributes for the character.
|
||
|
attr6
|
||
|
Attributes for the character.
|
||
|
""".strip())
|
||
|
|
||
|
def parse_function_should_fail(self, s):
|
||
|
with support.captured_stdout() as stdout:
|
||
|
with self.assertRaises(SystemExit):
|
||
|
self.parse_function(s)
|
||
|
return stdout.getvalue()
|
||
|
|
||
|
def test_disallowed_grouping__two_top_groups_on_left(self):
|
||
|
s = self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.two_top_groups_on_left
|
||
|
[
|
||
|
group1 : int
|
||
|
]
|
||
|
[
|
||
|
group2 : int
|
||
|
]
|
||
|
param: int
|
||
|
""")
|
||
|
self.assertEqual(s,
|
||
|
('Error on line 0:\n'
|
||
|
'Function two_top_groups_on_left has an unsupported group configuration. (Unexpected state 2.b)\n'))
|
||
|
|
||
|
def test_disallowed_grouping__two_top_groups_on_right(self):
|
||
|
self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.two_top_groups_on_right
|
||
|
param: int
|
||
|
[
|
||
|
group1 : int
|
||
|
]
|
||
|
[
|
||
|
group2 : int
|
||
|
]
|
||
|
""")
|
||
|
|
||
|
def test_disallowed_grouping__parameter_after_group_on_right(self):
|
||
|
self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.parameter_after_group_on_right
|
||
|
param: int
|
||
|
[
|
||
|
[
|
||
|
group1 : int
|
||
|
]
|
||
|
group2 : int
|
||
|
]
|
||
|
""")
|
||
|
|
||
|
def test_disallowed_grouping__group_after_parameter_on_left(self):
|
||
|
self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.group_after_parameter_on_left
|
||
|
[
|
||
|
group2 : int
|
||
|
[
|
||
|
group1 : int
|
||
|
]
|
||
|
]
|
||
|
param: int
|
||
|
""")
|
||
|
|
||
|
def test_disallowed_grouping__empty_group_on_left(self):
|
||
|
self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.empty_group
|
||
|
[
|
||
|
[
|
||
|
]
|
||
|
group2 : int
|
||
|
]
|
||
|
param: int
|
||
|
""")
|
||
|
|
||
|
def test_disallowed_grouping__empty_group_on_right(self):
|
||
|
self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.empty_group
|
||
|
param: int
|
||
|
[
|
||
|
[
|
||
|
]
|
||
|
group2 : int
|
||
|
]
|
||
|
""")
|
||
|
|
||
|
def test_no_parameters(self):
|
||
|
function = self.parse_function("""
|
||
|
module foo
|
||
|
foo.bar
|
||
|
|
||
|
Docstring
|
||
|
|
||
|
""")
|
||
|
self.assertEqual("bar($module, /)\n--\n\nDocstring", function.docstring)
|
||
|
self.assertEqual(1, len(function.parameters)) # self!
|
||
|
|
||
|
def test_init_with_no_parameters(self):
|
||
|
function = self.parse_function("""
|
||
|
module foo
|
||
|
class foo.Bar "unused" "notneeded"
|
||
|
foo.Bar.__init__
|
||
|
|
||
|
Docstring
|
||
|
|
||
|
""", signatures_in_block=3, function_index=2)
|
||
|
# self is not in the signature
|
||
|
self.assertEqual("Bar()\n--\n\nDocstring", function.docstring)
|
||
|
# but it *is* a parameter
|
||
|
self.assertEqual(1, len(function.parameters))
|
||
|
|
||
|
def test_illegal_module_line(self):
|
||
|
self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.bar => int
|
||
|
/
|
||
|
""")
|
||
|
|
||
|
def test_illegal_c_basename(self):
|
||
|
self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.bar as 935
|
||
|
/
|
||
|
""")
|
||
|
|
||
|
def test_single_star(self):
|
||
|
self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.bar
|
||
|
*
|
||
|
*
|
||
|
""")
|
||
|
|
||
|
def test_parameters_required_after_star_without_initial_parameters_or_docstring(self):
|
||
|
self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.bar
|
||
|
*
|
||
|
""")
|
||
|
|
||
|
def test_parameters_required_after_star_without_initial_parameters_with_docstring(self):
|
||
|
self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.bar
|
||
|
*
|
||
|
Docstring here.
|
||
|
""")
|
||
|
|
||
|
def test_parameters_required_after_star_with_initial_parameters_without_docstring(self):
|
||
|
self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.bar
|
||
|
this: int
|
||
|
*
|
||
|
""")
|
||
|
|
||
|
def test_parameters_required_after_star_with_initial_parameters_and_docstring(self):
|
||
|
self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.bar
|
||
|
this: int
|
||
|
*
|
||
|
Docstring.
|
||
|
""")
|
||
|
|
||
|
def test_single_slash(self):
|
||
|
self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.bar
|
||
|
/
|
||
|
/
|
||
|
""")
|
||
|
|
||
|
def test_mix_star_and_slash(self):
|
||
|
self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.bar
|
||
|
x: int
|
||
|
y: int
|
||
|
*
|
||
|
z: int
|
||
|
/
|
||
|
""")
|
||
|
|
||
|
def test_parameters_not_permitted_after_slash_for_now(self):
|
||
|
self.parse_function_should_fail("""
|
||
|
module foo
|
||
|
foo.bar
|
||
|
/
|
||
|
x: int
|
||
|
""")
|
||
|
|
||
|
def test_function_not_at_column_0(self):
|
||
|
function = self.parse_function("""
|
||
|
module foo
|
||
|
foo.bar
|
||
|
x: int
|
||
|
Nested docstring here, goeth.
|
||
|
*
|
||
|
y: str
|
||
|
Not at column 0!
|
||
|
""")
|
||
|
self.assertEqual("""
|
||
|
bar($module, /, x, *, y)
|
||
|
--
|
||
|
|
||
|
Not at column 0!
|
||
|
|
||
|
x
|
||
|
Nested docstring here, goeth.
|
||
|
""".strip(), function.docstring)
|
||
|
|
||
|
def test_directive(self):
|
||
|
c = FakeClinic()
|
||
|
parser = DSLParser(c)
|
||
|
parser.flag = False
|
||
|
parser.directives['setflag'] = lambda : setattr(parser, 'flag', True)
|
||
|
block = clinic.Block("setflag")
|
||
|
parser.parse(block)
|
||
|
self.assertTrue(parser.flag)
|
||
|
|
||
|
def test_legacy_converters(self):
|
||
|
block = self.parse('module os\nos.access\n path: "s"')
|
||
|
module, function = block.signatures
|
||
|
self.assertIsInstance((function.parameters['path']).converter, clinic.str_converter)
|
||
|
|
||
|
def parse(self, text):
|
||
|
c = FakeClinic()
|
||
|
parser = DSLParser(c)
|
||
|
block = clinic.Block(text)
|
||
|
parser.parse(block)
|
||
|
return block
|
||
|
|
||
|
def parse_function(self, text, signatures_in_block=2, function_index=1):
|
||
|
block = self.parse(text)
|
||
|
s = block.signatures
|
||
|
self.assertEqual(len(s), signatures_in_block)
|
||
|
assert isinstance(s[0], clinic.Module)
|
||
|
assert isinstance(s[function_index], clinic.Function)
|
||
|
return s[function_index]
|
||
|
|
||
|
def test_scaffolding(self):
|
||
|
# test repr on special values
|
||
|
self.assertEqual(repr(clinic.unspecified), '<Unspecified>')
|
||
|
self.assertEqual(repr(clinic.NULL), '<Null>')
|
||
|
|
||
|
# test that fail fails
|
||
|
with support.captured_stdout() as stdout:
|
||
|
with self.assertRaises(SystemExit):
|
||
|
clinic.fail('The igloos are melting!', filename='clown.txt', line_number=69)
|
||
|
self.assertEqual(stdout.getvalue(), 'Error in file "clown.txt" on line 69:\nThe igloos are melting!\n')
|
||
|
|
||
|
|
||
|
class ClinicExternalTest(TestCase):
|
||
|
maxDiff = None
|
||
|
|
||
|
def test_external(self):
|
||
|
# bpo-42398: Test that the destination file is left unchanged if the
|
||
|
# content does not change. Moreover, check also that the file
|
||
|
# modification time does not change in this case.
|
||
|
source = support.findfile('clinic.test')
|
||
|
with open(source, 'r', encoding='utf-8') as f:
|
||
|
orig_contents = f.read()
|
||
|
|
||
|
with support.temp_dir() as tmp_dir:
|
||
|
testfile = os.path.join(tmp_dir, 'clinic.test.c')
|
||
|
with open(testfile, 'w', encoding='utf-8') as f:
|
||
|
f.write(orig_contents)
|
||
|
old_mtime_ns = os.stat(testfile).st_mtime_ns
|
||
|
|
||
|
clinic.parse_file(testfile)
|
||
|
|
||
|
with open(testfile, 'r', encoding='utf-8') as f:
|
||
|
new_contents = f.read()
|
||
|
new_mtime_ns = os.stat(testfile).st_mtime_ns
|
||
|
|
||
|
self.assertEqual(new_contents, orig_contents)
|
||
|
# Don't change the file modification time
|
||
|
# if the content does not change
|
||
|
self.assertEqual(new_mtime_ns, old_mtime_ns)
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
unittest.main()
|