Home | History | Annotate | Download | only in test
      1 import os
      2 import base64
      3 import gettext
      4 import unittest
      5 
      6 from 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 
     15 GNU_MO_DATA = b'''\
     16 3hIElQAAAAAGAAAAHAAAAEwAAAALAAAAfAAAAAAAAACoAAAAFQAAAKkAAAAjAAAAvwAAAKEAAADj
     17 AAAABwAAAIUBAAALAAAAjQEAAEUBAACZAQAAFgAAAN8CAAAeAAAA9gIAAKEAAAAVAwAABQAAALcD
     18 AAAJAAAAvQMAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABQAAAAYAAAACAAAAAFJh
     19 eW1vbmQgTHV4dXJ5IFlhY2gtdABUaGVyZSBpcyAlcyBmaWxlAFRoZXJlIGFyZSAlcyBmaWxlcwBU
     20 aGlzIG1vZHVsZSBwcm92aWRlcyBpbnRlcm5hdGlvbmFsaXphdGlvbiBhbmQgbG9jYWxpemF0aW9u
     21 CnN1cHBvcnQgZm9yIHlvdXIgUHl0aG9uIHByb2dyYW1zIGJ5IHByb3ZpZGluZyBhbiBpbnRlcmZh
     22 Y2UgdG8gdGhlIEdOVQpnZXR0ZXh0IG1lc3NhZ2UgY2F0YWxvZyBsaWJyYXJ5LgBtdWxsdXNrAG51
     23 ZGdlIG51ZGdlAFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDAt
     24 MDgtMjkgMTI6MTktMDQ6MDAKTGFzdC1UcmFuc2xhdG9yOiBKLiBEYXZpZCBJYsOhw7FleiA8ai1k
     25 YXZpZEBub29zLmZyPgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpN
     26 SU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4
     27 NTktMQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBub25lCkdlbmVyYXRlZC1CeTogcHlnZXR0
     28 ZXh0LnB5IDEuMQpQbHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0
     29 d29iYmxlciBNYW5ncm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFo
     30 eXIgY2ViaXZxcmYgdmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVn
     31 IHNiZSBsYmhlIENsZ3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1
     32 ciBUQUgKdHJnZ3JrZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4AYmFjb24Ad2luayB3aW5rAA==
     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 
     39 GNU_MO_DATA_BAD_MAJOR_VERSION = b'''\
     40 3hIElQAABQAGAAAAHAAAAEwAAAALAAAAfAAAAAAAAACoAAAAFQAAAKkAAAAjAAAAvwAAAKEAAADj
     41 AAAABwAAAIUBAAALAAAAjQEAAEUBAACZAQAAFgAAAN8CAAAeAAAA9gIAAKEAAAAVAwAABQAAALcD
     42 AAAJAAAAvQMAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABQAAAAYAAAACAAAAAFJh
     43 eW1vbmQgTHV4dXJ5IFlhY2gtdABUaGVyZSBpcyAlcyBmaWxlAFRoZXJlIGFyZSAlcyBmaWxlcwBU
     44 aGlzIG1vZHVsZSBwcm92aWRlcyBpbnRlcm5hdGlvbmFsaXphdGlvbiBhbmQgbG9jYWxpemF0aW9u
     45 CnN1cHBvcnQgZm9yIHlvdXIgUHl0aG9uIHByb2dyYW1zIGJ5IHByb3ZpZGluZyBhbiBpbnRlcmZh
     46 Y2UgdG8gdGhlIEdOVQpnZXR0ZXh0IG1lc3NhZ2UgY2F0YWxvZyBsaWJyYXJ5LgBtdWxsdXNrAG51
     47 ZGdlIG51ZGdlAFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDAt
     48 MDgtMjkgMTI6MTktMDQ6MDAKTGFzdC1UcmFuc2xhdG9yOiBKLiBEYXZpZCBJYsOhw7FleiA8ai1k
     49 YXZpZEBub29zLmZyPgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpN
     50 SU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4
     51 NTktMQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBub25lCkdlbmVyYXRlZC1CeTogcHlnZXR0
     52 ZXh0LnB5IDEuMQpQbHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0
     53 d29iYmxlciBNYW5ncm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFo
     54 eXIgY2ViaXZxcmYgdmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVn
     55 IHNiZSBsYmhlIENsZ3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1
     56 ciBUQUgKdHJnZ3JrZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4AYmFjb24Ad2luayB3aW5rAA==
     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 
     63 GNU_MO_DATA_BAD_MINOR_VERSION = b'''\
     64 3hIElQcAAAAGAAAAHAAAAEwAAAALAAAAfAAAAAAAAACoAAAAFQAAAKkAAAAjAAAAvwAAAKEAAADj
     65 AAAABwAAAIUBAAALAAAAjQEAAEUBAACZAQAAFgAAAN8CAAAeAAAA9gIAAKEAAAAVAwAABQAAALcD
     66 AAAJAAAAvQMAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABQAAAAYAAAACAAAAAFJh
     67 eW1vbmQgTHV4dXJ5IFlhY2gtdABUaGVyZSBpcyAlcyBmaWxlAFRoZXJlIGFyZSAlcyBmaWxlcwBU
     68 aGlzIG1vZHVsZSBwcm92aWRlcyBpbnRlcm5hdGlvbmFsaXphdGlvbiBhbmQgbG9jYWxpemF0aW9u
     69 CnN1cHBvcnQgZm9yIHlvdXIgUHl0aG9uIHByb2dyYW1zIGJ5IHByb3ZpZGluZyBhbiBpbnRlcmZh
     70 Y2UgdG8gdGhlIEdOVQpnZXR0ZXh0IG1lc3NhZ2UgY2F0YWxvZyBsaWJyYXJ5LgBtdWxsdXNrAG51
     71 ZGdlIG51ZGdlAFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDAt
     72 MDgtMjkgMTI6MTktMDQ6MDAKTGFzdC1UcmFuc2xhdG9yOiBKLiBEYXZpZCBJYsOhw7FleiA8ai1k
     73 YXZpZEBub29zLmZyPgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpN
     74 SU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4
     75 NTktMQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBub25lCkdlbmVyYXRlZC1CeTogcHlnZXR0
     76 ZXh0LnB5IDEuMQpQbHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0
     77 d29iYmxlciBNYW5ncm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFo
     78 eXIgY2ViaXZxcmYgdmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVn
     79 IHNiZSBsYmhlIENsZ3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1
     80 ciBUQUgKdHJnZ3JrZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4AYmFjb24Ad2luayB3aW5rAA==
     81 '''
     82 
     83 
     84 UMO_DATA = b'''\
     85 3hIElQAAAAACAAAAHAAAACwAAAAFAAAAPAAAAAAAAABQAAAABAAAAFEAAAAPAQAAVgAAAAQAAABm
     86 AQAAAQAAAAIAAAAAAAAAAAAAAAAAAAAAYWLDngBQcm9qZWN0LUlkLVZlcnNpb246IDIuMApQTy1S
     87 ZXZpc2lvbi1EYXRlOiAyMDAzLTA0LTExIDEyOjQyLTA0MDAKTGFzdC1UcmFuc2xhdG9yOiBCYXJy
     88 eSBBLiBXQXJzYXcgPGJhcnJ5QHB5dGhvbi5vcmc+Ckxhbmd1YWdlLVRlYW06IFhYIDxweXRob24t
     89 ZGV2QHB5dGhvbi5vcmc+Ck1JTUUtVmVyc2lvbjogMS4wCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFp
     90 bjsgY2hhcnNldD11dGYtOApDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiA3Yml0CkdlbmVyYXRl
     91 ZC1CeTogbWFudWFsbHkKAMKkeXoA
     92 '''
     93 
     94 MMO_DATA = b'''\
     95 3hIElQAAAAABAAAAHAAAACQAAAADAAAALAAAAAAAAAA4AAAAeAEAADkAAAABAAAAAAAAAAAAAAAA
     96 UHJvamVjdC1JZC1WZXJzaW9uOiBObyBQcm9qZWN0IDAuMApQT1QtQ3JlYXRpb24tRGF0ZTogV2Vk
     97 IERlYyAxMSAwNzo0NDoxNSAyMDAyClBPLVJldmlzaW9uLURhdGU6IDIwMDItMDgtMTQgMDE6MTg6
     98 NTgrMDA6MDAKTGFzdC1UcmFuc2xhdG9yOiBKb2huIERvZSA8amRvZUBleGFtcGxlLmNvbT4KSmFu
     99 ZSBGb29iYXIgPGpmb29iYXJAZXhhbXBsZS5jb20+Ckxhbmd1YWdlLVRlYW06IHh4IDx4eEBleGFt
    100 cGxlLmNvbT4KTUlNRS1WZXJzaW9uOiAxLjAKQ29udGVudC1UeXBlOiB0ZXh0L3BsYWluOyBjaGFy
    101 c2V0PWlzby04ODU5LTE1CkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IHF1b3RlZC1wcmludGFi
    102 bGUKR2VuZXJhdGVkLUJ5OiBweWdldHRleHQucHkgMS4zCgA=
    103 '''
    104 
    105 LOCALEDIR = os.path.join('xx', 'LC_MESSAGES')
    106 MOFILE = os.path.join(LOCALEDIR, 'gettext.mo')
    107 MOFILE_BAD_MAJOR_VERSION = os.path.join(LOCALEDIR, 'gettext_bad_major_version.mo')
    108 MOFILE_BAD_MINOR_VERSION = os.path.join(LOCALEDIR, 'gettext_bad_minor_version.mo')
    109 UMOFILE = os.path.join(LOCALEDIR, 'ugettext.mo')
    110 MMOFILE = os.path.join(LOCALEDIR, 'metadata.mo')
    111 
    112 
    113 class 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 
    136 GNU_MO_DATA_ISSUE_17898 = b'''\
    137 3hIElQAAAAABAAAAHAAAACQAAAAAAAAAAAAAAAAAAAAsAAAAggAAAC0AAAAAUGx1cmFsLUZvcm1z
    138 OiBucGx1cmFscz0yOyBwbHVyYWw9KG4gIT0gMSk7CiMtIy0jLSMtIyAgbWVzc2FnZXMucG8gKEVk
    139 WCBTdHVkaW8pICAjLSMtIy0jLSMKQ29udGVudC1UeXBlOiB0ZXh0L3BsYWluOyBjaGFyc2V0PVVU
    140 Ri04CgA=
    141 '''
    142 
    143 class 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
    186 support for your Python programs by providing an interface to the GNU
    187 gettext message catalog library.'''),
    188            '''Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba
    189 fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH
    190 trggrkg 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 
    213 class 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
    280 support for your Python programs by providing an interface to the GNU
    281 gettext message catalog library.'''),
    282            '''Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba
    283 fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH
    284 trggrkg zrffntr pngnybt yvoenel.''')
    285 
    286 
    287 class 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 
    455 class 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 
    464 class 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 
    481 class 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 (at] example.com>\nJane Foobar <jfoobar (at] example.com>')
    496 
    497 
    498 class DummyGNUTranslations(gettext.GNUTranslations):
    499     def foo(self):
    500         return 'foo'
    501 
    502 
    503 class 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 
    529 class MiscTestCase(unittest.TestCase):
    530     def test__all__(self):
    531         blacklist = {'c2py', 'ENOENT'}
    532         support.check__all__(self, gettext, blacklist=blacklist)
    533 
    534 
    535 def test_main():
    536     support.run_unittest(__name__)
    537 
    538 if __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 (at] python.org>, 2000.
    551 #
    552 msgid ""
    553 msgstr ""
    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 (at] noos.fr>\n"
    557 "Language-Team: XX <python-dev (at] 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
    567 msgid "nudge nudge"
    568 msgstr "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
    572 msgid "albatross"
    573 msgstr ""
    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
    577 msgid "Raymond Luxury Yach-t"
    578 msgstr "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
    583 msgid "mullusk"
    584 msgstr "bacon"
    585 
    586 #: test_gettext.py:40 test_gettext.py:101
    587 msgid ""
    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."
    591 msgstr ""
    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.
    598 msgid "There is %s file"
    599 msgid_plural "There are %s files"
    600 msgstr[0] "Hay %s fichero"
    601 msgstr[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 (at] python.org>, 2000.
    611 #
    612 msgid ""
    613 msgstr ""
    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 (at] python.org>\n"
    617 "Language-Team: XX <python-dev (at] 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
    624 msgid "ab\xc3\x9e"
    625 msgstr "\xc2\xa4yz"
    626 '''
    627 
    628 # Here's the third example po file, used to generate MMO_DATA
    629 
    630 '''
    631 msgid ""
    632 msgstr ""
    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 (at] example.com>\n"
    637 "Jane Foobar <jfoobar (at] example.com>\n"
    638 "Language-Team: xx <xx (at] 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
    651 msgid ""
    652 msgstr ""
    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