test_gettext.py revision cdb2c601db078e0af2fcca49341a7d17d603e500
1import os
2import base64
3import shutil
4import gettext
5import unittest
6
7from test import 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 = b'''\
173hIElQAAAAAGAAAAHAAAAEwAAAALAAAAfAAAAAAAAACoAAAAFQAAAKkAAAAjAAAAvwAAAKEAAADj
18AAAABwAAAIUBAAALAAAAjQEAAEUBAACZAQAAFgAAAN8CAAAeAAAA9gIAAKEAAAAVAwAABQAAALcD
19AAAJAAAAvQMAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABQAAAAYAAAACAAAAAFJh
20eW1vbmQgTHV4dXJ5IFlhY2gtdABUaGVyZSBpcyAlcyBmaWxlAFRoZXJlIGFyZSAlcyBmaWxlcwBU
21aGlzIG1vZHVsZSBwcm92aWRlcyBpbnRlcm5hdGlvbmFsaXphdGlvbiBhbmQgbG9jYWxpemF0aW9u
22CnN1cHBvcnQgZm9yIHlvdXIgUHl0aG9uIHByb2dyYW1zIGJ5IHByb3ZpZGluZyBhbiBpbnRlcmZh
23Y2UgdG8gdGhlIEdOVQpnZXR0ZXh0IG1lc3NhZ2UgY2F0YWxvZyBsaWJyYXJ5LgBtdWxsdXNrAG51
24ZGdlIG51ZGdlAFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDAt
25MDgtMjkgMTI6MTktMDQ6MDAKTGFzdC1UcmFuc2xhdG9yOiBKLiBEYXZpZCBJYsOhw7FleiA8ai1k
26YXZpZEBub29zLmZyPgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpN
27SU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4
28NTktMQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBub25lCkdlbmVyYXRlZC1CeTogcHlnZXR0
29ZXh0LnB5IDEuMQpQbHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0
30d29iYmxlciBNYW5ncm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFo
31eXIgY2ViaXZxcmYgdmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVn
32IHNiZSBsYmhlIENsZ3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1
33ciBUQUgKdHJnZ3JrZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4AYmFjb24Ad2luayB3aW5rAA==
34'''
35
36UMO_DATA = b'''\
373hIElQAAAAACAAAAHAAAACwAAAAFAAAAPAAAAAAAAABQAAAABAAAAFEAAAAPAQAAVgAAAAQAAABm
38AQAAAQAAAAIAAAAAAAAAAAAAAAAAAAAAYWLDngBQcm9qZWN0LUlkLVZlcnNpb246IDIuMApQTy1S
39ZXZpc2lvbi1EYXRlOiAyMDAzLTA0LTExIDEyOjQyLTA0MDAKTGFzdC1UcmFuc2xhdG9yOiBCYXJy
40eSBBLiBXQXJzYXcgPGJhcnJ5QHB5dGhvbi5vcmc+Ckxhbmd1YWdlLVRlYW06IFhYIDxweXRob24t
41ZGV2QHB5dGhvbi5vcmc+Ck1JTUUtVmVyc2lvbjogMS4wCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFp
42bjsgY2hhcnNldD11dGYtOApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiA3Yml0CkdlbmVyYXRl
43ZC1CeTogbWFudWFsbHkKAMKkeXoA
44'''
45
46MMO_DATA = b'''\
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.decodebytes(GNU_MO_DATA))
69        with open(UMOFILE, 'wb') as fp:
70            fp.write(base64.decodebytes(UMO_DATA))
71        with open(MMOFILE, 'wb') as fp:
72            fp.write(base64.decodebytes(MMO_DATA))
73        self.env = support.EnvironmentVarGuard()
74        self.env['LANGUAGE'] = 'xx'
75        gettext._translations.clear()
76
77    def tearDown(self):
78        self.env.__exit__()
79        del self.env
80        support.rmtree(os.path.split(LOCALEDIR)[0])
81
82
83class GettextTestCase1(GettextBaseTest):
84    def setUp(self):
85        GettextBaseTest.setUp(self)
86        self.localedir = os.curdir
87        self.mofile = MOFILE
88        gettext.install('gettext', self.localedir)
89
90    def test_some_translations(self):
91        eq = self.assertEqual
92        # test some translations
93        eq(_('albatross'), 'albatross')
94        eq(_('mullusk'), 'bacon')
95        eq(_(r'Raymond Luxury Yach-t'), 'Throatwobbler Mangrove')
96        eq(_(r'nudge nudge'), 'wink wink')
97
98    def test_double_quotes(self):
99        eq = self.assertEqual
100        # double quotes
101        eq(_("albatross"), 'albatross')
102        eq(_("mullusk"), 'bacon')
103        eq(_(r"Raymond Luxury Yach-t"), 'Throatwobbler Mangrove')
104        eq(_(r"nudge nudge"), 'wink wink')
105
106    def test_triple_single_quotes(self):
107        eq = self.assertEqual
108        # triple single quotes
109        eq(_('''albatross'''), 'albatross')
110        eq(_('''mullusk'''), 'bacon')
111        eq(_(r'''Raymond Luxury Yach-t'''), 'Throatwobbler Mangrove')
112        eq(_(r'''nudge nudge'''), 'wink wink')
113
114    def test_triple_double_quotes(self):
115        eq = self.assertEqual
116        # triple double quotes
117        eq(_("""albatross"""), 'albatross')
118        eq(_("""mullusk"""), 'bacon')
119        eq(_(r"""Raymond Luxury Yach-t"""), 'Throatwobbler Mangrove')
120        eq(_(r"""nudge nudge"""), 'wink wink')
121
122    def test_multiline_strings(self):
123        eq = self.assertEqual
124        # multiline strings
125        eq(_('''This module provides internationalization and localization
126support for your Python programs by providing an interface to the GNU
127gettext message catalog library.'''),
128           '''Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba
129fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH
130trggrkg zrffntr pngnybt yvoenel.''')
131
132    def test_the_alternative_interface(self):
133        eq = self.assertEqual
134        # test the alternative interface
135        with open(self.mofile, 'rb') as fp:
136            t = gettext.GNUTranslations(fp)
137        # Install the translation object
138        t.install()
139        eq(_('nudge nudge'), 'wink wink')
140        # Try unicode return type
141        t.install()
142        eq(_('mullusk'), 'bacon')
143        # Test installation of other methods
144        import builtins
145        t.install(names=["gettext", "lgettext"])
146        eq(_, t.gettext)
147        eq(builtins.gettext, t.gettext)
148        eq(lgettext, t.lgettext)
149        del builtins.gettext
150        del builtins.lgettext
151
152
153class GettextTestCase2(GettextBaseTest):
154    def setUp(self):
155        GettextBaseTest.setUp(self)
156        self.localedir = os.curdir
157        # Set up the bindings
158        gettext.bindtextdomain('gettext', self.localedir)
159        gettext.textdomain('gettext')
160        # For convenience
161        self._ = gettext.gettext
162
163    def test_bindtextdomain(self):
164        self.assertEqual(gettext.bindtextdomain('gettext'), self.localedir)
165
166    def test_textdomain(self):
167        self.assertEqual(gettext.textdomain(), 'gettext')
168
169    def test_some_translations(self):
170        eq = self.assertEqual
171        # test some translations
172        eq(self._('albatross'), 'albatross')
173        eq(self._('mullusk'), 'bacon')
174        eq(self._(r'Raymond Luxury Yach-t'), 'Throatwobbler Mangrove')
175        eq(self._(r'nudge nudge'), 'wink wink')
176
177    def test_double_quotes(self):
178        eq = self.assertEqual
179        # double quotes
180        eq(self._("albatross"), 'albatross')
181        eq(self._("mullusk"), 'bacon')
182        eq(self._(r"Raymond Luxury Yach-t"), 'Throatwobbler Mangrove')
183        eq(self._(r"nudge nudge"), 'wink wink')
184
185    def test_triple_single_quotes(self):
186        eq = self.assertEqual
187        # triple single quotes
188        eq(self._('''albatross'''), 'albatross')
189        eq(self._('''mullusk'''), 'bacon')
190        eq(self._(r'''Raymond Luxury Yach-t'''), 'Throatwobbler Mangrove')
191        eq(self._(r'''nudge nudge'''), 'wink wink')
192
193    def test_triple_double_quotes(self):
194        eq = self.assertEqual
195        # triple double quotes
196        eq(self._("""albatross"""), 'albatross')
197        eq(self._("""mullusk"""), 'bacon')
198        eq(self._(r"""Raymond Luxury Yach-t"""), 'Throatwobbler Mangrove')
199        eq(self._(r"""nudge nudge"""), 'wink wink')
200
201    def test_multiline_strings(self):
202        eq = self.assertEqual
203        # multiline strings
204        eq(self._('''This module provides internationalization and localization
205support for your Python programs by providing an interface to the GNU
206gettext message catalog library.'''),
207           '''Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba
208fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH
209trggrkg zrffntr pngnybt yvoenel.''')
210
211
212class PluralFormsTestCase(GettextBaseTest):
213    def setUp(self):
214        GettextBaseTest.setUp(self)
215        self.mofile = MOFILE
216
217    def test_plural_forms1(self):
218        eq = self.assertEqual
219        x = gettext.ngettext('There is %s file', 'There are %s files', 1)
220        eq(x, 'Hay %s fichero')
221        x = gettext.ngettext('There is %s file', 'There are %s files', 2)
222        eq(x, 'Hay %s ficheros')
223
224    def test_plural_forms2(self):
225        eq = self.assertEqual
226        with open(self.mofile, 'rb') as fp:
227            t = gettext.GNUTranslations(fp)
228        x = t.ngettext('There is %s file', 'There are %s files', 1)
229        eq(x, 'Hay %s fichero')
230        x = t.ngettext('There is %s file', 'There are %s files', 2)
231        eq(x, 'Hay %s ficheros')
232
233    def test_hu(self):
234        eq = self.assertEqual
235        f = gettext.c2py('0')
236        s = ''.join([ str(f(x)) for x in range(200) ])
237        eq(s, "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
238
239    def test_de(self):
240        eq = self.assertEqual
241        f = gettext.c2py('n != 1')
242        s = ''.join([ str(f(x)) for x in range(200) ])
243        eq(s, "10111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")
244
245    def test_fr(self):
246        eq = self.assertEqual
247        f = gettext.c2py('n>1')
248        s = ''.join([ str(f(x)) for x in range(200) ])
249        eq(s, "00111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")
250
251    def test_gd(self):
252        eq = self.assertEqual
253        f = gettext.c2py('n==1 ? 0 : n==2 ? 1 : 2')
254        s = ''.join([ str(f(x)) for x in range(200) ])
255        eq(s, "20122222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222")
256
257    def test_gd2(self):
258        eq = self.assertEqual
259        # Tests the combination of parentheses and "?:"
260        f = gettext.c2py('n==1 ? 0 : (n==2 ? 1 : 2)')
261        s = ''.join([ str(f(x)) for x in range(200) ])
262        eq(s, "20122222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222")
263
264    def test_lt(self):
265        eq = self.assertEqual
266        f = gettext.c2py('n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2')
267        s = ''.join([ str(f(x)) for x in range(200) ])
268        eq(s, "20111111112222222222201111111120111111112011111111201111111120111111112011111111201111111120111111112011111111222222222220111111112011111111201111111120111111112011111111201111111120111111112011111111")
269
270    def test_ru(self):
271        eq = self.assertEqual
272        f = gettext.c2py('n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2')
273        s = ''.join([ str(f(x)) for x in range(200) ])
274        eq(s, "20111222222222222222201112222220111222222011122222201112222220111222222011122222201112222220111222222011122222222222222220111222222011122222201112222220111222222011122222201112222220111222222011122222")
275
276    def test_pl(self):
277        eq = self.assertEqual
278        f = gettext.c2py('n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2')
279        s = ''.join([ str(f(x)) for x in range(200) ])
280        eq(s, "20111222222222222222221112222222111222222211122222221112222222111222222211122222221112222222111222222211122222222222222222111222222211122222221112222222111222222211122222221112222222111222222211122222")
281
282    def test_sl(self):
283        eq = self.assertEqual
284        f = gettext.c2py('n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3')
285        s = ''.join([ str(f(x)) for x in range(200) ])
286        eq(s, "30122333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333012233333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333")
287
288    def test_security(self):
289        raises = self.assertRaises
290        # Test for a dangerous expression
291        raises(ValueError, gettext.c2py, "os.chmod('/etc/passwd',0777)")
292
293
294class UnicodeTranslationsTest(GettextBaseTest):
295    def setUp(self):
296        GettextBaseTest.setUp(self)
297        with open(UMOFILE, 'rb') as fp:
298            self.t = gettext.GNUTranslations(fp)
299        self._ = self.t.gettext
300
301    def test_unicode_msgid(self):
302        unless = self.assertTrue
303        unless(isinstance(self._(''), str))
304        unless(isinstance(self._(''), str))
305
306    def test_unicode_msgstr(self):
307        eq = self.assertEqual
308        eq(self._('ab\xde'), '\xa4yz')
309
310
311class WeirdMetadataTest(GettextBaseTest):
312    def setUp(self):
313        GettextBaseTest.setUp(self)
314        with open(MMOFILE, 'rb') as fp:
315            try:
316                self.t = gettext.GNUTranslations(fp)
317            except:
318                self.tearDown()
319                raise
320
321    def test_weird_metadata(self):
322        info = self.t.info()
323        self.assertEqual(len(info), 9)
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    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