From 9ec2cce20c8d5932c15250f29e8cbe22863c941e Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Sun, 27 Apr 2025 22:52:10 +0200 Subject: [PATCH 1/7] Improve msgfmt.py test coverage --- Lib/test/test_tools/test_msgfmt.py | 356 +++++++++++++++++++++++++++++ 1 file changed, 356 insertions(+) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 7be606bbff606a..64af0544bd1314 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -240,6 +240,362 @@ def test_strings(self): with self.assertRaises(Exception): msgfmt.make('messages.po', 'messages.mo') + def test_general_syntax_errors(self): + invalid_po_files = ( + '', + '"', + '""', + '"foo"', + # 'msgid', # invalid but currently accepted + 'msgstr', + 'msgid_plural', + # 'msgctxt', # invalid but currently accepted + 'msgstr', + 'msgstr[0]', + '[0]', + + # unclosed string + ''' +msgid " +msgstr "bar" +''', + + # unclosed string + ''' +msgid "foo +msgstr "bar" +''', + + # unclosed string + ''' +msgid "foo" " +msgstr "bar" +''', + + # unclosed string + ''' +msgid "foo" +" +msgstr "bar" +''', + + # illegal backslash + ''' +msgid "foo\\" +" +msgstr "bar" +''', + + # msgid with an index + ''' +msgid[0] "foo" +msgstr "bar" +''', + + # invalid plural index + # invalid but currently accepted +# ''' +# msgid "foo" +# msgid_plural "foos" +# msgstr[foo] "baz" +# ''', + + # invalid plural index + ''' +msgid "foo" +msgid_plural "foos" +msgstr[0 "baz" +''', + + # invalid plural index + # invalid but currently accepted +# ''' +# msgid "foo" +# msgid_plural "foos" +# msgstr[] "baz" +# ''', + + # invalid plural index + ''' +msgid "foo" +msgid_plural "foos" +msgstr1] "baz" +''', + + # invalid plural index + ''' +msgid "foo" +msgid_plural "foos" +msgstr[[0]] "baz" +''', + ) + with temp_cwd(): + for invalid_po 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 self.assertRaises((SystemExit, UnboundLocalError, + IndexError, SyntaxError)): + msgfmt.make('messages.po', 'messages.mo') + + def test_semantic_errors(self): + invalid_po_files = ( + # missing msgid after msgctxt + # invalid but currently accepted + # 'msgctxt "foo"', + + # missing msgstr after msgid + # invalid but currently accepted + # 'msgid "foo"', + + # comment line not allowed after msgctxt + # invalid but currently accepted +# ''' +# msgctxt "foo" +# # comment +# msgid "bar" +# msgstr "bar" +# ''', + + # comment line not allowed after msgid + # invalid but currently accepted +# ''' +# msgid "foo" +# # comment +# msgstr "bar" +# ''', + + # comment line not allowed after msgid_plural + # invalid but currently accepted +# ''' +# msgid "foo" +# msgid_plural "foos" +# # comment +# msgstr[0] "bar" +# ''', + + # msgctxt not allowed after msgctxt + # invalid but currently accepted +# ''' +# msgctxt "foo" + +# msgctxt "bar" +# msgid "foo" +# msgstr "bar" +# ''', + + # msgctxt not allowed after msgid + # invalid but currently accepted +# ''' +# msgid "foo" +# msgctxt "bar" + +# msgid "bar" +# msgstr "baz" +# ''', + + # msgctxt not allowed after msgid_plural + # invalid but currently accepted +# ''' +# msgid "foo" +# msgid_plural "foos" +# msgctxt "bar" + +# msgid "bar" +# msgstr "baz" +# ''', + + # msgid not allowed after msgid + # invalid but currently accepted +# ''' +# msgid "foo" + +# msgid "bar" +# msgstr "baz" +# ''', + + # msgid not allowed after msgid_plural + # invalid but currently accepted +# ''' +# msgid "foo" +# msgid_plural "foos" + +# msgid "bar" +# msgstr "baz" +# ''', + + # msgid_plural must be preceded by msgid + ''' +msgid_plural "foos" + +msgid "bar" +msgstr "baz" +''', + + # msgid_plural not allowed after comment + ''' +# comment +msgid_plural "foos" + +msgid "bar" +msgstr "baz" +''', + + # msgid_plural not allowed after msgid_plural + # invalid but currently accepted +# ''' +# msgid "foo" +# msgid_plural "foos" +# msgid_plural "bars" + +# msgid "bar" +# msgstr "baz" +# ''', + + # msgid_plural not allowed after msgctxt + ''' +msgctxt "foo" +msgid_plural "foos" + +msgid "bar" +msgstr "baz" +''', + + # msgid_plural not allowed after msgstr + ''' +msgid "foo" +msgstr "bar" +msgid_plural "foos" + +msgid "bar" +msgstr "baz" +''', + + # msgstr must be preceded by msgid + ''' +msgstr "foo" + +msgid "bar" +msgstr "baz" +''', + + # msgstr not allowed after comment + # invalid but currently accepted +# ''' +# # comment +# # msgstr "foo" + +# msgid "bar" +# msgstr "baz" +# ''', + + # msgstr not allowed after msgctxt + ''' +msgctxt "foo" +msgstr "bar" + +msgid "foo" +msgstr "bar" +''', + + # msgstr not allowed after msgstr + # invalid but currently accepted +# ''' +# msgid "foo" +# msgstr "bar" +# msgstr "baz" + +# msgid "bar" +# msgstr "baz" +# ''', + + # missing msgid_plural section + ''' +msgid "foo" +msgstr[0] "bar" + +msgid "bar" +msgstr "baz" +''' + ) + with temp_cwd(): + for invalid_po 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 self.assertRaises((SystemExit, UnboundLocalError)): + msgfmt.make('messages.po', 'messages.mo') + + def test_msgstr_invalid_indices(self): + invalid_po_files = ( + # wrong plural form index + # invalid but currently accepted +# ''' +# msgid "foo" +# msgid_plural "foos" +# msgstr[42] "bar" +# ''', + + # wrong plural form index + # invalid but currently accepted +# ''' +# msgid "foo" +# msgid_plural "foos" +# msgstr[0] "bar" +# msgstr[42] "bars" +# ''', + + # msgstr not pluralized + ''' +msgid "foo" +msgid_plural "foos" +msgstr "bar" +''', + ) + with temp_cwd(): + for invalid_po 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 self.assertRaises(SystemExit): + msgfmt.make('messages.po', 'messages.mo') + + def test_duplicate_entries(self): + invalid_po_files = ( + # duplicate msgid + # invalid but currently accepted +# ''' +# msgid "foo" +# msgstr "bar" + +# msgid "foo" +# msgstr "baz" +# ''', + + # duplicate msgctxt+msgid + # invalid but currently accepted +# ''' +# msgctxt "context" +# msgid "foo" +# msgstr "bar" + +# msgctxt "context" +# msgid "foo" +# msgstr "baz" +# ''' + ) + with temp_cwd(): + for invalid_po 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 self.assertRaises(SystemExit): + msgfmt.make('messages.po', 'messages.mo') + class CLITest(unittest.TestCase): From 6ba1488e67469ced05d0db9c5bd1f1bc9a3515d3 Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Sun, 4 May 2025 20:32:09 +0200 Subject: [PATCH 2/7] Remove commented-out tests --- Lib/test/test_tools/test_msgfmt.py | 182 ----------------------------- 1 file changed, 182 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 64af0544bd1314..dd47ad5674c65f 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -292,14 +292,6 @@ def test_general_syntax_errors(self): msgstr "bar" ''', - # invalid plural index - # invalid but currently accepted -# ''' -# msgid "foo" -# msgid_plural "foos" -# msgstr[foo] "baz" -# ''', - # invalid plural index ''' msgid "foo" @@ -307,14 +299,6 @@ def test_general_syntax_errors(self): msgstr[0 "baz" ''', - # invalid plural index - # invalid but currently accepted -# ''' -# msgid "foo" -# msgid_plural "foos" -# msgstr[] "baz" -# ''', - # invalid plural index ''' msgid "foo" @@ -341,90 +325,6 @@ def test_general_syntax_errors(self): def test_semantic_errors(self): invalid_po_files = ( - # missing msgid after msgctxt - # invalid but currently accepted - # 'msgctxt "foo"', - - # missing msgstr after msgid - # invalid but currently accepted - # 'msgid "foo"', - - # comment line not allowed after msgctxt - # invalid but currently accepted -# ''' -# msgctxt "foo" -# # comment -# msgid "bar" -# msgstr "bar" -# ''', - - # comment line not allowed after msgid - # invalid but currently accepted -# ''' -# msgid "foo" -# # comment -# msgstr "bar" -# ''', - - # comment line not allowed after msgid_plural - # invalid but currently accepted -# ''' -# msgid "foo" -# msgid_plural "foos" -# # comment -# msgstr[0] "bar" -# ''', - - # msgctxt not allowed after msgctxt - # invalid but currently accepted -# ''' -# msgctxt "foo" - -# msgctxt "bar" -# msgid "foo" -# msgstr "bar" -# ''', - - # msgctxt not allowed after msgid - # invalid but currently accepted -# ''' -# msgid "foo" -# msgctxt "bar" - -# msgid "bar" -# msgstr "baz" -# ''', - - # msgctxt not allowed after msgid_plural - # invalid but currently accepted -# ''' -# msgid "foo" -# msgid_plural "foos" -# msgctxt "bar" - -# msgid "bar" -# msgstr "baz" -# ''', - - # msgid not allowed after msgid - # invalid but currently accepted -# ''' -# msgid "foo" - -# msgid "bar" -# msgstr "baz" -# ''', - - # msgid not allowed after msgid_plural - # invalid but currently accepted -# ''' -# msgid "foo" -# msgid_plural "foos" - -# msgid "bar" -# msgstr "baz" -# ''', - # msgid_plural must be preceded by msgid ''' msgid_plural "foos" @@ -442,17 +342,6 @@ def test_semantic_errors(self): msgstr "baz" ''', - # msgid_plural not allowed after msgid_plural - # invalid but currently accepted -# ''' -# msgid "foo" -# msgid_plural "foos" -# msgid_plural "bars" - -# msgid "bar" -# msgstr "baz" -# ''', - # msgid_plural not allowed after msgctxt ''' msgctxt "foo" @@ -480,16 +369,6 @@ def test_semantic_errors(self): msgstr "baz" ''', - # msgstr not allowed after comment - # invalid but currently accepted -# ''' -# # comment -# # msgstr "foo" - -# msgid "bar" -# msgstr "baz" -# ''', - # msgstr not allowed after msgctxt ''' msgctxt "foo" @@ -499,17 +378,6 @@ def test_semantic_errors(self): msgstr "bar" ''', - # msgstr not allowed after msgstr - # invalid but currently accepted -# ''' -# msgid "foo" -# msgstr "bar" -# msgstr "baz" - -# msgid "bar" -# msgstr "baz" -# ''', - # missing msgid_plural section ''' msgid "foo" @@ -530,23 +398,6 @@ def test_semantic_errors(self): def test_msgstr_invalid_indices(self): invalid_po_files = ( - # wrong plural form index - # invalid but currently accepted -# ''' -# msgid "foo" -# msgid_plural "foos" -# msgstr[42] "bar" -# ''', - - # wrong plural form index - # invalid but currently accepted -# ''' -# msgid "foo" -# msgid_plural "foos" -# msgstr[0] "bar" -# msgstr[42] "bars" -# ''', - # msgstr not pluralized ''' msgid "foo" @@ -563,39 +414,6 @@ def test_msgstr_invalid_indices(self): with self.assertRaises(SystemExit): msgfmt.make('messages.po', 'messages.mo') - def test_duplicate_entries(self): - invalid_po_files = ( - # duplicate msgid - # invalid but currently accepted -# ''' -# msgid "foo" -# msgstr "bar" - -# msgid "foo" -# msgstr "baz" -# ''', - - # duplicate msgctxt+msgid - # invalid but currently accepted -# ''' -# msgctxt "context" -# msgid "foo" -# msgstr "bar" - -# msgctxt "context" -# msgid "foo" -# msgstr "baz" -# ''' - ) - with temp_cwd(): - for invalid_po 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 self.assertRaises(SystemExit): - msgfmt.make('messages.po', 'messages.mo') - class CLITest(unittest.TestCase): From 05a205f4d43f79822ff39cac1e26de3c39be5492 Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Sun, 4 May 2025 20:42:47 +0200 Subject: [PATCH 3/7] Use textwrap.dedent --- Lib/test/test_tools/test_msgfmt.py | 175 +++++++++++++++-------------- 1 file changed, 88 insertions(+), 87 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index dd47ad5674c65f..e229f2f368720e 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -10,6 +10,7 @@ from test.support.os_helper import temp_cwd from test.support.script_helper import assert_python_failure, assert_python_ok from test.test_tools import imports_under_tool, skip_if_missing, toolsdir +from textwrap import dedent skip_if_missing('i18n') @@ -255,63 +256,63 @@ def test_general_syntax_errors(self): '[0]', # unclosed string - ''' -msgid " -msgstr "bar" -''', + dedent('''\ + msgid " + msgstr "bar" + '''), # unclosed string - ''' -msgid "foo -msgstr "bar" -''', + dedent('''\ + msgid "foo + msgstr "bar" + '''), # unclosed string - ''' -msgid "foo" " -msgstr "bar" -''', + dedent('''\ + msgid "foo" " + msgstr "bar" + '''), # unclosed string - ''' -msgid "foo" -" -msgstr "bar" -''', + dedent('''\ + msgid "foo" + " + msgstr "bar" + '''), # illegal backslash - ''' -msgid "foo\\" -" -msgstr "bar" -''', + dedent('''\ + msgid "foo\\" + " + msgstr "bar" + '''), # msgid with an index - ''' -msgid[0] "foo" -msgstr "bar" -''', + dedent('''\ + msgid[0] "foo" + msgstr "bar" + '''), # invalid plural index - ''' -msgid "foo" -msgid_plural "foos" -msgstr[0 "baz" -''', + dedent('''\ + msgid "foo" + msgid_plural "foos" + msgstr[0 "baz" + '''), # invalid plural index - ''' -msgid "foo" -msgid_plural "foos" -msgstr1] "baz" -''', + dedent('''\ + msgid "foo" + msgid_plural "foos" + msgstr1] "baz" + '''), # invalid plural index - ''' -msgid "foo" -msgid_plural "foos" -msgstr[[0]] "baz" -''', + dedent('''\ + msgid "foo" + msgid_plural "foos" + msgstr[[0]] "baz" + '''), ) with temp_cwd(): for invalid_po in invalid_po_files: @@ -326,66 +327,66 @@ def test_general_syntax_errors(self): def test_semantic_errors(self): invalid_po_files = ( # msgid_plural must be preceded by msgid - ''' -msgid_plural "foos" + dedent('''\ + msgid_plural "foos" -msgid "bar" -msgstr "baz" -''', + msgid "bar" + msgstr "baz" + '''), # msgid_plural not allowed after comment - ''' -# comment -msgid_plural "foos" + dedent('''\ + # comment + msgid_plural "foos" -msgid "bar" -msgstr "baz" -''', + msgid "bar" + msgstr "baz" + '''), # msgid_plural not allowed after msgctxt - ''' -msgctxt "foo" -msgid_plural "foos" + dedent('''\ + msgctxt "foo" + msgid_plural "foos" -msgid "bar" -msgstr "baz" -''', + msgid "bar" + msgstr "baz" + '''), # msgid_plural not allowed after msgstr - ''' -msgid "foo" -msgstr "bar" -msgid_plural "foos" + dedent('''\ + msgid "foo" + msgstr "bar" + msgid_plural "foos" -msgid "bar" -msgstr "baz" -''', + msgid "bar" + msgstr "baz" + '''), # msgstr must be preceded by msgid - ''' -msgstr "foo" + dedent('''\ + msgstr "foo" -msgid "bar" -msgstr "baz" -''', + msgid "bar" + msgstr "baz" + '''), # msgstr not allowed after msgctxt - ''' -msgctxt "foo" -msgstr "bar" + dedent('''\ + msgctxt "foo" + msgstr "bar" -msgid "foo" -msgstr "bar" -''', + msgid "foo" + msgstr "bar" + '''), # missing msgid_plural section - ''' -msgid "foo" -msgstr[0] "bar" + dedent('''\ + msgid "foo" + msgstr[0] "bar" -msgid "bar" -msgstr "baz" -''' + msgid "bar" + msgstr "baz" + '''), ) with temp_cwd(): for invalid_po in invalid_po_files: @@ -399,11 +400,11 @@ def test_semantic_errors(self): def test_msgstr_invalid_indices(self): invalid_po_files = ( # msgstr not pluralized - ''' -msgid "foo" -msgid_plural "foos" -msgstr "bar" -''', + dedent('''\ + msgid "foo" + msgid_plural "foos" + msgstr "bar" + '''), ) with temp_cwd(): for invalid_po in invalid_po_files: From 98917e53419c550ab4b9f35b8dc3c7435a12bdf2 Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Sun, 4 May 2025 20:48:08 +0200 Subject: [PATCH 4/7] Silence error output --- Lib/test/test_tools/test_msgfmt.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index e229f2f368720e..5865fdcc8b1670 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -4,13 +4,15 @@ 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 from test.test_tools import imports_under_tool, skip_if_missing, toolsdir -from textwrap import dedent skip_if_missing('i18n') @@ -320,9 +322,10 @@ def test_general_syntax_errors(self): Path('messages.po').write_text(invalid_po) # Reset the global MESSAGES dictionary msgfmt.MESSAGES.clear() - with self.assertRaises((SystemExit, UnboundLocalError, - IndexError, SyntaxError)): - msgfmt.make('messages.po', 'messages.mo') + with redirect_stderr(StringIO()) as output: + with self.assertRaises((SystemExit, UnboundLocalError, + IndexError, SyntaxError)): + msgfmt.make('messages.po', 'messages.mo') def test_semantic_errors(self): invalid_po_files = ( @@ -394,8 +397,9 @@ def test_semantic_errors(self): Path('messages.po').write_text(invalid_po) # Reset the global MESSAGES dictionary msgfmt.MESSAGES.clear() - with self.assertRaises((SystemExit, UnboundLocalError)): - msgfmt.make('messages.po', 'messages.mo') + with redirect_stderr(StringIO()) as output: + with self.assertRaises((SystemExit, UnboundLocalError)): + msgfmt.make('messages.po', 'messages.mo') def test_msgstr_invalid_indices(self): invalid_po_files = ( @@ -412,8 +416,9 @@ def test_msgstr_invalid_indices(self): Path('messages.po').write_text(invalid_po) # Reset the global MESSAGES dictionary msgfmt.MESSAGES.clear() - with self.assertRaises(SystemExit): - msgfmt.make('messages.po', 'messages.mo') + with redirect_stderr(StringIO()) as output: + with self.assertRaises(SystemExit): + msgfmt.make('messages.po', 'messages.mo') class CLITest(unittest.TestCase): From 2955e906503654be640b669ac8908b41104222fe Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Sun, 4 May 2025 21:11:56 +0200 Subject: [PATCH 5/7] Check error messages --- Lib/test/test_tools/test_msgfmt.py | 262 ++++++++++++++++++----------- 1 file changed, 161 insertions(+), 101 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 5865fdcc8b1670..8840901b25afe9 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -1,6 +1,7 @@ """Tests for the Tools/i18n/msgfmt.py tool.""" import json +import os import struct import sys import unittest @@ -244,80 +245,108 @@ def test_strings(self): 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 = ( - '', - '"', - '""', - '"foo"', + ('', None), + ('"', None), + ('""', 'Syntax error on messages.po:1 before:'), + ('"foo"', f'Syntax error on messages.po:1 before:{os.linesep}foo'), # 'msgid', # invalid but currently accepted - 'msgstr', - 'msgid_plural', + ('msgstr', None), + ('msgid_plural', 'msgid_plural not preceded by msgid on messages.po:1'), # 'msgctxt', # invalid but currently accepted - 'msgstr', - 'msgstr[0]', - '[0]', + ('msgstr', None), + ('msgstr[0]', None), + ('[0]', f'Syntax error on messages.po:1 before:{os.linesep}[0]'), # unclosed string - dedent('''\ - msgid " - msgstr "bar" - '''), + ( + dedent('''\ + msgid " + msgstr "bar" + '''), + None + ), # unclosed string - dedent('''\ - msgid "foo - msgstr "bar" - '''), + ( + dedent('''\ + msgid "foo + msgstr "bar" + '''), + None + ), # unclosed string - dedent('''\ - msgid "foo" " - msgstr "bar" - '''), + ( + dedent('''\ + msgid "foo" " + msgstr "bar" + '''), + None + ), # unclosed string - dedent('''\ - msgid "foo" - " - msgstr "bar" - '''), + ( + dedent('''\ + msgid "foo" + " + msgstr "bar" + '''), + None + ), # illegal backslash - dedent('''\ - msgid "foo\\" - " - msgstr "bar" - '''), + ( + dedent('''\ + msgid "foo\\" + " + msgstr "bar" + '''), + None + ), # msgid with an index - dedent('''\ - msgid[0] "foo" - msgstr "bar" - '''), + ( + dedent('''\ + msgid[0] "foo" + msgstr "bar" + '''), + None + ), # invalid plural index - dedent('''\ - msgid "foo" - msgid_plural "foos" - msgstr[0 "baz" - '''), + ( + dedent('''\ + msgid "foo" + msgid_plural "foos" + msgstr[0 "baz" + '''), + None + ), # invalid plural index - dedent('''\ - msgid "foo" - msgid_plural "foos" - msgstr1] "baz" - '''), + ( + 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" - '''), + ( + dedent('''\ + msgid "foo" + msgid_plural "foos" + msgstr[[0]] "baz" + '''), + None + ) ) with temp_cwd(): - for invalid_po in invalid_po_files: + 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 @@ -326,73 +355,97 @@ def test_general_syntax_errors(self): 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" + ( + dedent('''\ + msgid_plural "foos" - msgid "bar" - msgstr "baz" - '''), + 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" + ( + dedent('''\ + # comment + msgid_plural "foos" - msgid "bar" - msgstr "baz" - '''), + 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" + ( + dedent('''\ + msgctxt "foo" + msgid_plural "foos" - msgid "bar" - msgstr "baz" - '''), + 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" - '''), + ( + 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" + ( + dedent('''\ + msgstr "foo" - msgid "bar" - msgstr "baz" - '''), + msgid "bar" + msgstr "baz" + '''), + None + ), # msgstr not allowed after msgctxt - dedent('''\ - msgctxt "foo" - msgstr "bar" + ( + dedent('''\ + msgctxt "foo" + msgstr "bar" - msgid "foo" - msgstr "bar" - '''), + msgid "foo" + msgstr "bar" + '''), + None + ), # missing msgid_plural section - dedent('''\ - msgid "foo" - msgstr[0] "bar" - - msgid "bar" - msgstr "baz" - '''), + ( + dedent('''\ + msgid "foo" + msgstr[0] "bar" + + msgid "bar" + msgstr "baz" + '''), + 'plural without msgid_plural on messages.po:2' + ), ) with temp_cwd(): - for invalid_po in invalid_po_files: + 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 @@ -400,18 +453,24 @@ def test_semantic_errors(self): 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" - '''), + ( + dedent('''\ + msgid "foo" + msgid_plural "foos" + msgstr "bar" + '''), + 'indexed msgstr required for plural on messages.po:3' + ), ) with temp_cwd(): - for invalid_po in invalid_po_files: + 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 @@ -419,6 +478,7 @@ def test_msgstr_invalid_indices(self): 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): From 389827a6f10c86f461e3c229955315d9550e4ea8 Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Sun, 4 May 2025 21:24:46 +0200 Subject: [PATCH 6/7] Get rid of extra newlines --- Lib/test/test_tools/test_msgfmt.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 8840901b25afe9..fd1f8b0b8ee6af 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -258,7 +258,6 @@ def test_general_syntax_errors(self): ('msgstr', None), ('msgstr[0]', None), ('[0]', f'Syntax error on messages.po:1 before:{os.linesep}[0]'), - # unclosed string ( dedent('''\ @@ -267,7 +266,6 @@ def test_general_syntax_errors(self): '''), None ), - # unclosed string ( dedent('''\ @@ -276,7 +274,6 @@ def test_general_syntax_errors(self): '''), None ), - # unclosed string ( dedent('''\ @@ -285,7 +282,6 @@ def test_general_syntax_errors(self): '''), None ), - # unclosed string ( dedent('''\ @@ -295,7 +291,6 @@ def test_general_syntax_errors(self): '''), None ), - # illegal backslash ( dedent('''\ @@ -305,7 +300,6 @@ def test_general_syntax_errors(self): '''), None ), - # msgid with an index ( dedent('''\ @@ -314,7 +308,6 @@ def test_general_syntax_errors(self): '''), None ), - # invalid plural index ( dedent('''\ @@ -324,7 +317,6 @@ def test_general_syntax_errors(self): '''), None ), - # invalid plural index ( dedent('''\ @@ -334,7 +326,6 @@ def test_general_syntax_errors(self): '''), 'indexed msgstr required for plural on messages.po:3' ), - # invalid plural index ( dedent('''\ @@ -371,7 +362,6 @@ def test_semantic_errors(self): '''), 'msgid_plural not preceded by msgid on messages.po:1' ), - # msgid_plural not allowed after comment ( dedent('''\ @@ -383,7 +373,6 @@ def test_semantic_errors(self): '''), 'msgid_plural not preceded by msgid on messages.po:2' ), - # msgid_plural not allowed after msgctxt ( dedent('''\ @@ -395,7 +384,6 @@ def test_semantic_errors(self): '''), 'msgid_plural not preceded by msgid on messages.po:2' ), - # msgid_plural not allowed after msgstr ( dedent('''\ @@ -408,7 +396,6 @@ def test_semantic_errors(self): '''), 'msgid_plural not preceded by msgid on messages.po:3' ), - # msgstr must be preceded by msgid ( dedent('''\ @@ -419,7 +406,6 @@ def test_semantic_errors(self): '''), None ), - # msgstr not allowed after msgctxt ( dedent('''\ @@ -431,7 +417,6 @@ def test_semantic_errors(self): '''), None ), - # missing msgid_plural section ( dedent('''\ From d6bdd7c0897b98f0988cc1fdf215504e1c668859 Mon Sep 17 00:00:00 2001 From: Tomas Roun Date: Sun, 4 May 2025 21:35:37 +0200 Subject: [PATCH 7/7] Windows fix --- Lib/test/test_tools/test_msgfmt.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index fd1f8b0b8ee6af..4dbcced4fdf691 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -1,7 +1,6 @@ """Tests for the Tools/i18n/msgfmt.py tool.""" import json -import os import struct import sys import unittest @@ -250,14 +249,14 @@ def test_general_syntax_errors(self): ('', None), ('"', None), ('""', 'Syntax error on messages.po:1 before:'), - ('"foo"', f'Syntax error on messages.po:1 before:{os.linesep}foo'), + ('"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:{os.linesep}[0]'), + ('[0]', f'Syntax error on messages.po:1 before:\n[0]'), # unclosed string ( dedent('''\