blob: 4aa9691a4829d19ea6681c0c6c31bec0250761ee [file] [log] [blame]
Larry Hastings31826802013-10-19 00:09:25 -07001# Argument Clinic
2# Copyright 2012-2013 by Larry Hastings.
3# Licensed to the PSF under a contributor agreement.
Larry Hastings31826802013-10-19 00:09:25 -07004
Eric Snowee536b22019-09-11 19:49:45 +01005from test import support, test_tools
Hai Shi46605972020-08-04 00:49:18 +08006from test.support import os_helper
Victor Stinner65fc98e2018-09-03 23:17:20 +02007from unittest import TestCase
Larry Hastings31826802013-10-19 00:09:25 -07008import collections
9import inspect
Victor Stinner65fc98e2018-09-03 23:17:20 +020010import os.path
Larry Hastings1abd7082014-01-16 14:15:03 -080011import sys
Larry Hastings31826802013-10-19 00:09:25 -070012import unittest
Victor Stinner65fc98e2018-09-03 23:17:20 +020013
Eric Snowee536b22019-09-11 19:49:45 +010014test_tools.skip_if_missing('clinic')
15with test_tools.imports_under_tool('clinic'):
Victor Stinner65fc98e2018-09-03 23:17:20 +020016 import clinic
17 from clinic import DSLParser
Larry Hastings31826802013-10-19 00:09:25 -070018
Larry Hastings7726ac92014-01-31 22:03:12 -080019
Larry Hastings31826802013-10-19 00:09:25 -070020class FakeConverter:
21 def __init__(self, name, args):
22 self.name = name
23 self.args = args
24
25
26class FakeConverterFactory:
27 def __init__(self, name):
28 self.name = name
29
30 def __call__(self, name, default, **kwargs):
31 return FakeConverter(self.name, kwargs)
32
33
34class FakeConvertersDict:
35 def __init__(self):
36 self.used_converters = {}
37
38 def get(self, name, default):
39 return self.used_converters.setdefault(name, FakeConverterFactory(name))
40
Victor Stinner65fc98e2018-09-03 23:17:20 +020041c = clinic.Clinic(language='C', filename = "file")
Larry Hastingsbebf7352014-01-17 17:47:17 -080042
Larry Hastings31826802013-10-19 00:09:25 -070043class FakeClinic:
44 def __init__(self):
45 self.converters = FakeConvertersDict()
46 self.legacy_converters = FakeConvertersDict()
Larry Hastings7726ac92014-01-31 22:03:12 -080047 self.language = clinic.CLanguage(None)
Larry Hastings31826802013-10-19 00:09:25 -070048 self.filename = None
Victor Stinner65fc98e2018-09-03 23:17:20 +020049 self.destination_buffers = {}
Larry Hastings31826802013-10-19 00:09:25 -070050 self.block_parser = clinic.BlockParser('', self.language)
51 self.modules = collections.OrderedDict()
Larry Hastings7726ac92014-01-31 22:03:12 -080052 self.classes = collections.OrderedDict()
Larry Hastings31826802013-10-19 00:09:25 -070053 clinic.clinic = self
54 self.name = "FakeClinic"
Larry Hastingsbebf7352014-01-17 17:47:17 -080055 self.line_prefix = self.line_suffix = ''
56 self.destinations = {}
57 self.add_destination("block", "buffer")
58 self.add_destination("file", "buffer")
59 self.add_destination("suppress", "suppress")
60 d = self.destinations.get
61 self.field_destinations = collections.OrderedDict((
62 ('docstring_prototype', d('suppress')),
63 ('docstring_definition', d('block')),
64 ('methoddef_define', d('block')),
65 ('impl_prototype', d('block')),
66 ('parser_prototype', d('suppress')),
67 ('parser_definition', d('block')),
68 ('impl_definition', d('block')),
69 ))
70
71 def get_destination(self, name):
72 d = self.destinations.get(name)
73 if not d:
74 sys.exit("Destination does not exist: " + repr(name))
75 return d
76
77 def add_destination(self, name, type, *args):
78 if name in self.destinations:
79 sys.exit("Destination already exists: " + repr(name))
80 self.destinations[name] = clinic.Destination(name, type, self, *args)
Larry Hastings31826802013-10-19 00:09:25 -070081
82 def is_directive(self, name):
83 return name == "module"
84
85 def directive(self, name, args):
86 self.called_directives[name] = args
87
88 _module_and_class = clinic.Clinic._module_and_class
89
Larry Hastings90261132014-01-07 12:21:08 -080090class ClinicWholeFileTest(TestCase):
91 def test_eol(self):
92 # regression test:
93 # clinic's block parser didn't recognize
94 # the "end line" for the block if it
95 # didn't end in "\n" (as in, the last)
96 # byte of the file was '/'.
Martin Pantereb995702016-07-28 01:11:04 +000097 # so it would spit out an end line for you.
Larry Hastings90261132014-01-07 12:21:08 -080098 # and since you really already had one,
99 # the last line of the block got corrupted.
Victor Stinner65fc98e2018-09-03 23:17:20 +0200100 c = clinic.Clinic(clinic.CLanguage(None), filename="file")
Larry Hastings90261132014-01-07 12:21:08 -0800101 raw = "/*[clinic]\nfoo\n[clinic]*/"
102 cooked = c.parse(raw).splitlines()
103 end_line = cooked[2].rstrip()
104 # this test is redundant, it's just here explicitly to catch
105 # the regression test so we don't forget what it looked like
106 self.assertNotEqual(end_line, "[clinic]*/[clinic]*/")
107 self.assertEqual(end_line, "[clinic]*/")
108
Larry Hastings31826802013-10-19 00:09:25 -0700109
110
111class ClinicGroupPermuterTest(TestCase):
112 def _test(self, l, m, r, output):
113 computed = clinic.permute_optional_groups(l, m, r)
114 self.assertEqual(output, computed)
115
116 def test_range(self):
117 self._test([['start']], ['stop'], [['step']],
118 (
119 ('stop',),
120 ('start', 'stop',),
121 ('start', 'stop', 'step',),
122 ))
123
124 def test_add_window(self):
125 self._test([['x', 'y']], ['ch'], [['attr']],
126 (
127 ('ch',),
128 ('ch', 'attr'),
129 ('x', 'y', 'ch',),
130 ('x', 'y', 'ch', 'attr'),
131 ))
132
133 def test_ludicrous(self):
134 self._test([['a1', 'a2', 'a3'], ['b1', 'b2']], ['c1'], [['d1', 'd2'], ['e1', 'e2', 'e3']],
135 (
136 ('c1',),
137 ('b1', 'b2', 'c1'),
138 ('b1', 'b2', 'c1', 'd1', 'd2'),
139 ('a1', 'a2', 'a3', 'b1', 'b2', 'c1'),
140 ('a1', 'a2', 'a3', 'b1', 'b2', 'c1', 'd1', 'd2'),
141 ('a1', 'a2', 'a3', 'b1', 'b2', 'c1', 'd1', 'd2', 'e1', 'e2', 'e3'),
142 ))
143
144 def test_right_only(self):
145 self._test([], [], [['a'],['b'],['c']],
146 (
147 (),
148 ('a',),
149 ('a', 'b'),
150 ('a', 'b', 'c')
151 ))
152
153 def test_have_left_options_but_required_is_empty(self):
154 def fn():
155 clinic.permute_optional_groups(['a'], [], [])
156 self.assertRaises(AssertionError, fn)
157
158
159class ClinicLinearFormatTest(TestCase):
160 def _test(self, input, output, **kwargs):
161 computed = clinic.linear_format(input, **kwargs)
162 self.assertEqual(output, computed)
163
164 def test_empty_strings(self):
165 self._test('', '')
166
167 def test_solo_newline(self):
168 self._test('\n', '\n')
169
170 def test_no_substitution(self):
171 self._test("""
172 abc
173 """, """
174 abc
175 """)
176
177 def test_empty_substitution(self):
178 self._test("""
179 abc
180 {name}
181 def
182 """, """
183 abc
184 def
185 """, name='')
186
187 def test_single_line_substitution(self):
188 self._test("""
189 abc
190 {name}
191 def
192 """, """
193 abc
194 GARGLE
195 def
196 """, name='GARGLE')
197
198 def test_multiline_substitution(self):
199 self._test("""
200 abc
201 {name}
202 def
203 """, """
204 abc
205 bingle
206 bungle
207
208 def
209 """, name='bingle\nbungle\n')
210
211class InertParser:
212 def __init__(self, clinic):
213 pass
214
215 def parse(self, block):
216 pass
217
218class CopyParser:
219 def __init__(self, clinic):
220 pass
221
222 def parse(self, block):
223 block.output = block.input
224
225
226class ClinicBlockParserTest(TestCase):
227 def _test(self, input, output):
Larry Hastings7726ac92014-01-31 22:03:12 -0800228 language = clinic.CLanguage(None)
Larry Hastings31826802013-10-19 00:09:25 -0700229
230 blocks = list(clinic.BlockParser(input, language))
231 writer = clinic.BlockPrinter(language)
232 for block in blocks:
233 writer.print_block(block)
234 output = writer.f.getvalue()
235 assert output == input, "output != input!\n\noutput " + repr(output) + "\n\n input " + repr(input)
236
237 def round_trip(self, input):
238 return self._test(input, input)
239
240 def test_round_trip_1(self):
241 self.round_trip("""
242 verbatim text here
243 lah dee dah
244""")
245 def test_round_trip_2(self):
246 self.round_trip("""
247 verbatim text here
248 lah dee dah
249/*[inert]
250abc
251[inert]*/
252def
253/*[inert checksum: 7b18d017f89f61cf17d47f92749ea6930a3f1deb]*/
254xyz
255""")
256
257 def _test_clinic(self, input, output):
Larry Hastings7726ac92014-01-31 22:03:12 -0800258 language = clinic.CLanguage(None)
Victor Stinner65fc98e2018-09-03 23:17:20 +0200259 c = clinic.Clinic(language, filename="file")
Larry Hastings31826802013-10-19 00:09:25 -0700260 c.parsers['inert'] = InertParser(c)
261 c.parsers['copy'] = CopyParser(c)
262 computed = c.parse(input)
263 self.assertEqual(output, computed)
264
265 def test_clinic_1(self):
266 self._test_clinic("""
267 verbatim text here
268 lah dee dah
Larry Hastings2a727912014-01-16 11:32:01 -0800269/*[copy input]
Larry Hastings31826802013-10-19 00:09:25 -0700270def
Larry Hastings2a727912014-01-16 11:32:01 -0800271[copy start generated code]*/
Larry Hastings31826802013-10-19 00:09:25 -0700272abc
Larry Hastings7726ac92014-01-31 22:03:12 -0800273/*[copy end generated code: output=03cfd743661f0797 input=7b18d017f89f61cf]*/
Larry Hastings31826802013-10-19 00:09:25 -0700274xyz
275""", """
276 verbatim text here
277 lah dee dah
Larry Hastings2a727912014-01-16 11:32:01 -0800278/*[copy input]
Larry Hastings31826802013-10-19 00:09:25 -0700279def
Larry Hastings2a727912014-01-16 11:32:01 -0800280[copy start generated code]*/
Larry Hastings31826802013-10-19 00:09:25 -0700281def
Larry Hastings7726ac92014-01-31 22:03:12 -0800282/*[copy end generated code: output=7b18d017f89f61cf input=7b18d017f89f61cf]*/
Larry Hastings31826802013-10-19 00:09:25 -0700283xyz
284""")
285
286
287class ClinicParserTest(TestCase):
288 def test_trivial(self):
289 parser = DSLParser(FakeClinic())
290 block = clinic.Block("module os\nos.access")
291 parser.parse(block)
292 module, function = block.signatures
293 self.assertEqual("access", function.name)
294 self.assertEqual("os", module.name)
295
296 def test_ignore_line(self):
297 block = self.parse("#\nmodule os\nos.access")
298 module, function = block.signatures
299 self.assertEqual("access", function.name)
300 self.assertEqual("os", module.name)
301
302 def test_param(self):
303 function = self.parse_function("module os\nos.access\n path: int")
304 self.assertEqual("access", function.name)
Larry Hastings7726ac92014-01-31 22:03:12 -0800305 self.assertEqual(2, len(function.parameters))
Larry Hastings31826802013-10-19 00:09:25 -0700306 p = function.parameters['path']
307 self.assertEqual('path', p.name)
308 self.assertIsInstance(p.converter, clinic.int_converter)
309
310 def test_param_default(self):
311 function = self.parse_function("module os\nos.access\n follow_symlinks: bool = True")
312 p = function.parameters['follow_symlinks']
313 self.assertEqual(True, p.default)
314
Larry Hastings1abd7082014-01-16 14:15:03 -0800315 def test_param_with_continuations(self):
316 function = self.parse_function("module os\nos.access\n follow_symlinks: \\\n bool \\\n =\\\n True")
317 p = function.parameters['follow_symlinks']
318 self.assertEqual(True, p.default)
319
320 def test_param_default_expression(self):
321 function = self.parse_function("module os\nos.access\n follow_symlinks: int(c_default='MAXSIZE') = sys.maxsize")
322 p = function.parameters['follow_symlinks']
323 self.assertEqual(sys.maxsize, p.default)
324 self.assertEqual("MAXSIZE", p.converter.c_default)
325
326 s = self.parse_function_should_fail("module os\nos.access\n follow_symlinks: int = sys.maxsize")
327 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")
328
Larry Hastings31826802013-10-19 00:09:25 -0700329 def test_param_no_docstring(self):
330 function = self.parse_function("""
331module os
332os.access
333 follow_symlinks: bool = True
Larry Hastings7726ac92014-01-31 22:03:12 -0800334 something_else: str = ''""")
Larry Hastings31826802013-10-19 00:09:25 -0700335 p = function.parameters['follow_symlinks']
Larry Hastings7726ac92014-01-31 22:03:12 -0800336 self.assertEqual(3, len(function.parameters))
Larry Hastings31826802013-10-19 00:09:25 -0700337 self.assertIsInstance(function.parameters['something_else'].converter, clinic.str_converter)
338
Larry Hastings7726ac92014-01-31 22:03:12 -0800339 def test_param_default_parameters_out_of_order(self):
340 s = self.parse_function_should_fail("""
341module os
342os.access
343 follow_symlinks: bool = True
344 something_else: str""")
345 self.assertEqual(s, """Error on line 0:
346Can't have a parameter without a default ('something_else')
347after a parameter with a default!
348""")
349
Larry Hastings31826802013-10-19 00:09:25 -0700350 def disabled_test_converter_arguments(self):
351 function = self.parse_function("module os\nos.access\n path: path_t(allow_fd=1)")
352 p = function.parameters['path']
353 self.assertEqual(1, p.converter.args['allow_fd'])
354
Larry Hastings31826802013-10-19 00:09:25 -0700355 def test_function_docstring(self):
356 function = self.parse_function("""
357module os
358os.stat as os_stat_fn
359
360 path: str
361 Path to be examined
362
363Perform a stat system call on the given path.""")
364 self.assertEqual("""
Larry Hastings2623c8c2014-02-08 22:15:29 -0800365stat($module, /, path)
366--
367
Larry Hastings31826802013-10-19 00:09:25 -0700368Perform a stat system call on the given path.
369
Larry Hastings31826802013-10-19 00:09:25 -0700370 path
371 Path to be examined
372""".strip(), function.docstring)
373
374 def test_explicit_parameters_in_docstring(self):
375 function = self.parse_function("""
376module foo
377foo.bar
378 x: int
379 Documentation for x.
380 y: int
381
382This is the documentation for foo.
383
384Okay, we're done here.
385""")
386 self.assertEqual("""
Larry Hastings2623c8c2014-02-08 22:15:29 -0800387bar($module, /, x, y)
388--
389
Larry Hastings31826802013-10-19 00:09:25 -0700390This is the documentation for foo.
391
Larry Hastings31826802013-10-19 00:09:25 -0700392 x
393 Documentation for x.
394
395Okay, we're done here.
396""".strip(), function.docstring)
397
398 def test_parser_regression_special_character_in_parameter_column_of_docstring_first_line(self):
399 function = self.parse_function("""
400module os
401os.stat
402 path: str
403This/used to break Clinic!
404""")
Larry Hastings2623c8c2014-02-08 22:15:29 -0800405 self.assertEqual("stat($module, /, path)\n--\n\nThis/used to break Clinic!", function.docstring)
Larry Hastings31826802013-10-19 00:09:25 -0700406
407 def test_c_name(self):
408 function = self.parse_function("module os\nos.stat as os_stat_fn")
409 self.assertEqual("os_stat_fn", function.c_basename)
410
411 def test_return_converter(self):
412 function = self.parse_function("module os\nos.stat -> int")
413 self.assertIsInstance(function.return_converter, clinic.int_return_converter)
414
415 def test_star(self):
416 function = self.parse_function("module os\nos.access\n *\n follow_symlinks: bool = True")
417 p = function.parameters['follow_symlinks']
418 self.assertEqual(inspect.Parameter.KEYWORD_ONLY, p.kind)
419 self.assertEqual(0, p.group)
420
421 def test_group(self):
422 function = self.parse_function("module window\nwindow.border\n [\n ls : int\n ]\n /\n")
423 p = function.parameters['ls']
424 self.assertEqual(1, p.group)
425
426 def test_left_group(self):
427 function = self.parse_function("""
428module curses
Larry Hastings6d2ea212014-01-05 02:50:45 -0800429curses.addch
Larry Hastings31826802013-10-19 00:09:25 -0700430 [
431 y: int
432 Y-coordinate.
433 x: int
434 X-coordinate.
435 ]
436 ch: char
437 Character to add.
438 [
439 attr: long
440 Attributes for the character.
441 ]
442 /
443""")
444 for name, group in (
445 ('y', -1), ('x', -1),
446 ('ch', 0),
447 ('attr', 1),
448 ):
449 p = function.parameters[name]
450 self.assertEqual(p.group, group)
451 self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY)
452 self.assertEqual(function.docstring.strip(), """
Larry Hastings6d2ea212014-01-05 02:50:45 -0800453addch([y, x,] ch, [attr])
454
455
Larry Hastings31826802013-10-19 00:09:25 -0700456 y
457 Y-coordinate.
458 x
459 X-coordinate.
460 ch
461 Character to add.
462 attr
463 Attributes for the character.
464 """.strip())
465
466 def test_nested_groups(self):
467 function = self.parse_function("""
468module curses
Larry Hastings6d2ea212014-01-05 02:50:45 -0800469curses.imaginary
Larry Hastings31826802013-10-19 00:09:25 -0700470 [
471 [
472 y1: int
473 Y-coordinate.
474 y2: int
475 Y-coordinate.
476 ]
477 x1: int
478 X-coordinate.
479 x2: int
480 X-coordinate.
481 ]
482 ch: char
483 Character to add.
484 [
485 attr1: long
486 Attributes for the character.
487 attr2: long
488 Attributes for the character.
489 attr3: long
490 Attributes for the character.
491 [
492 attr4: long
493 Attributes for the character.
494 attr5: long
495 Attributes for the character.
496 attr6: long
497 Attributes for the character.
498 ]
499 ]
500 /
501""")
502 for name, group in (
503 ('y1', -2), ('y2', -2),
504 ('x1', -1), ('x2', -1),
505 ('ch', 0),
506 ('attr1', 1), ('attr2', 1), ('attr3', 1),
507 ('attr4', 2), ('attr5', 2), ('attr6', 2),
508 ):
509 p = function.parameters[name]
510 self.assertEqual(p.group, group)
511 self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY)
512
513 self.assertEqual(function.docstring.strip(), """
Larry Hastings2623c8c2014-02-08 22:15:29 -0800514imaginary([[y1, y2,] x1, x2,] ch, [attr1, attr2, attr3, [attr4, attr5,
515 attr6]])
Larry Hastings6d2ea212014-01-05 02:50:45 -0800516
517
Larry Hastings31826802013-10-19 00:09:25 -0700518 y1
519 Y-coordinate.
520 y2
521 Y-coordinate.
522 x1
523 X-coordinate.
524 x2
525 X-coordinate.
526 ch
527 Character to add.
528 attr1
529 Attributes for the character.
530 attr2
531 Attributes for the character.
532 attr3
533 Attributes for the character.
534 attr4
535 Attributes for the character.
536 attr5
537 Attributes for the character.
538 attr6
539 Attributes for the character.
540 """.strip())
541
542 def parse_function_should_fail(self, s):
543 with support.captured_stdout() as stdout:
544 with self.assertRaises(SystemExit):
545 self.parse_function(s)
546 return stdout.getvalue()
547
548 def test_disallowed_grouping__two_top_groups_on_left(self):
549 s = self.parse_function_should_fail("""
550module foo
551foo.two_top_groups_on_left
552 [
553 group1 : int
554 ]
555 [
556 group2 : int
557 ]
558 param: int
559 """)
560 self.assertEqual(s,
561 ('Error on line 0:\n'
Larry Hastings7726ac92014-01-31 22:03:12 -0800562 'Function two_top_groups_on_left has an unsupported group configuration. (Unexpected state 2.b)\n'))
Larry Hastings31826802013-10-19 00:09:25 -0700563
564 def test_disallowed_grouping__two_top_groups_on_right(self):
565 self.parse_function_should_fail("""
566module foo
567foo.two_top_groups_on_right
568 param: int
569 [
570 group1 : int
571 ]
572 [
573 group2 : int
574 ]
575 """)
576
577 def test_disallowed_grouping__parameter_after_group_on_right(self):
578 self.parse_function_should_fail("""
579module foo
580foo.parameter_after_group_on_right
581 param: int
582 [
583 [
584 group1 : int
585 ]
586 group2 : int
587 ]
588 """)
589
590 def test_disallowed_grouping__group_after_parameter_on_left(self):
591 self.parse_function_should_fail("""
592module foo
593foo.group_after_parameter_on_left
594 [
595 group2 : int
596 [
597 group1 : int
598 ]
599 ]
600 param: int
601 """)
602
603 def test_disallowed_grouping__empty_group_on_left(self):
604 self.parse_function_should_fail("""
605module foo
606foo.empty_group
607 [
608 [
609 ]
610 group2 : int
611 ]
612 param: int
613 """)
614
615 def test_disallowed_grouping__empty_group_on_right(self):
616 self.parse_function_should_fail("""
617module foo
618foo.empty_group
619 param: int
620 [
621 [
622 ]
623 group2 : int
624 ]
625 """)
626
627 def test_no_parameters(self):
628 function = self.parse_function("""
629module foo
630foo.bar
631
632Docstring
633
634""")
Larry Hastings2623c8c2014-02-08 22:15:29 -0800635 self.assertEqual("bar($module, /)\n--\n\nDocstring", function.docstring)
Larry Hastings7726ac92014-01-31 22:03:12 -0800636 self.assertEqual(1, len(function.parameters)) # self!
Larry Hastings31826802013-10-19 00:09:25 -0700637
Larry Hastings2623c8c2014-02-08 22:15:29 -0800638 def test_init_with_no_parameters(self):
639 function = self.parse_function("""
640module foo
641class foo.Bar "unused" "notneeded"
642foo.Bar.__init__
643
644Docstring
645
646""", signatures_in_block=3, function_index=2)
647 # self is not in the signature
648 self.assertEqual("Bar()\n--\n\nDocstring", function.docstring)
649 # but it *is* a parameter
650 self.assertEqual(1, len(function.parameters))
651
Larry Hastingsdfcd4672013-10-27 02:49:39 -0700652 def test_illegal_module_line(self):
653 self.parse_function_should_fail("""
654module foo
655foo.bar => int
656 /
657""")
658
659 def test_illegal_c_basename(self):
660 self.parse_function_should_fail("""
661module foo
662foo.bar as 935
663 /
664""")
665
Larry Hastings31826802013-10-19 00:09:25 -0700666 def test_single_star(self):
667 self.parse_function_should_fail("""
668module foo
669foo.bar
670 *
671 *
672""")
673
674 def test_parameters_required_after_star_without_initial_parameters_or_docstring(self):
675 self.parse_function_should_fail("""
676module foo
677foo.bar
678 *
679""")
680
681 def test_parameters_required_after_star_without_initial_parameters_with_docstring(self):
682 self.parse_function_should_fail("""
683module foo
684foo.bar
685 *
686Docstring here.
687""")
688
689 def test_parameters_required_after_star_with_initial_parameters_without_docstring(self):
690 self.parse_function_should_fail("""
691module foo
692foo.bar
693 this: int
694 *
695""")
696
697 def test_parameters_required_after_star_with_initial_parameters_and_docstring(self):
698 self.parse_function_should_fail("""
699module foo
700foo.bar
701 this: int
702 *
703Docstring.
704""")
705
706 def test_single_slash(self):
707 self.parse_function_should_fail("""
708module foo
709foo.bar
710 /
711 /
712""")
713
714 def test_mix_star_and_slash(self):
715 self.parse_function_should_fail("""
716module foo
717foo.bar
718 x: int
719 y: int
720 *
721 z: int
722 /
723""")
724
725 def test_parameters_not_permitted_after_slash_for_now(self):
726 self.parse_function_should_fail("""
727module foo
728foo.bar
729 /
730 x: int
731""")
732
733 def test_function_not_at_column_0(self):
734 function = self.parse_function("""
735 module foo
736 foo.bar
737 x: int
738 Nested docstring here, goeth.
739 *
740 y: str
741 Not at column 0!
742""")
743 self.assertEqual("""
Larry Hastings2623c8c2014-02-08 22:15:29 -0800744bar($module, /, x, *, y)
745--
746
Larry Hastings31826802013-10-19 00:09:25 -0700747Not at column 0!
748
Larry Hastings31826802013-10-19 00:09:25 -0700749 x
750 Nested docstring here, goeth.
751""".strip(), function.docstring)
752
Larry Hastings31826802013-10-19 00:09:25 -0700753 def test_directive(self):
754 c = FakeClinic()
755 parser = DSLParser(c)
756 parser.flag = False
757 parser.directives['setflag'] = lambda : setattr(parser, 'flag', True)
758 block = clinic.Block("setflag")
759 parser.parse(block)
760 self.assertTrue(parser.flag)
761
762 def test_legacy_converters(self):
763 block = self.parse('module os\nos.access\n path: "s"')
764 module, function = block.signatures
765 self.assertIsInstance((function.parameters['path']).converter, clinic.str_converter)
766
767 def parse(self, text):
768 c = FakeClinic()
769 parser = DSLParser(c)
770 block = clinic.Block(text)
771 parser.parse(block)
772 return block
773
Larry Hastings2623c8c2014-02-08 22:15:29 -0800774 def parse_function(self, text, signatures_in_block=2, function_index=1):
Larry Hastings31826802013-10-19 00:09:25 -0700775 block = self.parse(text)
776 s = block.signatures
Larry Hastings2623c8c2014-02-08 22:15:29 -0800777 self.assertEqual(len(s), signatures_in_block)
Larry Hastings31826802013-10-19 00:09:25 -0700778 assert isinstance(s[0], clinic.Module)
Larry Hastings2623c8c2014-02-08 22:15:29 -0800779 assert isinstance(s[function_index], clinic.Function)
780 return s[function_index]
Larry Hastings31826802013-10-19 00:09:25 -0700781
782 def test_scaffolding(self):
783 # test repr on special values
784 self.assertEqual(repr(clinic.unspecified), '<Unspecified>')
785 self.assertEqual(repr(clinic.NULL), '<Null>')
786
787 # test that fail fails
788 with support.captured_stdout() as stdout:
789 with self.assertRaises(SystemExit):
790 clinic.fail('The igloos are melting!', filename='clown.txt', line_number=69)
791 self.assertEqual(stdout.getvalue(), 'Error in file "clown.txt" on line 69:\nThe igloos are melting!\n')
792
793
Serhiy Storchaka837c7dc2018-12-25 10:17:28 +0200794class ClinicExternalTest(TestCase):
795 maxDiff = None
796
797 def test_external(self):
Victor Stinner8fba9522020-11-18 15:36:27 +0100798 # bpo-42398: Test that the destination file is left unchanged if the
799 # content does not change. Moreover, check also that the file
800 # modification time does not change in this case.
Serhiy Storchaka837c7dc2018-12-25 10:17:28 +0200801 source = support.findfile('clinic.test')
802 with open(source, 'r', encoding='utf-8') as f:
Victor Stinner8fba9522020-11-18 15:36:27 +0100803 orig_contents = f.read()
804
805 with os_helper.temp_dir() as tmp_dir:
806 testfile = os.path.join(tmp_dir, 'clinic.test.c')
Serhiy Storchaka837c7dc2018-12-25 10:17:28 +0200807 with open(testfile, 'w', encoding='utf-8') as f:
Victor Stinner8fba9522020-11-18 15:36:27 +0100808 f.write(orig_contents)
809 old_mtime_ns = os.stat(testfile).st_mtime_ns
810
811 clinic.parse_file(testfile)
812
Serhiy Storchaka837c7dc2018-12-25 10:17:28 +0200813 with open(testfile, 'r', encoding='utf-8') as f:
Victor Stinner8fba9522020-11-18 15:36:27 +0100814 new_contents = f.read()
815 new_mtime_ns = os.stat(testfile).st_mtime_ns
816
817 self.assertEqual(new_contents, orig_contents)
818 # Don't change the file modification time
819 # if the content does not change
820 self.assertEqual(new_mtime_ns, old_mtime_ns)
Serhiy Storchaka837c7dc2018-12-25 10:17:28 +0200821
822
Larry Hastings31826802013-10-19 00:09:25 -0700823if __name__ == "__main__":
824 unittest.main()