Skip to content

Commit 1277a5e

Browse files
authored
gh-130197: Improve test coverage of msgfmt.py part 2 (#133309)
1 parent f9c0e1e commit 1277a5e

1 file changed

Lines changed: 224 additions & 0 deletions

File tree

‎Lib/test/test_tools/test_msgfmt.py‎

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
import struct
55
import sys
66
import unittest
7+
from contextlib import redirect_stderr
78
from gettext import GNUTranslations
9+
from io import StringIO
810
from pathlib import Path
11+
from textwrap import dedent
912

1013
from test.support.os_helper import temp_cwd
1114
from test.support.script_helper import assert_python_failure, assert_python_ok
@@ -240,6 +243,227 @@ def test_strings(self):
240243
with self.assertRaises(Exception):
241244
msgfmt.make('messages.po', 'messages.mo')
242245

246+
def test_general_syntax_errors(self):
247+
# 2-tuples of input PO files and expected error messages
248+
invalid_po_files = (
249+
('', None),
250+
('"', None),
251+
('""', 'Syntax error on messages.po:1 before:'),
252+
('"foo"', f'Syntax error on messages.po:1 before:\nfoo'),
253+
# 'msgid', # invalid but currently accepted
254+
('msgstr', None),
255+
('msgid_plural', 'msgid_plural not preceded by msgid on messages.po:1'),
256+
# 'msgctxt', # invalid but currently accepted
257+
('msgstr', None),
258+
('msgstr[0]', None),
259+
('[0]', f'Syntax error on messages.po:1 before:\n[0]'),
260+
# unclosed string
261+
(
262+
dedent('''\
263+
msgid "
264+
msgstr "bar"
265+
'''),
266+
None
267+
),
268+
# unclosed string
269+
(
270+
dedent('''\
271+
msgid "foo
272+
msgstr "bar"
273+
'''),
274+
None
275+
),
276+
# unclosed string
277+
(
278+
dedent('''\
279+
msgid "foo" "
280+
msgstr "bar"
281+
'''),
282+
None
283+
),
284+
# unclosed string
285+
(
286+
dedent('''\
287+
msgid "foo"
288+
"
289+
msgstr "bar"
290+
'''),
291+
None
292+
),
293+
# illegal backslash
294+
(
295+
dedent('''\
296+
msgid "foo\\"
297+
"
298+
msgstr "bar"
299+
'''),
300+
None
301+
),
302+
# msgid with an index
303+
(
304+
dedent('''\
305+
msgid[0] "foo"
306+
msgstr "bar"
307+
'''),
308+
None
309+
),
310+
# invalid plural index
311+
(
312+
dedent('''\
313+
msgid "foo"
314+
msgid_plural "foos"
315+
msgstr[0 "baz"
316+
'''),
317+
None
318+
),
319+
# invalid plural index
320+
(
321+
dedent('''\
322+
msgid "foo"
323+
msgid_plural "foos"
324+
msgstr1] "baz"
325+
'''),
326+
'indexed msgstr required for plural on messages.po:3'
327+
),
328+
# invalid plural index
329+
(
330+
dedent('''\
331+
msgid "foo"
332+
msgid_plural "foos"
333+
msgstr[[0]] "baz"
334+
'''),
335+
None
336+
)
337+
)
338+
with temp_cwd():
339+
for invalid_po, err_msg in invalid_po_files:
340+
with self.subTest(invalid_po=invalid_po):
341+
Path('messages.po').write_text(invalid_po)
342+
# Reset the global MESSAGES dictionary
343+
msgfmt.MESSAGES.clear()
344+
with redirect_stderr(StringIO()) as output:
345+
with self.assertRaises((SystemExit, UnboundLocalError,
346+
IndexError, SyntaxError)):
347+
msgfmt.make('messages.po', 'messages.mo')
348+
if err_msg:
349+
self.assertEqual(output.getvalue().strip(), err_msg)
350+
351+
def test_semantic_errors(self):
352+
# 2-tuples of input PO files and expected error messages
353+
invalid_po_files = (
354+
# msgid_plural must be preceded by msgid
355+
(
356+
dedent('''\
357+
msgid_plural "foos"
358+
359+
msgid "bar"
360+
msgstr "baz"
361+
'''),
362+
'msgid_plural not preceded by msgid on messages.po:1'
363+
),
364+
# msgid_plural not allowed after comment
365+
(
366+
dedent('''\
367+
# comment
368+
msgid_plural "foos"
369+
370+
msgid "bar"
371+
msgstr "baz"
372+
'''),
373+
'msgid_plural not preceded by msgid on messages.po:2'
374+
),
375+
# msgid_plural not allowed after msgctxt
376+
(
377+
dedent('''\
378+
msgctxt "foo"
379+
msgid_plural "foos"
380+
381+
msgid "bar"
382+
msgstr "baz"
383+
'''),
384+
'msgid_plural not preceded by msgid on messages.po:2'
385+
),
386+
# msgid_plural not allowed after msgstr
387+
(
388+
dedent('''\
389+
msgid "foo"
390+
msgstr "bar"
391+
msgid_plural "foos"
392+
393+
msgid "bar"
394+
msgstr "baz"
395+
'''),
396+
'msgid_plural not preceded by msgid on messages.po:3'
397+
),
398+
# msgstr must be preceded by msgid
399+
(
400+
dedent('''\
401+
msgstr "foo"
402+
403+
msgid "bar"
404+
msgstr "baz"
405+
'''),
406+
None
407+
),
408+
# msgstr not allowed after msgctxt
409+
(
410+
dedent('''\
411+
msgctxt "foo"
412+
msgstr "bar"
413+
414+
msgid "foo"
415+
msgstr "bar"
416+
'''),
417+
None
418+
),
419+
# missing msgid_plural section
420+
(
421+
dedent('''\
422+
msgid "foo"
423+
msgstr[0] "bar"
424+
425+
msgid "bar"
426+
msgstr "baz"
427+
'''),
428+
'plural without msgid_plural on messages.po:2'
429+
),
430+
)
431+
with temp_cwd():
432+
for invalid_po, err_msg in invalid_po_files:
433+
with self.subTest(invalid_po=invalid_po):
434+
Path('messages.po').write_text(invalid_po)
435+
# Reset the global MESSAGES dictionary
436+
msgfmt.MESSAGES.clear()
437+
with redirect_stderr(StringIO()) as output:
438+
with self.assertRaises((SystemExit, UnboundLocalError)):
439+
msgfmt.make('messages.po', 'messages.mo')
440+
if err_msg:
441+
self.assertEqual(output.getvalue().strip(), err_msg)
442+
443+
def test_msgstr_invalid_indices(self):
444+
# 2-tuples of input PO files and expected error messages
445+
invalid_po_files = (
446+
# msgstr not pluralized
447+
(
448+
dedent('''\
449+
msgid "foo"
450+
msgid_plural "foos"
451+
msgstr "bar"
452+
'''),
453+
'indexed msgstr required for plural on messages.po:3'
454+
),
455+
)
456+
with temp_cwd():
457+
for invalid_po, err_msg in invalid_po_files:
458+
with self.subTest(invalid_po=invalid_po):
459+
Path('messages.po').write_text(invalid_po)
460+
# Reset the global MESSAGES dictionary
461+
msgfmt.MESSAGES.clear()
462+
with redirect_stderr(StringIO()) as output:
463+
with self.assertRaises(SystemExit):
464+
msgfmt.make('messages.po', 'messages.mo')
465+
self.assertEqual(output.getvalue().strip(), err_msg)
466+
243467

244468
class CLITest(unittest.TestCase):
245469

0 commit comments

Comments
 (0)