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