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