https://github.com/python/cpython/commit/1277a5e5820ee747865413998981ceba90587bcc
commit: 1277a5e5820ee747865413998981ceba90587bcc
branch: main
author: Tomas R. <[email protected]>
committer: ambv <[email protected]>
date: 2026-10-20T17:25:31-07:00
summary:
gh-130197: Improve test coverage of `msgfmt.py` part 2 (#133309)
files:
M Lib/test/test_tools/test_msgfmt.py
diff --git a/Lib/test/test_tools/test_msgfmt.py
b/Lib/test/test_tools/test_msgfmt.py
index 7be606bbff606a3..3019e52d22ec82d 100644
--- a/Lib/test/test_tools/test_msgfmt.py
+++ b/Lib/test/test_tools/test_msgfmt.py
@@ -4,8 +4,11 @@
import struct
import sys
import unittest
+from contextlib import redirect_stderr
from gettext import GNUTranslations
+from io import StringIO
from pathlib import Path
+from textwrap import dedent
from test.support.os_helper import temp_cwd
from test.support.script_helper import assert_python_failure, assert_python_ok
@@ -240,6 +243,227 @@ def test_strings(self):
with self.assertRaises(Exception):
msgfmt.make('messages.po', 'messages.mo')
+ def test_general_syntax_errors(self):
+ # 2-tuples of input PO files and expected error messages
+ invalid_po_files = (
+ ('', None),
+ ('"', None),
+ ('""', 'Syntax error on messages.po:1 before:'),
+ ('"foo"', f'Syntax error on messages.po:1 before:\nfoo'),
+ # 'msgid', # invalid but currently accepted
+ ('msgstr', None),
+ ('msgid_plural', 'msgid_plural not preceded by msgid on
messages.po:1'),
+ # 'msgctxt', # invalid but currently accepted
+ ('msgstr', None),
+ ('msgstr[0]', None),
+ ('[0]', f'Syntax error on messages.po:1 before:\n[0]'),
+ # unclosed string
+ (
+ dedent('''\
+ msgid "
+ msgstr "bar"
+ '''),
+ None
+ ),
+ # unclosed string
+ (
+ dedent('''\
+ msgid "foo
+ msgstr "bar"
+ '''),
+ None
+ ),
+ # unclosed string
+ (
+ dedent('''\
+ msgid "foo" "
+ msgstr "bar"
+ '''),
+ None
+ ),
+ # unclosed string
+ (
+ dedent('''\
+ msgid "foo"
+ "
+ msgstr "bar"
+ '''),
+ None
+ ),
+ # illegal backslash
+ (
+ dedent('''\
+ msgid "foo\\"
+ "
+ msgstr "bar"
+ '''),
+ None
+ ),
+ # msgid with an index
+ (
+ dedent('''\
+ msgid[0] "foo"
+ msgstr "bar"
+ '''),
+ None
+ ),
+ # invalid plural index
+ (
+ dedent('''\
+ msgid "foo"
+ msgid_plural "foos"
+ msgstr[0 "baz"
+ '''),
+ None
+ ),
+ # invalid plural index
+ (
+ dedent('''\
+ msgid "foo"
+ msgid_plural "foos"
+ msgstr1] "baz"
+ '''),
+ 'indexed msgstr required for plural on messages.po:3'
+ ),
+ # invalid plural index
+ (
+ dedent('''\
+ msgid "foo"
+ msgid_plural "foos"
+ msgstr[[0]] "baz"
+ '''),
+ None
+ )
+ )
+ with temp_cwd():
+ for invalid_po, err_msg in invalid_po_files:
+ with self.subTest(invalid_po=invalid_po):
+ Path('messages.po').write_text(invalid_po)
+ # Reset the global MESSAGES dictionary
+ msgfmt.MESSAGES.clear()
+ with redirect_stderr(StringIO()) as output:
+ with self.assertRaises((SystemExit, UnboundLocalError,
+ IndexError, SyntaxError)):
+ msgfmt.make('messages.po', 'messages.mo')
+ if err_msg:
+ self.assertEqual(output.getvalue().strip(), err_msg)
+
+ def test_semantic_errors(self):
+ # 2-tuples of input PO files and expected error messages
+ invalid_po_files = (
+ # msgid_plural must be preceded by msgid
+ (
+ dedent('''\
+ msgid_plural "foos"
+
+ msgid "bar"
+ msgstr "baz"
+ '''),
+ 'msgid_plural not preceded by msgid on messages.po:1'
+ ),
+ # msgid_plural not allowed after comment
+ (
+ dedent('''\
+ # comment
+ msgid_plural "foos"
+
+ msgid "bar"
+ msgstr "baz"
+ '''),
+ 'msgid_plural not preceded by msgid on messages.po:2'
+ ),
+ # msgid_plural not allowed after msgctxt
+ (
+ dedent('''\
+ msgctxt "foo"
+ msgid_plural "foos"
+
+ msgid "bar"
+ msgstr "baz"
+ '''),
+ 'msgid_plural not preceded by msgid on messages.po:2'
+ ),
+ # msgid_plural not allowed after msgstr
+ (
+ dedent('''\
+ msgid "foo"
+ msgstr "bar"
+ msgid_plural "foos"
+
+ msgid "bar"
+ msgstr "baz"
+ '''),
+ 'msgid_plural not preceded by msgid on messages.po:3'
+ ),
+ # msgstr must be preceded by msgid
+ (
+ dedent('''\
+ msgstr "foo"
+
+ msgid "bar"
+ msgstr "baz"
+ '''),
+ None
+ ),
+ # msgstr not allowed after msgctxt
+ (
+ dedent('''\
+ msgctxt "foo"
+ msgstr "bar"
+
+ msgid "foo"
+ msgstr "bar"
+ '''),
+ None
+ ),
+ # missing msgid_plural section
+ (
+ dedent('''\
+ msgid "foo"
+ msgstr[0] "bar"
+
+ msgid "bar"
+ msgstr "baz"
+ '''),
+ 'plural without msgid_plural on messages.po:2'
+ ),
+ )
+ with temp_cwd():
+ for invalid_po, err_msg in invalid_po_files:
+ with self.subTest(invalid_po=invalid_po):
+ Path('messages.po').write_text(invalid_po)
+ # Reset the global MESSAGES dictionary
+ msgfmt.MESSAGES.clear()
+ with redirect_stderr(StringIO()) as output:
+ with self.assertRaises((SystemExit,
UnboundLocalError)):
+ msgfmt.make('messages.po', 'messages.mo')
+ if err_msg:
+ self.assertEqual(output.getvalue().strip(), err_msg)
+
+ def test_msgstr_invalid_indices(self):
+ # 2-tuples of input PO files and expected error messages
+ invalid_po_files = (
+ # msgstr not pluralized
+ (
+ dedent('''\
+ msgid "foo"
+ msgid_plural "foos"
+ msgstr "bar"
+ '''),
+ 'indexed msgstr required for plural on messages.po:3'
+ ),
+ )
+ with temp_cwd():
+ for invalid_po, err_msg in invalid_po_files:
+ with self.subTest(invalid_po=invalid_po):
+ Path('messages.po').write_text(invalid_po)
+ # Reset the global MESSAGES dictionary
+ msgfmt.MESSAGES.clear()
+ with redirect_stderr(StringIO()) as output:
+ with self.assertRaises(SystemExit):
+ msgfmt.make('messages.po', 'messages.mo')
+ self.assertEqual(output.getvalue().strip(), err_msg)
+
class CLITest(unittest.TestCase):
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]