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