1import os
2import base64
3import gettext
4import unittest
5
6from test import support
7from test.support import os_helper
8
9
10# TODO:
11#  - Add new tests, for example for "dgettext"
12#  - Remove dummy tests, for example testing for single and double quotes
13#    has no sense, it would have if we were testing a parser (i.e. pygettext)
14#  - Tests should have only one assert.
15
16GNU_MO_DATA = b'''\
173hIElQAAAAAJAAAAHAAAAGQAAAAAAAAArAAAAAAAAACsAAAAFQAAAK0AAAAjAAAAwwAAAKEAAADn
18AAAAMAAAAIkBAAAHAAAAugEAABYAAADCAQAAHAAAANkBAAALAAAA9gEAAEIBAAACAgAAFgAAAEUD
19AAAeAAAAXAMAAKEAAAB7AwAAMgAAAB0EAAAFAAAAUAQAABsAAABWBAAAIQAAAHIEAAAJAAAAlAQA
20AABSYXltb25kIEx1eHVyeSBZYWNoLXQAVGhlcmUgaXMgJXMgZmlsZQBUaGVyZSBhcmUgJXMgZmls
21ZXMAVGhpcyBtb2R1bGUgcHJvdmlkZXMgaW50ZXJuYXRpb25hbGl6YXRpb24gYW5kIGxvY2FsaXph
22dGlvbgpzdXBwb3J0IGZvciB5b3VyIFB5dGhvbiBwcm9ncmFtcyBieSBwcm92aWRpbmcgYW4gaW50
23ZXJmYWNlIHRvIHRoZSBHTlUKZ2V0dGV4dCBtZXNzYWdlIGNhdGFsb2cgbGlicmFyeS4AV2l0aCBj
24b250ZXh0BFRoZXJlIGlzICVzIGZpbGUAVGhlcmUgYXJlICVzIGZpbGVzAG11bGx1c2sAbXkgY29u
25dGV4dARudWRnZSBudWRnZQBteSBvdGhlciBjb250ZXh0BG51ZGdlIG51ZGdlAG51ZGdlIG51ZGdl
26AFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDMtMDQtMTEgMTQ6
27MzItMDQwMApMYXN0LVRyYW5zbGF0b3I6IEouIERhdmlkIEliYW5leiA8ai1kYXZpZEBub29zLmZy
28PgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpNSU1FLVZlcnNpb246
29IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4NTktMQpDb250ZW50
30LVRyYW5zZmVyLUVuY29kaW5nOiA4Yml0CkdlbmVyYXRlZC1CeTogcHlnZXR0ZXh0LnB5IDEuMQpQ
31bHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0d29iYmxlciBNYW5n
32cm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFoeXIgY2ViaXZxcmYg
33dmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVnIHNiZSBsYmhlIENs
34Z3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1ciBUQUgKdHJnZ3Jr
35ZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4ASGF5ICVzIGZpY2hlcm8gKGNvbnRleHQpAEhheSAl
36cyBmaWNoZXJvcyAoY29udGV4dCkAYmFjb24Ad2luayB3aW5rIChpbiAibXkgY29udGV4dCIpAHdp
37bmsgd2luayAoaW4gIm15IG90aGVyIGNvbnRleHQiKQB3aW5rIHdpbmsA
38'''
39
40# This data contains an invalid major version number (5)
41# An unexpected major version number should be treated as an error when
42# parsing a .mo file
43
44GNU_MO_DATA_BAD_MAJOR_VERSION = b'''\
453hIElQAABQAGAAAAHAAAAEwAAAALAAAAfAAAAAAAAACoAAAAFQAAAKkAAAAjAAAAvwAAAKEAAADj
46AAAABwAAAIUBAAALAAAAjQEAAEUBAACZAQAAFgAAAN8CAAAeAAAA9gIAAKEAAAAVAwAABQAAALcD
47AAAJAAAAvQMAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABQAAAAYAAAACAAAAAFJh
48eW1vbmQgTHV4dXJ5IFlhY2gtdABUaGVyZSBpcyAlcyBmaWxlAFRoZXJlIGFyZSAlcyBmaWxlcwBU
49aGlzIG1vZHVsZSBwcm92aWRlcyBpbnRlcm5hdGlvbmFsaXphdGlvbiBhbmQgbG9jYWxpemF0aW9u
50CnN1cHBvcnQgZm9yIHlvdXIgUHl0aG9uIHByb2dyYW1zIGJ5IHByb3ZpZGluZyBhbiBpbnRlcmZh
51Y2UgdG8gdGhlIEdOVQpnZXR0ZXh0IG1lc3NhZ2UgY2F0YWxvZyBsaWJyYXJ5LgBtdWxsdXNrAG51
52ZGdlIG51ZGdlAFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDAt
53MDgtMjkgMTI6MTktMDQ6MDAKTGFzdC1UcmFuc2xhdG9yOiBKLiBEYXZpZCBJYsOhw7FleiA8ai1k
54YXZpZEBub29zLmZyPgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpN
55SU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4
56NTktMQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBub25lCkdlbmVyYXRlZC1CeTogcHlnZXR0
57ZXh0LnB5IDEuMQpQbHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0
58d29iYmxlciBNYW5ncm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFo
59eXIgY2ViaXZxcmYgdmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVn
60IHNiZSBsYmhlIENsZ3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1
61ciBUQUgKdHJnZ3JrZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4AYmFjb24Ad2luayB3aW5rAA==
62'''
63
64# This data contains an invalid minor version number (7)
65# An unexpected minor version number only indicates that some of the file's
66# contents may not be able to be read. It does not indicate an error.
67
68GNU_MO_DATA_BAD_MINOR_VERSION = b'''\
693hIElQcAAAAGAAAAHAAAAEwAAAALAAAAfAAAAAAAAACoAAAAFQAAAKkAAAAjAAAAvwAAAKEAAADj
70AAAABwAAAIUBAAALAAAAjQEAAEUBAACZAQAAFgAAAN8CAAAeAAAA9gIAAKEAAAAVAwAABQAAALcD
71AAAJAAAAvQMAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABQAAAAYAAAACAAAAAFJh
72eW1vbmQgTHV4dXJ5IFlhY2gtdABUaGVyZSBpcyAlcyBmaWxlAFRoZXJlIGFyZSAlcyBmaWxlcwBU
73aGlzIG1vZHVsZSBwcm92aWRlcyBpbnRlcm5hdGlvbmFsaXphdGlvbiBhbmQgbG9jYWxpemF0aW9u
74CnN1cHBvcnQgZm9yIHlvdXIgUHl0aG9uIHByb2dyYW1zIGJ5IHByb3ZpZGluZyBhbiBpbnRlcmZh
75Y2UgdG8gdGhlIEdOVQpnZXR0ZXh0IG1lc3NhZ2UgY2F0YWxvZyBsaWJyYXJ5LgBtdWxsdXNrAG51
76ZGdlIG51ZGdlAFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDAt
77MDgtMjkgMTI6MTktMDQ6MDAKTGFzdC1UcmFuc2xhdG9yOiBKLiBEYXZpZCBJYsOhw7FleiA8ai1k
78YXZpZEBub29zLmZyPgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpN
79SU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4
80NTktMQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBub25lCkdlbmVyYXRlZC1CeTogcHlnZXR0
81ZXh0LnB5IDEuMQpQbHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0
82d29iYmxlciBNYW5ncm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFo
83eXIgY2ViaXZxcmYgdmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVn
84IHNiZSBsYmhlIENsZ3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1
85ciBUQUgKdHJnZ3JrZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4AYmFjb24Ad2luayB3aW5rAA==
86'''
87
88
89UMO_DATA = b'''\
903hIElQAAAAADAAAAHAAAADQAAAAAAAAAAAAAAAAAAABMAAAABAAAAE0AAAAQAAAAUgAAAA8BAABj
91AAAABAAAAHMBAAAWAAAAeAEAAABhYsOeAG15Y29udGV4dMOeBGFiw54AUHJvamVjdC1JZC1WZXJz
92aW9uOiAyLjAKUE8tUmV2aXNpb24tRGF0ZTogMjAwMy0wNC0xMSAxMjo0Mi0wNDAwCkxhc3QtVHJh
93bnNsYXRvcjogQmFycnkgQS4gV0Fyc2F3IDxiYXJyeUBweXRob24ub3JnPgpMYW5ndWFnZS1UZWFt
94OiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpNSU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5
95cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9dXRmLTgKQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzog
96N2JpdApHZW5lcmF0ZWQtQnk6IG1hbnVhbGx5CgDCpHl6AMKkeXogKGNvbnRleHQgdmVyc2lvbikA
97'''
98
99MMO_DATA = b'''\
1003hIElQAAAAABAAAAHAAAACQAAAADAAAALAAAAAAAAAA4AAAAeAEAADkAAAABAAAAAAAAAAAAAAAA
101UHJvamVjdC1JZC1WZXJzaW9uOiBObyBQcm9qZWN0IDAuMApQT1QtQ3JlYXRpb24tRGF0ZTogV2Vk
102IERlYyAxMSAwNzo0NDoxNSAyMDAyClBPLVJldmlzaW9uLURhdGU6IDIwMDItMDgtMTQgMDE6MTg6
103NTgrMDA6MDAKTGFzdC1UcmFuc2xhdG9yOiBKb2huIERvZSA8amRvZUBleGFtcGxlLmNvbT4KSmFu
104ZSBGb29iYXIgPGpmb29iYXJAZXhhbXBsZS5jb20+Ckxhbmd1YWdlLVRlYW06IHh4IDx4eEBleGFt
105cGxlLmNvbT4KTUlNRS1WZXJzaW9uOiAxLjAKQ29udGVudC1UeXBlOiB0ZXh0L3BsYWluOyBjaGFy
106c2V0PWlzby04ODU5LTE1CkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IHF1b3RlZC1wcmludGFi
107bGUKR2VuZXJhdGVkLUJ5OiBweWdldHRleHQucHkgMS4zCgA=
108'''
109
110LOCALEDIR = os.path.join('xx', 'LC_MESSAGES')
111MOFILE = os.path.join(LOCALEDIR, 'gettext.mo')
112MOFILE_BAD_MAJOR_VERSION = os.path.join(LOCALEDIR, 'gettext_bad_major_version.mo')
113MOFILE_BAD_MINOR_VERSION = os.path.join(LOCALEDIR, 'gettext_bad_minor_version.mo')
114UMOFILE = os.path.join(LOCALEDIR, 'ugettext.mo')
115MMOFILE = os.path.join(LOCALEDIR, 'metadata.mo')
116
117
118class GettextBaseTest(unittest.TestCase):
119    def setUp(self):
120        self.addCleanup(os_helper.rmtree, os.path.split(LOCALEDIR)[0])
121        if not os.path.isdir(LOCALEDIR):
122            os.makedirs(LOCALEDIR)
123        with open(MOFILE, 'wb') as fp:
124            fp.write(base64.decodebytes(GNU_MO_DATA))
125        with open(MOFILE_BAD_MAJOR_VERSION, 'wb') as fp:
126            fp.write(base64.decodebytes(GNU_MO_DATA_BAD_MAJOR_VERSION))
127        with open(MOFILE_BAD_MINOR_VERSION, 'wb') as fp:
128            fp.write(base64.decodebytes(GNU_MO_DATA_BAD_MINOR_VERSION))
129        with open(UMOFILE, 'wb') as fp:
130            fp.write(base64.decodebytes(UMO_DATA))
131        with open(MMOFILE, 'wb') as fp:
132            fp.write(base64.decodebytes(MMO_DATA))
133        self.env = self.enterContext(os_helper.EnvironmentVarGuard())
134        self.env['LANGUAGE'] = 'xx'
135        gettext._translations.clear()
136
137
138GNU_MO_DATA_ISSUE_17898 = b'''\
1393hIElQAAAAABAAAAHAAAACQAAAAAAAAAAAAAAAAAAAAsAAAAggAAAC0AAAAAUGx1cmFsLUZvcm1z
140OiBucGx1cmFscz0yOyBwbHVyYWw9KG4gIT0gMSk7CiMtIy0jLSMtIyAgbWVzc2FnZXMucG8gKEVk
141WCBTdHVkaW8pICAjLSMtIy0jLSMKQ29udGVudC1UeXBlOiB0ZXh0L3BsYWluOyBjaGFyc2V0PVVU
142Ri04CgA=
143'''
144
145class GettextTestCase1(GettextBaseTest):
146    def setUp(self):
147        GettextBaseTest.setUp(self)
148        self.localedir = os.curdir
149        self.mofile = MOFILE
150        gettext.install('gettext', self.localedir, names=['pgettext'])
151
152    def test_some_translations(self):
153        eq = self.assertEqual
154        # test some translations
155        eq(_('albatross'), 'albatross')
156        eq(_('mullusk'), 'bacon')
157        eq(_(r'Raymond Luxury Yach-t'), 'Throatwobbler Mangrove')
158        eq(_(r'nudge nudge'), 'wink wink')
159
160    def test_some_translations_with_context(self):
161        eq = self.assertEqual
162        eq(pgettext('my context', 'nudge nudge'),
163           'wink wink (in "my context")')
164        eq(pgettext('my other context', 'nudge nudge'),
165           'wink wink (in "my other context")')
166
167    def test_double_quotes(self):
168        eq = self.assertEqual
169        # double quotes
170        eq(_("albatross"), 'albatross')
171        eq(_("mullusk"), 'bacon')
172        eq(_(r"Raymond Luxury Yach-t"), 'Throatwobbler Mangrove')
173        eq(_(r"nudge nudge"), 'wink wink')
174
175    def test_triple_single_quotes(self):
176        eq = self.assertEqual
177        # triple single quotes
178        eq(_('''albatross'''), 'albatross')
179        eq(_('''mullusk'''), 'bacon')
180        eq(_(r'''Raymond Luxury Yach-t'''), 'Throatwobbler Mangrove')
181        eq(_(r'''nudge nudge'''), 'wink wink')
182
183    def test_triple_double_quotes(self):
184        eq = self.assertEqual
185        # triple double quotes
186        eq(_("""albatross"""), 'albatross')
187        eq(_("""mullusk"""), 'bacon')
188        eq(_(r"""Raymond Luxury Yach-t"""), 'Throatwobbler Mangrove')
189        eq(_(r"""nudge nudge"""), 'wink wink')
190
191    def test_multiline_strings(self):
192        eq = self.assertEqual
193        # multiline strings
194        eq(_('''This module provides internationalization and localization
195support for your Python programs by providing an interface to the GNU
196gettext message catalog library.'''),
197           '''Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba
198fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH
199trggrkg zrffntr pngnybt yvoenel.''')
200
201    def test_the_alternative_interface(self):
202        eq = self.assertEqual
203        neq = self.assertNotEqual
204        # test the alternative interface
205        with open(self.mofile, 'rb') as fp:
206            t = gettext.GNUTranslations(fp)
207        # Install the translation object
208        t.install()
209        eq(_('nudge nudge'), 'wink wink')
210        # Try unicode return type
211        t.install()
212        eq(_('mullusk'), 'bacon')
213        # Test installation of other methods
214        import builtins
215        t.install(names=["gettext", "ngettext"])
216        eq(_, t.gettext)
217        eq(builtins.gettext, t.gettext)
218        eq(ngettext, t.ngettext)
219        neq(pgettext, t.pgettext)
220        del builtins.gettext
221        del builtins.ngettext
222
223
224class GettextTestCase2(GettextBaseTest):
225    def setUp(self):
226        GettextBaseTest.setUp(self)
227        self.localedir = os.curdir
228        # Set up the bindings
229        gettext.bindtextdomain('gettext', self.localedir)
230        gettext.textdomain('gettext')
231        # For convenience
232        self._ = gettext.gettext
233
234    def test_bindtextdomain(self):
235        self.assertEqual(gettext.bindtextdomain('gettext'), self.localedir)
236
237    def test_textdomain(self):
238        self.assertEqual(gettext.textdomain(), 'gettext')
239
240    def test_bad_major_version(self):
241        with open(MOFILE_BAD_MAJOR_VERSION, 'rb') as fp:
242            with self.assertRaises(OSError) as cm:
243                gettext.GNUTranslations(fp)
244
245            exception = cm.exception
246            self.assertEqual(exception.errno, 0)
247            self.assertEqual(exception.strerror, "Bad version number 5")
248            self.assertEqual(exception.filename, MOFILE_BAD_MAJOR_VERSION)
249
250    def test_bad_minor_version(self):
251        with open(MOFILE_BAD_MINOR_VERSION, 'rb') as fp:
252            # Check that no error is thrown with a bad minor version number
253            gettext.GNUTranslations(fp)
254
255    def test_some_translations(self):
256        eq = self.assertEqual
257        # test some translations
258        eq(self._('albatross'), 'albatross')
259        eq(self._('mullusk'), 'bacon')
260        eq(self._(r'Raymond Luxury Yach-t'), 'Throatwobbler Mangrove')
261        eq(self._(r'nudge nudge'), 'wink wink')
262
263    def test_some_translations_with_context(self):
264        eq = self.assertEqual
265        eq(gettext.pgettext('my context', 'nudge nudge'),
266           'wink wink (in "my context")')
267        eq(gettext.pgettext('my other context', 'nudge nudge'),
268           'wink wink (in "my other context")')
269
270    def test_some_translations_with_context_and_domain(self):
271        eq = self.assertEqual
272        eq(gettext.dpgettext('gettext', 'my context', 'nudge nudge'),
273           'wink wink (in "my context")')
274        eq(gettext.dpgettext('gettext', 'my other context', 'nudge nudge'),
275           'wink wink (in "my other context")')
276
277    def test_double_quotes(self):
278        eq = self.assertEqual
279        # double quotes
280        eq(self._("albatross"), 'albatross')
281        eq(self._("mullusk"), 'bacon')
282        eq(self._(r"Raymond Luxury Yach-t"), 'Throatwobbler Mangrove')
283        eq(self._(r"nudge nudge"), 'wink wink')
284
285    def test_triple_single_quotes(self):
286        eq = self.assertEqual
287        # triple single quotes
288        eq(self._('''albatross'''), 'albatross')
289        eq(self._('''mullusk'''), 'bacon')
290        eq(self._(r'''Raymond Luxury Yach-t'''), 'Throatwobbler Mangrove')
291        eq(self._(r'''nudge nudge'''), 'wink wink')
292
293    def test_triple_double_quotes(self):
294        eq = self.assertEqual
295        # triple double quotes
296        eq(self._("""albatross"""), 'albatross')
297        eq(self._("""mullusk"""), 'bacon')
298        eq(self._(r"""Raymond Luxury Yach-t"""), 'Throatwobbler Mangrove')
299        eq(self._(r"""nudge nudge"""), 'wink wink')
300
301    def test_multiline_strings(self):
302        eq = self.assertEqual
303        # multiline strings
304        eq(self._('''This module provides internationalization and localization
305support for your Python programs by providing an interface to the GNU
306gettext message catalog library.'''),
307           '''Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba
308fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH
309trggrkg zrffntr pngnybt yvoenel.''')
310
311
312class PluralFormsTestCase(GettextBaseTest):
313    def setUp(self):
314        GettextBaseTest.setUp(self)
315        self.mofile = MOFILE
316
317    def test_plural_forms1(self):
318        eq = self.assertEqual
319        x = gettext.ngettext('There is %s file', 'There are %s files', 1)
320        eq(x, 'Hay %s fichero')
321        x = gettext.ngettext('There is %s file', 'There are %s files', 2)
322        eq(x, 'Hay %s ficheros')
323
324    def test_plural_context_forms1(self):
325        eq = self.assertEqual
326        x = gettext.npgettext('With context',
327                              'There is %s file', 'There are %s files', 1)
328        eq(x, 'Hay %s fichero (context)')
329        x = gettext.npgettext('With context',
330                              'There is %s file', 'There are %s files', 2)
331        eq(x, 'Hay %s ficheros (context)')
332
333    def test_plural_forms2(self):
334        eq = self.assertEqual
335        with open(self.mofile, 'rb') as fp:
336            t = gettext.GNUTranslations(fp)
337        x = t.ngettext('There is %s file', 'There are %s files', 1)
338        eq(x, 'Hay %s fichero')
339        x = t.ngettext('There is %s file', 'There are %s files', 2)
340        eq(x, 'Hay %s ficheros')
341
342    def test_plural_context_forms2(self):
343        eq = self.assertEqual
344        with open(self.mofile, 'rb') as fp:
345            t = gettext.GNUTranslations(fp)
346        x = t.npgettext('With context',
347                        'There is %s file', 'There are %s files', 1)
348        eq(x, 'Hay %s fichero (context)')
349        x = t.npgettext('With context',
350                        'There is %s file', 'There are %s files', 2)
351        eq(x, 'Hay %s ficheros (context)')
352
353    # Examples from http://www.gnu.org/software/gettext/manual/gettext.html
354
355    def test_ja(self):
356        eq = self.assertEqual
357        f = gettext.c2py('0')
358        s = ''.join([ str(f(x)) for x in range(200) ])
359        eq(s, "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
360
361    def test_de(self):
362        eq = self.assertEqual
363        f = gettext.c2py('n != 1')
364        s = ''.join([ str(f(x)) for x in range(200) ])
365        eq(s, "10111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")
366
367    def test_fr(self):
368        eq = self.assertEqual
369        f = gettext.c2py('n>1')
370        s = ''.join([ str(f(x)) for x in range(200) ])
371        eq(s, "00111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")
372
373    def test_lv(self):
374        eq = self.assertEqual
375        f = gettext.c2py('n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2')
376        s = ''.join([ str(f(x)) for x in range(200) ])
377        eq(s, "20111111111111111111101111111110111111111011111111101111111110111111111011111111101111111110111111111011111111111111111110111111111011111111101111111110111111111011111111101111111110111111111011111111")
378
379    def test_gd(self):
380        eq = self.assertEqual
381        f = gettext.c2py('n==1 ? 0 : n==2 ? 1 : 2')
382        s = ''.join([ str(f(x)) for x in range(200) ])
383        eq(s, "20122222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222")
384
385    def test_gd2(self):
386        eq = self.assertEqual
387        # Tests the combination of parentheses and "?:"
388        f = gettext.c2py('n==1 ? 0 : (n==2 ? 1 : 2)')
389        s = ''.join([ str(f(x)) for x in range(200) ])
390        eq(s, "20122222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222")
391
392    def test_ro(self):
393        eq = self.assertEqual
394        f = gettext.c2py('n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2')
395        s = ''.join([ str(f(x)) for x in range(200) ])
396        eq(s, "10111111111111111111222222222222222222222222222222222222222222222222222222222222222222222222222222222111111111111111111122222222222222222222222222222222222222222222222222222222222222222222222222222222")
397
398    def test_lt(self):
399        eq = self.assertEqual
400        f = gettext.c2py('n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2')
401        s = ''.join([ str(f(x)) for x in range(200) ])
402        eq(s, "20111111112222222222201111111120111111112011111111201111111120111111112011111111201111111120111111112011111111222222222220111111112011111111201111111120111111112011111111201111111120111111112011111111")
403
404    def test_ru(self):
405        eq = self.assertEqual
406        f = gettext.c2py('n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2')
407        s = ''.join([ str(f(x)) for x in range(200) ])
408        eq(s, "20111222222222222222201112222220111222222011122222201112222220111222222011122222201112222220111222222011122222222222222220111222222011122222201112222220111222222011122222201112222220111222222011122222")
409
410    def test_cs(self):
411        eq = self.assertEqual
412        f = gettext.c2py('(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2')
413        s = ''.join([ str(f(x)) for x in range(200) ])
414        eq(s, "20111222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222")
415
416    def test_pl(self):
417        eq = self.assertEqual
418        f = gettext.c2py('n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2')
419        s = ''.join([ str(f(x)) for x in range(200) ])
420        eq(s, "20111222222222222222221112222222111222222211122222221112222222111222222211122222221112222222111222222211122222222222222222111222222211122222221112222222111222222211122222221112222222111222222211122222")
421
422    def test_sl(self):
423        eq = self.assertEqual
424        f = gettext.c2py('n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3')
425        s = ''.join([ str(f(x)) for x in range(200) ])
426        eq(s, "30122333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333012233333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333")
427
428    def test_ar(self):
429        eq = self.assertEqual
430        f = gettext.c2py('n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5')
431        s = ''.join([ str(f(x)) for x in range(200) ])
432        eq(s, "01233333333444444444444444444444444444444444444444444444444444444444444444444444444444444444444444445553333333344444444444444444444444444444444444444444444444444444444444444444444444444444444444444444")
433
434    def test_security(self):
435        raises = self.assertRaises
436        # Test for a dangerous expression
437        raises(ValueError, gettext.c2py, "os.chmod('/etc/passwd',0777)")
438        # issue28563
439        raises(ValueError, gettext.c2py, '"(eval(foo) && ""')
440        raises(ValueError, gettext.c2py, 'f"{os.system(\'sh\')}"')
441        # Maximum recursion depth exceeded during compilation
442        raises(ValueError, gettext.c2py, 'n+'*10000 + 'n')
443        self.assertEqual(gettext.c2py('n+'*100 + 'n')(1), 101)
444        # MemoryError during compilation
445        raises(ValueError, gettext.c2py, '('*100 + 'n' + ')'*100)
446        # Maximum recursion depth exceeded in C to Python translator
447        raises(ValueError, gettext.c2py, '('*10000 + 'n' + ')'*10000)
448        self.assertEqual(gettext.c2py('('*20 + 'n' + ')'*20)(1), 1)
449
450    def test_chained_comparison(self):
451        # C doesn't chain comparison as Python so 2 == 2 == 2 gets different results
452        f = gettext.c2py('n == n == n')
453        self.assertEqual(''.join(str(f(x)) for x in range(3)), '010')
454        f = gettext.c2py('1 < n == n')
455        self.assertEqual(''.join(str(f(x)) for x in range(3)), '100')
456        f = gettext.c2py('n == n < 2')
457        self.assertEqual(''.join(str(f(x)) for x in range(3)), '010')
458        f = gettext.c2py('0 < n < 2')
459        self.assertEqual(''.join(str(f(x)) for x in range(3)), '111')
460
461    def test_decimal_number(self):
462        self.assertEqual(gettext.c2py('0123')(1), 123)
463
464    def test_invalid_syntax(self):
465        invalid_expressions = [
466            'x>1', '(n>1', 'n>1)', '42**42**42', '0xa', '1.0', '1e2',
467            'n>0x1', '+n', '-n', 'n()', 'n(1)', '1+', 'nn', 'n n',
468        ]
469        for expr in invalid_expressions:
470            with self.assertRaises(ValueError):
471                gettext.c2py(expr)
472
473    def test_nested_condition_operator(self):
474        self.assertEqual(gettext.c2py('n?1?2:3:4')(0), 4)
475        self.assertEqual(gettext.c2py('n?1?2:3:4')(1), 2)
476        self.assertEqual(gettext.c2py('n?1:3?4:5')(0), 4)
477        self.assertEqual(gettext.c2py('n?1:3?4:5')(1), 1)
478
479    def test_division(self):
480        f = gettext.c2py('2/n*3')
481        self.assertEqual(f(1), 6)
482        self.assertEqual(f(2), 3)
483        self.assertEqual(f(3), 0)
484        self.assertEqual(f(-1), -6)
485        self.assertRaises(ZeroDivisionError, f, 0)
486
487    def test_plural_number(self):
488        f = gettext.c2py('n != 1')
489        self.assertEqual(f(1), 0)
490        self.assertEqual(f(2), 1)
491        with self.assertWarns(DeprecationWarning):
492            self.assertEqual(f(1.0), 0)
493        with self.assertWarns(DeprecationWarning):
494            self.assertEqual(f(2.0), 1)
495        with self.assertWarns(DeprecationWarning):
496            self.assertEqual(f(1.1), 1)
497        self.assertRaises(TypeError, f, '2')
498        self.assertRaises(TypeError, f, b'2')
499        self.assertRaises(TypeError, f, [])
500        self.assertRaises(TypeError, f, object())
501
502
503class GNUTranslationParsingTest(GettextBaseTest):
504    def test_plural_form_error_issue17898(self):
505        with open(MOFILE, 'wb') as fp:
506            fp.write(base64.decodebytes(GNU_MO_DATA_ISSUE_17898))
507        with open(MOFILE, 'rb') as fp:
508            # If this runs cleanly, the bug is fixed.
509            t = gettext.GNUTranslations(fp)
510
511    def test_ignore_comments_in_headers_issue36239(self):
512        """Checks that comments like:
513
514            #-#-#-#-#  messages.po (EdX Studio)  #-#-#-#-#
515
516        are ignored.
517        """
518        with open(MOFILE, 'wb') as fp:
519            fp.write(base64.decodebytes(GNU_MO_DATA_ISSUE_17898))
520        with open(MOFILE, 'rb') as fp:
521            t = gettext.GNUTranslations(fp)
522            self.assertEqual(t.info()["plural-forms"], "nplurals=2; plural=(n != 1);")
523
524
525class UnicodeTranslationsTest(GettextBaseTest):
526    def setUp(self):
527        GettextBaseTest.setUp(self)
528        with open(UMOFILE, 'rb') as fp:
529            self.t = gettext.GNUTranslations(fp)
530        self._ = self.t.gettext
531        self.pgettext = self.t.pgettext
532
533    def test_unicode_msgid(self):
534        self.assertIsInstance(self._(''), str)
535
536    def test_unicode_msgstr(self):
537        self.assertEqual(self._('ab\xde'), '\xa4yz')
538
539    def test_unicode_context_msgstr(self):
540        t = self.pgettext('mycontext\xde', 'ab\xde')
541        self.assertTrue(isinstance(t, str))
542        self.assertEqual(t, '\xa4yz (context version)')
543
544
545class UnicodeTranslationsPluralTest(GettextBaseTest):
546    def setUp(self):
547        GettextBaseTest.setUp(self)
548        with open(MOFILE, 'rb') as fp:
549            self.t = gettext.GNUTranslations(fp)
550        self.ngettext = self.t.ngettext
551        self.npgettext = self.t.npgettext
552
553    def test_unicode_msgid(self):
554        unless = self.assertTrue
555        unless(isinstance(self.ngettext('', '', 1), str))
556        unless(isinstance(self.ngettext('', '', 2), str))
557
558    def test_unicode_context_msgid(self):
559        unless = self.assertTrue
560        unless(isinstance(self.npgettext('', '', '', 1), str))
561        unless(isinstance(self.npgettext('', '', '', 2), str))
562
563    def test_unicode_msgstr(self):
564        eq = self.assertEqual
565        unless = self.assertTrue
566        t = self.ngettext("There is %s file", "There are %s files", 1)
567        unless(isinstance(t, str))
568        eq(t, "Hay %s fichero")
569        unless(isinstance(t, str))
570        t = self.ngettext("There is %s file", "There are %s files", 5)
571        unless(isinstance(t, str))
572        eq(t, "Hay %s ficheros")
573
574    def test_unicode_msgstr_with_context(self):
575        eq = self.assertEqual
576        unless = self.assertTrue
577        t = self.npgettext("With context",
578                           "There is %s file", "There are %s files", 1)
579        unless(isinstance(t, str))
580        eq(t, "Hay %s fichero (context)")
581        t = self.npgettext("With context",
582                           "There is %s file", "There are %s files", 5)
583        unless(isinstance(t, str))
584        eq(t, "Hay %s ficheros (context)")
585
586
587class WeirdMetadataTest(GettextBaseTest):
588    def setUp(self):
589        GettextBaseTest.setUp(self)
590        with open(MMOFILE, 'rb') as fp:
591            try:
592                self.t = gettext.GNUTranslations(fp)
593            except:
594                self.tearDown()
595                raise
596
597    def test_weird_metadata(self):
598        info = self.t.info()
599        self.assertEqual(len(info), 9)
600        self.assertEqual(info['last-translator'],
601           'John Doe <[email protected]>\nJane Foobar <[email protected]>')
602
603
604class DummyGNUTranslations(gettext.GNUTranslations):
605    def foo(self):
606        return 'foo'
607
608
609class GettextCacheTestCase(GettextBaseTest):
610    def test_cache(self):
611        self.localedir = os.curdir
612        self.mofile = MOFILE
613
614        self.assertEqual(len(gettext._translations), 0)
615
616        t = gettext.translation('gettext', self.localedir)
617
618        self.assertEqual(len(gettext._translations), 1)
619
620        t = gettext.translation('gettext', self.localedir,
621                                class_=DummyGNUTranslations)
622
623        self.assertEqual(len(gettext._translations), 2)
624        self.assertEqual(t.__class__, DummyGNUTranslations)
625
626        # Calling it again doesn't add to the cache
627
628        t = gettext.translation('gettext', self.localedir,
629                                class_=DummyGNUTranslations)
630
631        self.assertEqual(len(gettext._translations), 2)
632        self.assertEqual(t.__class__, DummyGNUTranslations)
633
634
635class MiscTestCase(unittest.TestCase):
636    def test__all__(self):
637        support.check__all__(self, gettext,
638                             not_exported={'c2py', 'ENOENT'})
639
640
641if __name__ == '__main__':
642    unittest.main()
643
644
645# For reference, here's the .po file used to created the GNU_MO_DATA above.
646#
647# The original version was automatically generated from the sources with
648# pygettext. Later it was manually modified to add plural forms support.
649
650b'''
651# Dummy translation for the Python test_gettext.py module.
652# Copyright (C) 2001 Python Software Foundation
653# Barry Warsaw <[email protected]>, 2000.
654#
655msgid ""
656msgstr ""
657"Project-Id-Version: 2.0\n"
658"PO-Revision-Date: 2003-04-11 14:32-0400\n"
659"Last-Translator: J. David Ibanez <[email protected]>\n"
660"Language-Team: XX <[email protected]>\n"
661"MIME-Version: 1.0\n"
662"Content-Type: text/plain; charset=iso-8859-1\n"
663"Content-Transfer-Encoding: 8bit\n"
664"Generated-By: pygettext.py 1.1\n"
665"Plural-Forms: nplurals=2; plural=n!=1;\n"
666
667#: test_gettext.py:19 test_gettext.py:25 test_gettext.py:31 test_gettext.py:37
668#: test_gettext.py:51 test_gettext.py:80 test_gettext.py:86 test_gettext.py:92
669#: test_gettext.py:98
670msgid "nudge nudge"
671msgstr "wink wink"
672
673msgctxt "my context"
674msgid "nudge nudge"
675msgstr "wink wink (in \"my context\")"
676
677msgctxt "my other context"
678msgid "nudge nudge"
679msgstr "wink wink (in \"my other context\")"
680
681#: test_gettext.py:16 test_gettext.py:22 test_gettext.py:28 test_gettext.py:34
682#: test_gettext.py:77 test_gettext.py:83 test_gettext.py:89 test_gettext.py:95
683msgid "albatross"
684msgstr ""
685
686#: test_gettext.py:18 test_gettext.py:24 test_gettext.py:30 test_gettext.py:36
687#: test_gettext.py:79 test_gettext.py:85 test_gettext.py:91 test_gettext.py:97
688msgid "Raymond Luxury Yach-t"
689msgstr "Throatwobbler Mangrove"
690
691#: test_gettext.py:17 test_gettext.py:23 test_gettext.py:29 test_gettext.py:35
692#: test_gettext.py:56 test_gettext.py:78 test_gettext.py:84 test_gettext.py:90
693#: test_gettext.py:96
694msgid "mullusk"
695msgstr "bacon"
696
697#: test_gettext.py:40 test_gettext.py:101
698msgid ""
699"This module provides internationalization and localization\n"
700"support for your Python programs by providing an interface to the GNU\n"
701"gettext message catalog library."
702msgstr ""
703"Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba\n"
704"fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH\n"
705"trggrkg zrffntr pngnybt yvoenel."
706
707# Manually added, as neither pygettext nor xgettext support plural forms
708# in Python.
709msgid "There is %s file"
710msgid_plural "There are %s files"
711msgstr[0] "Hay %s fichero"
712msgstr[1] "Hay %s ficheros"
713
714# Manually added, as neither pygettext nor xgettext support plural forms
715# and context in Python.
716msgctxt "With context"
717msgid "There is %s file"
718msgid_plural "There are %s files"
719msgstr[0] "Hay %s fichero (context)"
720msgstr[1] "Hay %s ficheros (context)"
721'''
722
723# Here's the second example po file example, used to generate the UMO_DATA
724# containing utf-8 encoded Unicode strings
725
726b'''
727# Dummy translation for the Python test_gettext.py module.
728# Copyright (C) 2001 Python Software Foundation
729# Barry Warsaw <[email protected]>, 2000.
730#
731msgid ""
732msgstr ""
733"Project-Id-Version: 2.0\n"
734"PO-Revision-Date: 2003-04-11 12:42-0400\n"
735"Last-Translator: Barry A. WArsaw <[email protected]>\n"
736"Language-Team: XX <[email protected]>\n"
737"MIME-Version: 1.0\n"
738"Content-Type: text/plain; charset=utf-8\n"
739"Content-Transfer-Encoding: 7bit\n"
740"Generated-By: manually\n"
741
742#: nofile:0
743msgid "ab\xc3\x9e"
744msgstr "\xc2\xa4yz"
745
746#: nofile:1
747msgctxt "mycontext\xc3\x9e"
748msgid "ab\xc3\x9e"
749msgstr "\xc2\xa4yz (context version)"
750'''
751
752# Here's the third example po file, used to generate MMO_DATA
753
754b'''
755msgid ""
756msgstr ""
757"Project-Id-Version: No Project 0.0\n"
758"POT-Creation-Date: Wed Dec 11 07:44:15 2002\n"
759"PO-Revision-Date: 2002-08-14 01:18:58+00:00\n"
760"Last-Translator: John Doe <[email protected]>\n"
761"Jane Foobar <[email protected]>\n"
762"Language-Team: xx <[email protected]>\n"
763"MIME-Version: 1.0\n"
764"Content-Type: text/plain; charset=iso-8859-15\n"
765"Content-Transfer-Encoding: quoted-printable\n"
766"Generated-By: pygettext.py 1.3\n"
767'''
768
769#
770# messages.po, used for bug 17898
771#
772
773b'''
774# test file for http://bugs.python.org/issue17898
775msgid ""
776msgstr ""
777"Plural-Forms: nplurals=2; plural=(n != 1);\n"
778"#-#-#-#-#  messages.po (EdX Studio)  #-#-#-#-#\n"
779"Content-Type: text/plain; charset=UTF-8\n"
780'''
781