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