1import os
2import base64
3import shutil
4import gettext
5import unittest
6
7from test import test_support
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 = '''\
173hIElQAAAAAGAAAAHAAAAEwAAAALAAAAfAAAAAAAAACoAAAAFQAAAKkAAAAjAAAAvwAAAKEAAADj
18AAAABwAAAIUBAAALAAAAjQEAAEUBAACZAQAAFgAAAN8CAAAeAAAA9gIAAKEAAAAVAwAABQAAALcD
19AAAJAAAAvQMAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABQAAAAYAAAACAAAAAFJh
20eW1vbmQgTHV4dXJ5IFlhY2gtdABUaGVyZSBpcyAlcyBmaWxlAFRoZXJlIGFyZSAlcyBmaWxlcwBU
21aGlzIG1vZHVsZSBwcm92aWRlcyBpbnRlcm5hdGlvbmFsaXphdGlvbiBhbmQgbG9jYWxpemF0aW9u
22CnN1cHBvcnQgZm9yIHlvdXIgUHl0aG9uIHByb2dyYW1zIGJ5IHByb3ZpZGluZyBhbiBpbnRlcmZh
23Y2UgdG8gdGhlIEdOVQpnZXR0ZXh0IG1lc3NhZ2UgY2F0YWxvZyBsaWJyYXJ5LgBtdWxsdXNrAG51
24ZGdlIG51ZGdlAFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDAt
25MDgtMjkgMTI6MTktMDQ6MDAKTGFzdC1UcmFuc2xhdG9yOiBKLiBEYXZpZCBJYsOhw7FleiA8ai1k
26YXZpZEBub29zLmZyPgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpN
27SU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4
28NTktMQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBub25lCkdlbmVyYXRlZC1CeTogcHlnZXR0
29ZXh0LnB5IDEuMQpQbHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0
30d29iYmxlciBNYW5ncm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFo
31eXIgY2ViaXZxcmYgdmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVn
32IHNiZSBsYmhlIENsZ3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1
33ciBUQUgKdHJnZ3JrZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4AYmFjb24Ad2luayB3aW5rAA==
34'''
35
36UMO_DATA = '''\
373hIElQAAAAACAAAAHAAAACwAAAAFAAAAPAAAAAAAAABQAAAABAAAAFEAAAAPAQAAVgAAAAQAAABm
38AQAAAQAAAAIAAAAAAAAAAAAAAAAAAAAAYWLDngBQcm9qZWN0LUlkLVZlcnNpb246IDIuMApQTy1S
39ZXZpc2lvbi1EYXRlOiAyMDAzLTA0LTExIDEyOjQyLTA0MDAKTGFzdC1UcmFuc2xhdG9yOiBCYXJy
40eSBBLiBXQXJzYXcgPGJhcnJ5QHB5dGhvbi5vcmc+Ckxhbmd1YWdlLVRlYW06IFhYIDxweXRob24t
41ZGV2QHB5dGhvbi5vcmc+Ck1JTUUtVmVyc2lvbjogMS4wCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFp
42bjsgY2hhcnNldD11dGYtOApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiA3Yml0CkdlbmVyYXRl
43ZC1CeTogbWFudWFsbHkKAMKkeXoA
44'''
45
46MMO_DATA = '''\
473hIElQAAAAABAAAAHAAAACQAAAADAAAALAAAAAAAAAA4AAAAeAEAADkAAAABAAAAAAAAAAAAAAAA
48UHJvamVjdC1JZC1WZXJzaW9uOiBObyBQcm9qZWN0IDAuMApQT1QtQ3JlYXRpb24tRGF0ZTogV2Vk
49IERlYyAxMSAwNzo0NDoxNSAyMDAyClBPLVJldmlzaW9uLURhdGU6IDIwMDItMDgtMTQgMDE6MTg6
50NTgrMDA6MDAKTGFzdC1UcmFuc2xhdG9yOiBKb2huIERvZSA8amRvZUBleGFtcGxlLmNvbT4KSmFu
51ZSBGb29iYXIgPGpmb29iYXJAZXhhbXBsZS5jb20+Ckxhbmd1YWdlLVRlYW06IHh4IDx4eEBleGFt
52cGxlLmNvbT4KTUlNRS1WZXJzaW9uOiAxLjAKQ29udGVudC1UeXBlOiB0ZXh0L3BsYWluOyBjaGFy
53c2V0PWlzby04ODU5LTE1CkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IHF1b3RlZC1wcmludGFi
54bGUKR2VuZXJhdGVkLUJ5OiBweWdldHRleHQucHkgMS4zCgA=
55'''
56
57LOCALEDIR = os.path.join('xx', 'LC_MESSAGES')
58MOFILE = os.path.join(LOCALEDIR, 'gettext.mo')
59UMOFILE = os.path.join(LOCALEDIR, 'ugettext.mo')
60MMOFILE = os.path.join(LOCALEDIR, 'metadata.mo')
61
62
63class GettextBaseTest(unittest.TestCase):
64    def setUp(self):
65        if not os.path.isdir(LOCALEDIR):
66            os.makedirs(LOCALEDIR)
67        with open(MOFILE, 'wb') as fp:
68            fp.write(base64.decodestring(GNU_MO_DATA))
69        with open(UMOFILE, 'wb') as fp:
70            fp.write(base64.decodestring(UMO_DATA))
71        with open(MMOFILE, 'wb') as fp:
72            fp.write(base64.decodestring(MMO_DATA))
73
74        self.env = test_support.EnvironmentVarGuard()
75        self.env['LANGUAGE'] = 'xx'
76        gettext._translations.clear()
77
78    def tearDown(self):
79        self.env.__exit__()
80        del self.env
81        shutil.rmtree(os.path.split(LOCALEDIR)[0])
82
83
84class GettextTestCase1(GettextBaseTest):
85    def setUp(self):
86        GettextBaseTest.setUp(self)
87        self.localedir = os.curdir
88        self.mofile = MOFILE
89        gettext.install('gettext', self.localedir)
90
91    def test_some_translations(self):
92        eq = self.assertEqual
93        # test some translations
94        eq(_('albatross'), 'albatross')
95        eq(_(u'mullusk'), 'bacon')
96        eq(_(r'Raymond Luxury Yach-t'), 'Throatwobbler Mangrove')
97        eq(_(ur'nudge nudge'), 'wink wink')
98
99    def test_double_quotes(self):
100        eq = self.assertEqual
101        # double quotes
102        eq(_("albatross"), 'albatross')
103        eq(_(u"mullusk"), 'bacon')
104        eq(_(r"Raymond Luxury Yach-t"), 'Throatwobbler Mangrove')
105        eq(_(ur"nudge nudge"), 'wink wink')
106
107    def test_triple_single_quotes(self):
108        eq = self.assertEqual
109        # triple single quotes
110        eq(_('''albatross'''), 'albatross')
111        eq(_(u'''mullusk'''), 'bacon')
112        eq(_(r'''Raymond Luxury Yach-t'''), 'Throatwobbler Mangrove')
113        eq(_(ur'''nudge nudge'''), 'wink wink')
114
115    def test_triple_double_quotes(self):
116        eq = self.assertEqual
117        # triple double quotes
118        eq(_("""albatross"""), 'albatross')
119        eq(_(u"""mullusk"""), 'bacon')
120        eq(_(r"""Raymond Luxury Yach-t"""), 'Throatwobbler Mangrove')
121        eq(_(ur"""nudge nudge"""), 'wink wink')
122
123    def test_multiline_strings(self):
124        eq = self.assertEqual
125        # multiline strings
126        eq(_('''This module provides internationalization and localization
127support for your Python programs by providing an interface to the GNU
128gettext message catalog library.'''),
129           '''Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba
130fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH
131trggrkg zrffntr pngnybt yvoenel.''')
132
133    def test_the_alternative_interface(self):
134        eq = self.assertEqual
135        # test the alternative interface
136        with open(self.mofile, 'rb') as fp:
137            t = gettext.GNUTranslations(fp)
138        # Install the translation object
139        t.install()
140        eq(_('nudge nudge'), 'wink wink')
141        # Try unicode return type
142        t.install(unicode=True)
143        eq(_('mullusk'), 'bacon')
144        # Test installation of other methods
145        import __builtin__
146        t.install(unicode=True, names=["gettext", "lgettext"])
147        eq(_, t.ugettext)
148        eq(__builtin__.gettext, t.ugettext)
149        eq(lgettext, t.lgettext)
150        del __builtin__.gettext
151        del __builtin__.lgettext
152
153
154class GettextTestCase2(GettextBaseTest):
155    def setUp(self):
156        GettextBaseTest.setUp(self)
157        self.localedir = os.curdir
158        # Set up the bindings
159        gettext.bindtextdomain('gettext', self.localedir)
160        gettext.textdomain('gettext')
161        # For convenience
162        self._ = gettext.gettext
163
164    def test_bindtextdomain(self):
165        self.assertEqual(gettext.bindtextdomain('gettext'), self.localedir)
166
167    def test_textdomain(self):
168        self.assertEqual(gettext.textdomain(), 'gettext')
169
170    def test_some_translations(self):
171        eq = self.assertEqual
172        # test some translations
173        eq(self._('albatross'), 'albatross')
174        eq(self._(u'mullusk'), 'bacon')
175        eq(self._(r'Raymond Luxury Yach-t'), 'Throatwobbler Mangrove')
176        eq(self._(ur'nudge nudge'), 'wink wink')
177
178    def test_double_quotes(self):
179        eq = self.assertEqual
180        # double quotes
181        eq(self._("albatross"), 'albatross')
182        eq(self._(u"mullusk"), 'bacon')
183        eq(self._(r"Raymond Luxury Yach-t"), 'Throatwobbler Mangrove')
184        eq(self._(ur"nudge nudge"), 'wink wink')
185
186    def test_triple_single_quotes(self):
187        eq = self.assertEqual
188        # triple single quotes
189        eq(self._('''albatross'''), 'albatross')
190        eq(self._(u'''mullusk'''), 'bacon')
191        eq(self._(r'''Raymond Luxury Yach-t'''), 'Throatwobbler Mangrove')
192        eq(self._(ur'''nudge nudge'''), 'wink wink')
193
194    def test_triple_double_quotes(self):
195        eq = self.assertEqual
196        # triple double quotes
197        eq(self._("""albatross"""), 'albatross')
198        eq(self._(u"""mullusk"""), 'bacon')
199        eq(self._(r"""Raymond Luxury Yach-t"""), 'Throatwobbler Mangrove')
200        eq(self._(ur"""nudge nudge"""), 'wink wink')
201
202    def test_multiline_strings(self):
203        eq = self.assertEqual
204        # multiline strings
205        eq(self._('''This module provides internationalization and localization
206support for your Python programs by providing an interface to the GNU
207gettext message catalog library.'''),
208           '''Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba
209fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH
210trggrkg zrffntr pngnybt yvoenel.''')
211
212
213class PluralFormsTestCase(GettextBaseTest):
214    def setUp(self):
215        GettextBaseTest.setUp(self)
216        self.mofile = MOFILE
217
218    def test_plural_forms1(self):
219        eq = self.assertEqual
220        x = gettext.ngettext('There is %s file', 'There are %s files', 1)
221        eq(x, 'Hay %s fichero')
222        x = gettext.ngettext('There is %s file', 'There are %s files', 2)
223        eq(x, 'Hay %s ficheros')
224
225    def test_plural_forms2(self):
226        eq = self.assertEqual
227        with open(self.mofile, 'rb') as fp:
228            t = gettext.GNUTranslations(fp)
229        x = t.ngettext('There is %s file', 'There are %s files', 1)
230        eq(x, 'Hay %s fichero')
231        x = t.ngettext('There is %s file', 'There are %s files', 2)
232        eq(x, 'Hay %s ficheros')
233
234    def test_hu(self):
235        eq = self.assertEqual
236        f = gettext.c2py('0')
237        s = ''.join([ str(f(x)) for x in range(200) ])
238        eq(s, "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
239
240    def test_de(self):
241        eq = self.assertEqual
242        f = gettext.c2py('n != 1')
243        s = ''.join([ str(f(x)) for x in range(200) ])
244        eq(s, "10111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")
245
246    def test_fr(self):
247        eq = self.assertEqual
248        f = gettext.c2py('n>1')
249        s = ''.join([ str(f(x)) for x in range(200) ])
250        eq(s, "00111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")
251
252    def test_gd(self):
253        eq = self.assertEqual
254        f = gettext.c2py('n==1 ? 0 : n==2 ? 1 : 2')
255        s = ''.join([ str(f(x)) for x in range(200) ])
256        eq(s, "20122222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222")
257
258    def test_gd2(self):
259        eq = self.assertEqual
260        # Tests the combination of parentheses and "?:"
261        f = gettext.c2py('n==1 ? 0 : (n==2 ? 1 : 2)')
262        s = ''.join([ str(f(x)) for x in range(200) ])
263        eq(s, "20122222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222")
264
265    def test_lt(self):
266        eq = self.assertEqual
267        f = gettext.c2py('n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2')
268        s = ''.join([ str(f(x)) for x in range(200) ])
269        eq(s, "20111111112222222222201111111120111111112011111111201111111120111111112011111111201111111120111111112011111111222222222220111111112011111111201111111120111111112011111111201111111120111111112011111111")
270
271    def test_ru(self):
272        eq = self.assertEqual
273        f = gettext.c2py('n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2')
274        s = ''.join([ str(f(x)) for x in range(200) ])
275        eq(s, "20111222222222222222201112222220111222222011122222201112222220111222222011122222201112222220111222222011122222222222222220111222222011122222201112222220111222222011122222201112222220111222222011122222")
276
277    def test_pl(self):
278        eq = self.assertEqual
279        f = gettext.c2py('n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2')
280        s = ''.join([ str(f(x)) for x in range(200) ])
281        eq(s, "20111222222222222222221112222222111222222211122222221112222222111222222211122222221112222222111222222211122222222222222222111222222211122222221112222222111222222211122222221112222222111222222211122222")
282
283    def test_sl(self):
284        eq = self.assertEqual
285        f = gettext.c2py('n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3')
286        s = ''.join([ str(f(x)) for x in range(200) ])
287        eq(s, "30122333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333012233333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333")
288
289    def test_security(self):
290        raises = self.assertRaises
291        # Test for a dangerous expression
292        raises(ValueError, gettext.c2py, "os.chmod('/etc/passwd',0777)")
293
294
295class UnicodeTranslationsTest(GettextBaseTest):
296    def setUp(self):
297        GettextBaseTest.setUp(self)
298        with open(UMOFILE, 'rb') as fp:
299            self.t = gettext.GNUTranslations(fp)
300        self._ = self.t.ugettext
301
302    def test_unicode_msgid(self):
303        unless = self.assertTrue
304        unless(isinstance(self._(''), unicode))
305        unless(isinstance(self._(u''), unicode))
306
307    def test_unicode_msgstr(self):
308        eq = self.assertEqual
309        eq(self._(u'ab\xde'), u'\xa4yz')
310
311
312class WeirdMetadataTest(GettextBaseTest):
313    def setUp(self):
314        GettextBaseTest.setUp(self)
315        with open(MMOFILE, 'rb') as fp:
316            try:
317                self.t = gettext.GNUTranslations(fp)
318            except:
319                self.tearDown()
320                raise
321
322    def test_weird_metadata(self):
323        info = self.t.info()
324        self.assertEqual(info['last-translator'],
325           'John Doe <jdoe@example.com>\nJane Foobar <jfoobar@example.com>')
326
327
328class DummyGNUTranslations(gettext.GNUTranslations):
329    def foo(self):
330        return 'foo'
331
332
333class GettextCacheTestCase(GettextBaseTest):
334    def test_cache(self):
335        self.localedir = os.curdir
336        self.mofile = MOFILE
337
338        self.assertEqual(len(gettext._translations), 0)
339
340        t = gettext.translation('gettext', self.localedir)
341
342        self.assertEqual(len(gettext._translations), 1)
343
344        t = gettext.translation('gettext', self.localedir,
345                                class_=DummyGNUTranslations)
346
347        self.assertEqual(len(gettext._translations), 2)
348        self.assertEqual(t.__class__, DummyGNUTranslations)
349
350        # Calling it again doesn't add to the cache
351
352        t = gettext.translation('gettext', self.localedir,
353                                class_=DummyGNUTranslations)
354
355        self.assertEqual(len(gettext._translations), 2)
356        self.assertEqual(t.__class__, DummyGNUTranslations)
357
358
359def test_main():
360    test_support.run_unittest(__name__)
361
362if __name__ == '__main__':
363    test_main()
364
365
366# For reference, here's the .po file used to created the GNU_MO_DATA above.
367#
368# The original version was automatically generated from the sources with
369# pygettext. Later it was manually modified to add plural forms support.
370
371'''
372# Dummy translation for the Python test_gettext.py module.
373# Copyright (C) 2001 Python Software Foundation
374# Barry Warsaw <barry@python.org>, 2000.
375#
376msgid ""
377msgstr ""
378"Project-Id-Version: 2.0\n"
379"PO-Revision-Date: 2003-04-11 14:32-0400\n"
380"Last-Translator: J. David Ibanez <j-david@noos.fr>\n"
381"Language-Team: XX <python-dev@python.org>\n"
382"MIME-Version: 1.0\n"
383"Content-Type: text/plain; charset=iso-8859-1\n"
384"Content-Transfer-Encoding: 8bit\n"
385"Generated-By: pygettext.py 1.1\n"
386"Plural-Forms: nplurals=2; plural=n!=1;\n"
387
388#: test_gettext.py:19 test_gettext.py:25 test_gettext.py:31 test_gettext.py:37
389#: test_gettext.py:51 test_gettext.py:80 test_gettext.py:86 test_gettext.py:92
390#: test_gettext.py:98
391msgid "nudge nudge"
392msgstr "wink wink"
393
394#: test_gettext.py:16 test_gettext.py:22 test_gettext.py:28 test_gettext.py:34
395#: test_gettext.py:77 test_gettext.py:83 test_gettext.py:89 test_gettext.py:95
396msgid "albatross"
397msgstr ""
398
399#: test_gettext.py:18 test_gettext.py:24 test_gettext.py:30 test_gettext.py:36
400#: test_gettext.py:79 test_gettext.py:85 test_gettext.py:91 test_gettext.py:97
401msgid "Raymond Luxury Yach-t"
402msgstr "Throatwobbler Mangrove"
403
404#: test_gettext.py:17 test_gettext.py:23 test_gettext.py:29 test_gettext.py:35
405#: test_gettext.py:56 test_gettext.py:78 test_gettext.py:84 test_gettext.py:90
406#: test_gettext.py:96
407msgid "mullusk"
408msgstr "bacon"
409
410#: test_gettext.py:40 test_gettext.py:101
411msgid ""
412"This module provides internationalization and localization\n"
413"support for your Python programs by providing an interface to the GNU\n"
414"gettext message catalog library."
415msgstr ""
416"Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba\n"
417"fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH\n"
418"trggrkg zrffntr pngnybt yvoenel."
419
420# Manually added, as neither pygettext nor xgettext support plural forms
421# in Python.
422msgid "There is %s file"
423msgid_plural "There are %s files"
424msgstr[0] "Hay %s fichero"
425msgstr[1] "Hay %s ficheros"
426'''
427
428# Here's the second example po file example, used to generate the UMO_DATA
429# containing utf-8 encoded Unicode strings
430
431'''
432# Dummy translation for the Python test_gettext.py module.
433# Copyright (C) 2001 Python Software Foundation
434# Barry Warsaw <barry@python.org>, 2000.
435#
436msgid ""
437msgstr ""
438"Project-Id-Version: 2.0\n"
439"PO-Revision-Date: 2003-04-11 12:42-0400\n"
440"Last-Translator: Barry A. WArsaw <barry@python.org>\n"
441"Language-Team: XX <python-dev@python.org>\n"
442"MIME-Version: 1.0\n"
443"Content-Type: text/plain; charset=utf-8\n"
444"Content-Transfer-Encoding: 7bit\n"
445"Generated-By: manually\n"
446
447#: nofile:0
448msgid "ab\xc3\x9e"
449msgstr "\xc2\xa4yz"
450'''
451
452# Here's the third example po file, used to generate MMO_DATA
453
454'''
455msgid ""
456msgstr ""
457"Project-Id-Version: No Project 0.0\n"
458"POT-Creation-Date: Wed Dec 11 07:44:15 2002\n"
459"PO-Revision-Date: 2002-08-14 01:18:58+00:00\n"
460"Last-Translator: John Doe <jdoe@example.com>\n"
461"Jane Foobar <jfoobar@example.com>\n"
462"Language-Team: xx <xx@example.com>\n"
463"MIME-Version: 1.0\n"
464"Content-Type: text/plain; charset=iso-8859-15\n"
465"Content-Transfer-Encoding: quoted-printable\n"
466"Generated-By: pygettext.py 1.3\n"
467'''
468