(root)/
Python-3.12.0/
Lib/
test/
test_gettext.py
       1  import os
       2  import base64
       3  import gettext
       4  import unittest
       5  
       6  from test import support
       7  from test.support import os_helper
       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  
      16  GNU_MO_DATA = b'''\
      17  3hIElQAAAAAJAAAAHAAAAGQAAAAAAAAArAAAAAAAAACsAAAAFQAAAK0AAAAjAAAAwwAAAKEAAADn
      18  AAAAMAAAAIkBAAAHAAAAugEAABYAAADCAQAAHAAAANkBAAALAAAA9gEAAEIBAAACAgAAFgAAAEUD
      19  AAAeAAAAXAMAAKEAAAB7AwAAMgAAAB0EAAAFAAAAUAQAABsAAABWBAAAIQAAAHIEAAAJAAAAlAQA
      20  AABSYXltb25kIEx1eHVyeSBZYWNoLXQAVGhlcmUgaXMgJXMgZmlsZQBUaGVyZSBhcmUgJXMgZmls
      21  ZXMAVGhpcyBtb2R1bGUgcHJvdmlkZXMgaW50ZXJuYXRpb25hbGl6YXRpb24gYW5kIGxvY2FsaXph
      22  dGlvbgpzdXBwb3J0IGZvciB5b3VyIFB5dGhvbiBwcm9ncmFtcyBieSBwcm92aWRpbmcgYW4gaW50
      23  ZXJmYWNlIHRvIHRoZSBHTlUKZ2V0dGV4dCBtZXNzYWdlIGNhdGFsb2cgbGlicmFyeS4AV2l0aCBj
      24  b250ZXh0BFRoZXJlIGlzICVzIGZpbGUAVGhlcmUgYXJlICVzIGZpbGVzAG11bGx1c2sAbXkgY29u
      25  dGV4dARudWRnZSBudWRnZQBteSBvdGhlciBjb250ZXh0BG51ZGdlIG51ZGdlAG51ZGdlIG51ZGdl
      26  AFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDMtMDQtMTEgMTQ6
      27  MzItMDQwMApMYXN0LVRyYW5zbGF0b3I6IEouIERhdmlkIEliYW5leiA8ai1kYXZpZEBub29zLmZy
      28  PgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpNSU1FLVZlcnNpb246
      29  IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4NTktMQpDb250ZW50
      30  LVRyYW5zZmVyLUVuY29kaW5nOiA4Yml0CkdlbmVyYXRlZC1CeTogcHlnZXR0ZXh0LnB5IDEuMQpQ
      31  bHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0d29iYmxlciBNYW5n
      32  cm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFoeXIgY2ViaXZxcmYg
      33  dmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVnIHNiZSBsYmhlIENs
      34  Z3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1ciBUQUgKdHJnZ3Jr
      35  ZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4ASGF5ICVzIGZpY2hlcm8gKGNvbnRleHQpAEhheSAl
      36  cyBmaWNoZXJvcyAoY29udGV4dCkAYmFjb24Ad2luayB3aW5rIChpbiAibXkgY29udGV4dCIpAHdp
      37  bmsgd2luayAoaW4gIm15IG90aGVyIGNvbnRleHQiKQB3aW5rIHdpbmsA
      38  '''
      39  
      40  # This data contains an invalid major version number (5)
      41  # An unexpected major version number should be treated as an error when
      42  # parsing a .mo file
      43  
      44  GNU_MO_DATA_BAD_MAJOR_VERSION = b'''\
      45  3hIElQAABQAGAAAAHAAAAEwAAAALAAAAfAAAAAAAAACoAAAAFQAAAKkAAAAjAAAAvwAAAKEAAADj
      46  AAAABwAAAIUBAAALAAAAjQEAAEUBAACZAQAAFgAAAN8CAAAeAAAA9gIAAKEAAAAVAwAABQAAALcD
      47  AAAJAAAAvQMAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABQAAAAYAAAACAAAAAFJh
      48  eW1vbmQgTHV4dXJ5IFlhY2gtdABUaGVyZSBpcyAlcyBmaWxlAFRoZXJlIGFyZSAlcyBmaWxlcwBU
      49  aGlzIG1vZHVsZSBwcm92aWRlcyBpbnRlcm5hdGlvbmFsaXphdGlvbiBhbmQgbG9jYWxpemF0aW9u
      50  CnN1cHBvcnQgZm9yIHlvdXIgUHl0aG9uIHByb2dyYW1zIGJ5IHByb3ZpZGluZyBhbiBpbnRlcmZh
      51  Y2UgdG8gdGhlIEdOVQpnZXR0ZXh0IG1lc3NhZ2UgY2F0YWxvZyBsaWJyYXJ5LgBtdWxsdXNrAG51
      52  ZGdlIG51ZGdlAFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDAt
      53  MDgtMjkgMTI6MTktMDQ6MDAKTGFzdC1UcmFuc2xhdG9yOiBKLiBEYXZpZCBJYsOhw7FleiA8ai1k
      54  YXZpZEBub29zLmZyPgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpN
      55  SU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4
      56  NTktMQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBub25lCkdlbmVyYXRlZC1CeTogcHlnZXR0
      57  ZXh0LnB5IDEuMQpQbHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0
      58  d29iYmxlciBNYW5ncm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFo
      59  eXIgY2ViaXZxcmYgdmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVn
      60  IHNiZSBsYmhlIENsZ3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1
      61  ciBUQUgKdHJnZ3JrZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4AYmFjb24Ad2luayB3aW5rAA==
      62  '''
      63  
      64  # This data contains an invalid minor version number (7)
      65  # An unexpected minor version number only indicates that some of the file's
      66  # contents may not be able to be read. It does not indicate an error.
      67  
      68  GNU_MO_DATA_BAD_MINOR_VERSION = b'''\
      69  3hIElQcAAAAGAAAAHAAAAEwAAAALAAAAfAAAAAAAAACoAAAAFQAAAKkAAAAjAAAAvwAAAKEAAADj
      70  AAAABwAAAIUBAAALAAAAjQEAAEUBAACZAQAAFgAAAN8CAAAeAAAA9gIAAKEAAAAVAwAABQAAALcD
      71  AAAJAAAAvQMAAAEAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABQAAAAYAAAACAAAAAFJh
      72  eW1vbmQgTHV4dXJ5IFlhY2gtdABUaGVyZSBpcyAlcyBmaWxlAFRoZXJlIGFyZSAlcyBmaWxlcwBU
      73  aGlzIG1vZHVsZSBwcm92aWRlcyBpbnRlcm5hdGlvbmFsaXphdGlvbiBhbmQgbG9jYWxpemF0aW9u
      74  CnN1cHBvcnQgZm9yIHlvdXIgUHl0aG9uIHByb2dyYW1zIGJ5IHByb3ZpZGluZyBhbiBpbnRlcmZh
      75  Y2UgdG8gdGhlIEdOVQpnZXR0ZXh0IG1lc3NhZ2UgY2F0YWxvZyBsaWJyYXJ5LgBtdWxsdXNrAG51
      76  ZGdlIG51ZGdlAFByb2plY3QtSWQtVmVyc2lvbjogMi4wClBPLVJldmlzaW9uLURhdGU6IDIwMDAt
      77  MDgtMjkgMTI6MTktMDQ6MDAKTGFzdC1UcmFuc2xhdG9yOiBKLiBEYXZpZCBJYsOhw7FleiA8ai1k
      78  YXZpZEBub29zLmZyPgpMYW5ndWFnZS1UZWFtOiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpN
      79  SU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9aXNvLTg4
      80  NTktMQpDb250ZW50LVRyYW5zZmVyLUVuY29kaW5nOiBub25lCkdlbmVyYXRlZC1CeTogcHlnZXR0
      81  ZXh0LnB5IDEuMQpQbHVyYWwtRm9ybXM6IG5wbHVyYWxzPTI7IHBsdXJhbD1uIT0xOwoAVGhyb2F0
      82  d29iYmxlciBNYW5ncm92ZQBIYXkgJXMgZmljaGVybwBIYXkgJXMgZmljaGVyb3MAR3V2ZiB6YnFo
      83  eXIgY2ViaXZxcmYgdmFncmVhbmd2YmFueXZtbmd2YmEgbmFxIHlicG55dm1uZ3ZiYQpmaGNjYmVn
      84  IHNiZSBsYmhlIENsZ3ViYSBjZWJ0ZW56ZiBvbCBjZWJpdnF2YXQgbmEgdmFncmVzbnByIGdiIGd1
      85  ciBUQUgKdHJnZ3JrZyB6cmZmbnRyIHBuZ255YnQgeXZvZW5lbC4AYmFjb24Ad2luayB3aW5rAA==
      86  '''
      87  
      88  
      89  UMO_DATA = b'''\
      90  3hIElQAAAAADAAAAHAAAADQAAAAAAAAAAAAAAAAAAABMAAAABAAAAE0AAAAQAAAAUgAAAA8BAABj
      91  AAAABAAAAHMBAAAWAAAAeAEAAABhYsOeAG15Y29udGV4dMOeBGFiw54AUHJvamVjdC1JZC1WZXJz
      92  aW9uOiAyLjAKUE8tUmV2aXNpb24tRGF0ZTogMjAwMy0wNC0xMSAxMjo0Mi0wNDAwCkxhc3QtVHJh
      93  bnNsYXRvcjogQmFycnkgQS4gV0Fyc2F3IDxiYXJyeUBweXRob24ub3JnPgpMYW5ndWFnZS1UZWFt
      94  OiBYWCA8cHl0aG9uLWRldkBweXRob24ub3JnPgpNSU1FLVZlcnNpb246IDEuMApDb250ZW50LVR5
      95  cGU6IHRleHQvcGxhaW47IGNoYXJzZXQ9dXRmLTgKQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzog
      96  N2JpdApHZW5lcmF0ZWQtQnk6IG1hbnVhbGx5CgDCpHl6AMKkeXogKGNvbnRleHQgdmVyc2lvbikA
      97  '''
      98  
      99  MMO_DATA = b'''\
     100  3hIElQAAAAABAAAAHAAAACQAAAADAAAALAAAAAAAAAA4AAAAeAEAADkAAAABAAAAAAAAAAAAAAAA
     101  UHJvamVjdC1JZC1WZXJzaW9uOiBObyBQcm9qZWN0IDAuMApQT1QtQ3JlYXRpb24tRGF0ZTogV2Vk
     102  IERlYyAxMSAwNzo0NDoxNSAyMDAyClBPLVJldmlzaW9uLURhdGU6IDIwMDItMDgtMTQgMDE6MTg6
     103  NTgrMDA6MDAKTGFzdC1UcmFuc2xhdG9yOiBKb2huIERvZSA8amRvZUBleGFtcGxlLmNvbT4KSmFu
     104  ZSBGb29iYXIgPGpmb29iYXJAZXhhbXBsZS5jb20+Ckxhbmd1YWdlLVRlYW06IHh4IDx4eEBleGFt
     105  cGxlLmNvbT4KTUlNRS1WZXJzaW9uOiAxLjAKQ29udGVudC1UeXBlOiB0ZXh0L3BsYWluOyBjaGFy
     106  c2V0PWlzby04ODU5LTE1CkNvbnRlbnQtVHJhbnNmZXItRW5jb2Rpbmc6IHF1b3RlZC1wcmludGFi
     107  bGUKR2VuZXJhdGVkLUJ5OiBweWdldHRleHQucHkgMS4zCgA=
     108  '''
     109  
     110  LOCALEDIR = os.path.join('xx', 'LC_MESSAGES')
     111  MOFILE = os.path.join(LOCALEDIR, 'gettext.mo')
     112  MOFILE_BAD_MAJOR_VERSION = os.path.join(LOCALEDIR, 'gettext_bad_major_version.mo')
     113  MOFILE_BAD_MINOR_VERSION = os.path.join(LOCALEDIR, 'gettext_bad_minor_version.mo')
     114  UMOFILE = os.path.join(LOCALEDIR, 'ugettext.mo')
     115  MMOFILE = os.path.join(LOCALEDIR, 'metadata.mo')
     116  
     117  
     118  class ESC[4;38;5;81mGettextBaseTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     119      def setUp(self):
     120          self.addCleanup(os_helper.rmtree, os.path.split(LOCALEDIR)[0])
     121          if not os.path.isdir(LOCALEDIR):
     122              os.makedirs(LOCALEDIR)
     123          with open(MOFILE, 'wb') as fp:
     124              fp.write(base64.decodebytes(GNU_MO_DATA))
     125          with open(MOFILE_BAD_MAJOR_VERSION, 'wb') as fp:
     126              fp.write(base64.decodebytes(GNU_MO_DATA_BAD_MAJOR_VERSION))
     127          with open(MOFILE_BAD_MINOR_VERSION, 'wb') as fp:
     128              fp.write(base64.decodebytes(GNU_MO_DATA_BAD_MINOR_VERSION))
     129          with open(UMOFILE, 'wb') as fp:
     130              fp.write(base64.decodebytes(UMO_DATA))
     131          with open(MMOFILE, 'wb') as fp:
     132              fp.write(base64.decodebytes(MMO_DATA))
     133          self.env = self.enterContext(os_helper.EnvironmentVarGuard())
     134          self.env['LANGUAGE'] = 'xx'
     135          gettext._translations.clear()
     136  
     137  
     138  GNU_MO_DATA_ISSUE_17898 = b'''\
     139  3hIElQAAAAABAAAAHAAAACQAAAAAAAAAAAAAAAAAAAAsAAAAggAAAC0AAAAAUGx1cmFsLUZvcm1z
     140  OiBucGx1cmFscz0yOyBwbHVyYWw9KG4gIT0gMSk7CiMtIy0jLSMtIyAgbWVzc2FnZXMucG8gKEVk
     141  WCBTdHVkaW8pICAjLSMtIy0jLSMKQ29udGVudC1UeXBlOiB0ZXh0L3BsYWluOyBjaGFyc2V0PVVU
     142  Ri04CgA=
     143  '''
     144  
     145  class ESC[4;38;5;81mGettextTestCase1(ESC[4;38;5;149mGettextBaseTest):
     146      def setUp(self):
     147          GettextBaseTest.setUp(self)
     148          self.localedir = os.curdir
     149          self.mofile = MOFILE
     150          gettext.install('gettext', self.localedir, names=['pgettext'])
     151  
     152      def test_some_translations(self):
     153          eq = self.assertEqual
     154          # test some translations
     155          eq(_('albatross'), 'albatross')
     156          eq(_('mullusk'), 'bacon')
     157          eq(_(r'Raymond Luxury Yach-t'), 'Throatwobbler Mangrove')
     158          eq(_(r'nudge nudge'), 'wink wink')
     159  
     160      def test_some_translations_with_context(self):
     161          eq = self.assertEqual
     162          eq(pgettext('my context', 'nudge nudge'),
     163             'wink wink (in "my context")')
     164          eq(pgettext('my other context', 'nudge nudge'),
     165             'wink wink (in "my other context")')
     166  
     167      def test_double_quotes(self):
     168          eq = self.assertEqual
     169          # double quotes
     170          eq(_("albatross"), 'albatross')
     171          eq(_("mullusk"), 'bacon')
     172          eq(_(r"Raymond Luxury Yach-t"), 'Throatwobbler Mangrove')
     173          eq(_(r"nudge nudge"), 'wink wink')
     174  
     175      def test_triple_single_quotes(self):
     176          eq = self.assertEqual
     177          # triple single quotes
     178          eq(_('''albatross'''), 'albatross')
     179          eq(_('''mullusk'''), 'bacon')
     180          eq(_(r'''Raymond Luxury Yach-t'''), 'Throatwobbler Mangrove')
     181          eq(_(r'''nudge nudge'''), 'wink wink')
     182  
     183      def test_triple_double_quotes(self):
     184          eq = self.assertEqual
     185          # triple double quotes
     186          eq(_("""albatross"""), 'albatross')
     187          eq(_("""mullusk"""), 'bacon')
     188          eq(_(r"""Raymond Luxury Yach-t"""), 'Throatwobbler Mangrove')
     189          eq(_(r"""nudge nudge"""), 'wink wink')
     190  
     191      def test_multiline_strings(self):
     192          eq = self.assertEqual
     193          # multiline strings
     194          eq(_('''This module provides internationalization and localization
     195  support for your Python programs by providing an interface to the GNU
     196  gettext message catalog library.'''),
     197             '''Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba
     198  fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH
     199  trggrkg zrffntr pngnybt yvoenel.''')
     200  
     201      def test_the_alternative_interface(self):
     202          eq = self.assertEqual
     203          neq = self.assertNotEqual
     204          # test the alternative interface
     205          with open(self.mofile, 'rb') as fp:
     206              t = gettext.GNUTranslations(fp)
     207          # Install the translation object
     208          t.install()
     209          eq(_('nudge nudge'), 'wink wink')
     210          # Try unicode return type
     211          t.install()
     212          eq(_('mullusk'), 'bacon')
     213          # Test installation of other methods
     214          import builtins
     215          t.install(names=["gettext", "ngettext"])
     216          eq(_, t.gettext)
     217          eq(builtins.gettext, t.gettext)
     218          eq(ngettext, t.ngettext)
     219          neq(pgettext, t.pgettext)
     220          del builtins.gettext
     221          del builtins.ngettext
     222  
     223  
     224  class ESC[4;38;5;81mGettextTestCase2(ESC[4;38;5;149mGettextBaseTest):
     225      def setUp(self):
     226          GettextBaseTest.setUp(self)
     227          self.localedir = os.curdir
     228          # Set up the bindings
     229          gettext.bindtextdomain('gettext', self.localedir)
     230          gettext.textdomain('gettext')
     231          # For convenience
     232          self._ = gettext.gettext
     233  
     234      def test_bindtextdomain(self):
     235          self.assertEqual(gettext.bindtextdomain('gettext'), self.localedir)
     236  
     237      def test_textdomain(self):
     238          self.assertEqual(gettext.textdomain(), 'gettext')
     239  
     240      def test_bad_major_version(self):
     241          with open(MOFILE_BAD_MAJOR_VERSION, 'rb') as fp:
     242              with self.assertRaises(OSError) as cm:
     243                  gettext.GNUTranslations(fp)
     244  
     245              exception = cm.exception
     246              self.assertEqual(exception.errno, 0)
     247              self.assertEqual(exception.strerror, "Bad version number 5")
     248              self.assertEqual(exception.filename, MOFILE_BAD_MAJOR_VERSION)
     249  
     250      def test_bad_minor_version(self):
     251          with open(MOFILE_BAD_MINOR_VERSION, 'rb') as fp:
     252              # Check that no error is thrown with a bad minor version number
     253              gettext.GNUTranslations(fp)
     254  
     255      def test_some_translations(self):
     256          eq = self.assertEqual
     257          # test some translations
     258          eq(self._('albatross'), 'albatross')
     259          eq(self._('mullusk'), 'bacon')
     260          eq(self._(r'Raymond Luxury Yach-t'), 'Throatwobbler Mangrove')
     261          eq(self._(r'nudge nudge'), 'wink wink')
     262  
     263      def test_some_translations_with_context(self):
     264          eq = self.assertEqual
     265          eq(gettext.pgettext('my context', 'nudge nudge'),
     266             'wink wink (in "my context")')
     267          eq(gettext.pgettext('my other context', 'nudge nudge'),
     268             'wink wink (in "my other context")')
     269  
     270      def test_some_translations_with_context_and_domain(self):
     271          eq = self.assertEqual
     272          eq(gettext.dpgettext('gettext', 'my context', 'nudge nudge'),
     273             'wink wink (in "my context")')
     274          eq(gettext.dpgettext('gettext', 'my other context', 'nudge nudge'),
     275             'wink wink (in "my other context")')
     276  
     277      def test_double_quotes(self):
     278          eq = self.assertEqual
     279          # double quotes
     280          eq(self._("albatross"), 'albatross')
     281          eq(self._("mullusk"), 'bacon')
     282          eq(self._(r"Raymond Luxury Yach-t"), 'Throatwobbler Mangrove')
     283          eq(self._(r"nudge nudge"), 'wink wink')
     284  
     285      def test_triple_single_quotes(self):
     286          eq = self.assertEqual
     287          # triple single quotes
     288          eq(self._('''albatross'''), 'albatross')
     289          eq(self._('''mullusk'''), 'bacon')
     290          eq(self._(r'''Raymond Luxury Yach-t'''), 'Throatwobbler Mangrove')
     291          eq(self._(r'''nudge nudge'''), 'wink wink')
     292  
     293      def test_triple_double_quotes(self):
     294          eq = self.assertEqual
     295          # triple double quotes
     296          eq(self._("""albatross"""), 'albatross')
     297          eq(self._("""mullusk"""), 'bacon')
     298          eq(self._(r"""Raymond Luxury Yach-t"""), 'Throatwobbler Mangrove')
     299          eq(self._(r"""nudge nudge"""), 'wink wink')
     300  
     301      def test_multiline_strings(self):
     302          eq = self.assertEqual
     303          # multiline strings
     304          eq(self._('''This module provides internationalization and localization
     305  support for your Python programs by providing an interface to the GNU
     306  gettext message catalog library.'''),
     307             '''Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba
     308  fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH
     309  trggrkg zrffntr pngnybt yvoenel.''')
     310  
     311  
     312  class ESC[4;38;5;81mPluralFormsTestCase(ESC[4;38;5;149mGettextBaseTest):
     313      def setUp(self):
     314          GettextBaseTest.setUp(self)
     315          self.mofile = MOFILE
     316  
     317      def test_plural_forms1(self):
     318          eq = self.assertEqual
     319          x = gettext.ngettext('There is %s file', 'There are %s files', 1)
     320          eq(x, 'Hay %s fichero')
     321          x = gettext.ngettext('There is %s file', 'There are %s files', 2)
     322          eq(x, 'Hay %s ficheros')
     323          x = gettext.gettext('There is %s file')
     324          eq(x, 'Hay %s fichero')
     325  
     326      def test_plural_context_forms1(self):
     327          eq = self.assertEqual
     328          x = gettext.npgettext('With context',
     329                                'There is %s file', 'There are %s files', 1)
     330          eq(x, 'Hay %s fichero (context)')
     331          x = gettext.npgettext('With context',
     332                                'There is %s file', 'There are %s files', 2)
     333          eq(x, 'Hay %s ficheros (context)')
     334          x = gettext.pgettext('With context', 'There is %s file')
     335          eq(x, 'Hay %s fichero (context)')
     336  
     337      def test_plural_forms2(self):
     338          eq = self.assertEqual
     339          with open(self.mofile, 'rb') as fp:
     340              t = gettext.GNUTranslations(fp)
     341          x = t.ngettext('There is %s file', 'There are %s files', 1)
     342          eq(x, 'Hay %s fichero')
     343          x = t.ngettext('There is %s file', 'There are %s files', 2)
     344          eq(x, 'Hay %s ficheros')
     345          x = t.gettext('There is %s file')
     346          eq(x, 'Hay %s fichero')
     347  
     348      def test_plural_context_forms2(self):
     349          eq = self.assertEqual
     350          with open(self.mofile, 'rb') as fp:
     351              t = gettext.GNUTranslations(fp)
     352          x = t.npgettext('With context',
     353                          'There is %s file', 'There are %s files', 1)
     354          eq(x, 'Hay %s fichero (context)')
     355          x = t.npgettext('With context',
     356                          'There is %s file', 'There are %s files', 2)
     357          eq(x, 'Hay %s ficheros (context)')
     358          x = gettext.pgettext('With context', 'There is %s file')
     359          eq(x, 'Hay %s fichero (context)')
     360  
     361      # Examples from http://www.gnu.org/software/gettext/manual/gettext.html
     362  
     363      def test_ja(self):
     364          eq = self.assertEqual
     365          f = gettext.c2py('0')
     366          s = ''.join([ str(f(x)) for x in range(200) ])
     367          eq(s, "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
     368  
     369      def test_de(self):
     370          eq = self.assertEqual
     371          f = gettext.c2py('n != 1')
     372          s = ''.join([ str(f(x)) for x in range(200) ])
     373          eq(s, "10111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")
     374  
     375      def test_fr(self):
     376          eq = self.assertEqual
     377          f = gettext.c2py('n>1')
     378          s = ''.join([ str(f(x)) for x in range(200) ])
     379          eq(s, "00111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111")
     380  
     381      def test_lv(self):
     382          eq = self.assertEqual
     383          f = gettext.c2py('n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2')
     384          s = ''.join([ str(f(x)) for x in range(200) ])
     385          eq(s, "20111111111111111111101111111110111111111011111111101111111110111111111011111111101111111110111111111011111111111111111110111111111011111111101111111110111111111011111111101111111110111111111011111111")
     386  
     387      def test_gd(self):
     388          eq = self.assertEqual
     389          f = gettext.c2py('n==1 ? 0 : n==2 ? 1 : 2')
     390          s = ''.join([ str(f(x)) for x in range(200) ])
     391          eq(s, "20122222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222")
     392  
     393      def test_gd2(self):
     394          eq = self.assertEqual
     395          # Tests the combination of parentheses and "?:"
     396          f = gettext.c2py('n==1 ? 0 : (n==2 ? 1 : 2)')
     397          s = ''.join([ str(f(x)) for x in range(200) ])
     398          eq(s, "20122222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222")
     399  
     400      def test_ro(self):
     401          eq = self.assertEqual
     402          f = gettext.c2py('n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2')
     403          s = ''.join([ str(f(x)) for x in range(200) ])
     404          eq(s, "10111111111111111111222222222222222222222222222222222222222222222222222222222222222222222222222222222111111111111111111122222222222222222222222222222222222222222222222222222222222222222222222222222222")
     405  
     406      def test_lt(self):
     407          eq = self.assertEqual
     408          f = gettext.c2py('n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2')
     409          s = ''.join([ str(f(x)) for x in range(200) ])
     410          eq(s, "20111111112222222222201111111120111111112011111111201111111120111111112011111111201111111120111111112011111111222222222220111111112011111111201111111120111111112011111111201111111120111111112011111111")
     411  
     412      def test_ru(self):
     413          eq = self.assertEqual
     414          f = gettext.c2py('n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2')
     415          s = ''.join([ str(f(x)) for x in range(200) ])
     416          eq(s, "20111222222222222222201112222220111222222011122222201112222220111222222011122222201112222220111222222011122222222222222220111222222011122222201112222220111222222011122222201112222220111222222011122222")
     417  
     418      def test_cs(self):
     419          eq = self.assertEqual
     420          f = gettext.c2py('(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2')
     421          s = ''.join([ str(f(x)) for x in range(200) ])
     422          eq(s, "20111222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222")
     423  
     424      def test_pl(self):
     425          eq = self.assertEqual
     426          f = gettext.c2py('n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2')
     427          s = ''.join([ str(f(x)) for x in range(200) ])
     428          eq(s, "20111222222222222222221112222222111222222211122222221112222222111222222211122222221112222222111222222211122222222222222222111222222211122222221112222222111222222211122222221112222222111222222211122222")
     429  
     430      def test_sl(self):
     431          eq = self.assertEqual
     432          f = gettext.c2py('n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3')
     433          s = ''.join([ str(f(x)) for x in range(200) ])
     434          eq(s, "30122333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333012233333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333")
     435  
     436      def test_ar(self):
     437          eq = self.assertEqual
     438          f = gettext.c2py('n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5')
     439          s = ''.join([ str(f(x)) for x in range(200) ])
     440          eq(s, "01233333333444444444444444444444444444444444444444444444444444444444444444444444444444444444444444445553333333344444444444444444444444444444444444444444444444444444444444444444444444444444444444444444")
     441  
     442      def test_security(self):
     443          raises = self.assertRaises
     444          # Test for a dangerous expression
     445          raises(ValueError, gettext.c2py, "os.chmod('/etc/passwd',0777)")
     446          # issue28563
     447          raises(ValueError, gettext.c2py, '"(eval(foo) && ""')
     448          raises(ValueError, gettext.c2py, 'f"{os.system(\'sh\')}"')
     449          # Maximum recursion depth exceeded during compilation
     450          raises(ValueError, gettext.c2py, 'n+'*10000 + 'n')
     451          self.assertEqual(gettext.c2py('n+'*100 + 'n')(1), 101)
     452          # MemoryError during compilation
     453          raises(ValueError, gettext.c2py, '('*100 + 'n' + ')'*100)
     454          # Maximum recursion depth exceeded in C to Python translator
     455          raises(ValueError, gettext.c2py, '('*10000 + 'n' + ')'*10000)
     456          self.assertEqual(gettext.c2py('('*20 + 'n' + ')'*20)(1), 1)
     457  
     458      def test_chained_comparison(self):
     459          # C doesn't chain comparison as Python so 2 == 2 == 2 gets different results
     460          f = gettext.c2py('n == n == n')
     461          self.assertEqual(''.join(str(f(x)) for x in range(3)), '010')
     462          f = gettext.c2py('1 < n == n')
     463          self.assertEqual(''.join(str(f(x)) for x in range(3)), '100')
     464          f = gettext.c2py('n == n < 2')
     465          self.assertEqual(''.join(str(f(x)) for x in range(3)), '010')
     466          f = gettext.c2py('0 < n < 2')
     467          self.assertEqual(''.join(str(f(x)) for x in range(3)), '111')
     468  
     469      def test_decimal_number(self):
     470          self.assertEqual(gettext.c2py('0123')(1), 123)
     471  
     472      def test_invalid_syntax(self):
     473          invalid_expressions = [
     474              'x>1', '(n>1', 'n>1)', '42**42**42', '0xa', '1.0', '1e2',
     475              'n>0x1', '+n', '-n', 'n()', 'n(1)', '1+', 'nn', 'n n',
     476          ]
     477          for expr in invalid_expressions:
     478              with self.assertRaises(ValueError):
     479                  gettext.c2py(expr)
     480  
     481      def test_nested_condition_operator(self):
     482          self.assertEqual(gettext.c2py('n?1?2:3:4')(0), 4)
     483          self.assertEqual(gettext.c2py('n?1?2:3:4')(1), 2)
     484          self.assertEqual(gettext.c2py('n?1:3?4:5')(0), 4)
     485          self.assertEqual(gettext.c2py('n?1:3?4:5')(1), 1)
     486  
     487      def test_division(self):
     488          f = gettext.c2py('2/n*3')
     489          self.assertEqual(f(1), 6)
     490          self.assertEqual(f(2), 3)
     491          self.assertEqual(f(3), 0)
     492          self.assertEqual(f(-1), -6)
     493          self.assertRaises(ZeroDivisionError, f, 0)
     494  
     495      def test_plural_number(self):
     496          f = gettext.c2py('n != 1')
     497          self.assertEqual(f(1), 0)
     498          self.assertEqual(f(2), 1)
     499          with self.assertWarns(DeprecationWarning):
     500              self.assertEqual(f(1.0), 0)
     501          with self.assertWarns(DeprecationWarning):
     502              self.assertEqual(f(2.0), 1)
     503          with self.assertWarns(DeprecationWarning):
     504              self.assertEqual(f(1.1), 1)
     505          self.assertRaises(TypeError, f, '2')
     506          self.assertRaises(TypeError, f, b'2')
     507          self.assertRaises(TypeError, f, [])
     508          self.assertRaises(TypeError, f, object())
     509  
     510  
     511  class ESC[4;38;5;81mGNUTranslationParsingTest(ESC[4;38;5;149mGettextBaseTest):
     512      def test_plural_form_error_issue17898(self):
     513          with open(MOFILE, 'wb') as fp:
     514              fp.write(base64.decodebytes(GNU_MO_DATA_ISSUE_17898))
     515          with open(MOFILE, 'rb') as fp:
     516              # If this runs cleanly, the bug is fixed.
     517              t = gettext.GNUTranslations(fp)
     518  
     519      def test_ignore_comments_in_headers_issue36239(self):
     520          """Checks that comments like:
     521  
     522              #-#-#-#-#  messages.po (EdX Studio)  #-#-#-#-#
     523  
     524          are ignored.
     525          """
     526          with open(MOFILE, 'wb') as fp:
     527              fp.write(base64.decodebytes(GNU_MO_DATA_ISSUE_17898))
     528          with open(MOFILE, 'rb') as fp:
     529              t = gettext.GNUTranslations(fp)
     530              self.assertEqual(t.info()["plural-forms"], "nplurals=2; plural=(n != 1);")
     531  
     532  
     533  class ESC[4;38;5;81mUnicodeTranslationsTest(ESC[4;38;5;149mGettextBaseTest):
     534      def setUp(self):
     535          GettextBaseTest.setUp(self)
     536          with open(UMOFILE, 'rb') as fp:
     537              self.t = gettext.GNUTranslations(fp)
     538          self._ = self.t.gettext
     539          self.pgettext = self.t.pgettext
     540  
     541      def test_unicode_msgid(self):
     542          self.assertIsInstance(self._(''), str)
     543  
     544      def test_unicode_msgstr(self):
     545          self.assertEqual(self._('ab\xde'), '\xa4yz')
     546  
     547      def test_unicode_context_msgstr(self):
     548          t = self.pgettext('mycontext\xde', 'ab\xde')
     549          self.assertTrue(isinstance(t, str))
     550          self.assertEqual(t, '\xa4yz (context version)')
     551  
     552  
     553  class ESC[4;38;5;81mUnicodeTranslationsPluralTest(ESC[4;38;5;149mGettextBaseTest):
     554      def setUp(self):
     555          GettextBaseTest.setUp(self)
     556          with open(MOFILE, 'rb') as fp:
     557              self.t = gettext.GNUTranslations(fp)
     558          self.ngettext = self.t.ngettext
     559          self.npgettext = self.t.npgettext
     560  
     561      def test_unicode_msgid(self):
     562          unless = self.assertTrue
     563          unless(isinstance(self.ngettext('', '', 1), str))
     564          unless(isinstance(self.ngettext('', '', 2), str))
     565  
     566      def test_unicode_context_msgid(self):
     567          unless = self.assertTrue
     568          unless(isinstance(self.npgettext('', '', '', 1), str))
     569          unless(isinstance(self.npgettext('', '', '', 2), str))
     570  
     571      def test_unicode_msgstr(self):
     572          eq = self.assertEqual
     573          unless = self.assertTrue
     574          t = self.ngettext("There is %s file", "There are %s files", 1)
     575          unless(isinstance(t, str))
     576          eq(t, "Hay %s fichero")
     577          unless(isinstance(t, str))
     578          t = self.ngettext("There is %s file", "There are %s files", 5)
     579          unless(isinstance(t, str))
     580          eq(t, "Hay %s ficheros")
     581  
     582      def test_unicode_msgstr_with_context(self):
     583          eq = self.assertEqual
     584          unless = self.assertTrue
     585          t = self.npgettext("With context",
     586                             "There is %s file", "There are %s files", 1)
     587          unless(isinstance(t, str))
     588          eq(t, "Hay %s fichero (context)")
     589          t = self.npgettext("With context",
     590                             "There is %s file", "There are %s files", 5)
     591          unless(isinstance(t, str))
     592          eq(t, "Hay %s ficheros (context)")
     593  
     594  
     595  class ESC[4;38;5;81mWeirdMetadataTest(ESC[4;38;5;149mGettextBaseTest):
     596      def setUp(self):
     597          GettextBaseTest.setUp(self)
     598          with open(MMOFILE, 'rb') as fp:
     599              try:
     600                  self.t = gettext.GNUTranslations(fp)
     601              except:
     602                  self.tearDown()
     603                  raise
     604  
     605      def test_weird_metadata(self):
     606          info = self.t.info()
     607          self.assertEqual(len(info), 9)
     608          self.assertEqual(info['last-translator'],
     609             'John Doe <jdoe@example.com>\nJane Foobar <jfoobar@example.com>')
     610  
     611  
     612  class ESC[4;38;5;81mDummyGNUTranslations(ESC[4;38;5;149mgettextESC[4;38;5;149m.ESC[4;38;5;149mGNUTranslations):
     613      def foo(self):
     614          return 'foo'
     615  
     616  
     617  class ESC[4;38;5;81mGettextCacheTestCase(ESC[4;38;5;149mGettextBaseTest):
     618      def test_cache(self):
     619          self.localedir = os.curdir
     620          self.mofile = MOFILE
     621  
     622          self.assertEqual(len(gettext._translations), 0)
     623  
     624          t = gettext.translation('gettext', self.localedir)
     625  
     626          self.assertEqual(len(gettext._translations), 1)
     627  
     628          t = gettext.translation('gettext', self.localedir,
     629                                  class_=DummyGNUTranslations)
     630  
     631          self.assertEqual(len(gettext._translations), 2)
     632          self.assertEqual(t.__class__, DummyGNUTranslations)
     633  
     634          # Calling it again doesn't add to the cache
     635  
     636          t = gettext.translation('gettext', self.localedir,
     637                                  class_=DummyGNUTranslations)
     638  
     639          self.assertEqual(len(gettext._translations), 2)
     640          self.assertEqual(t.__class__, DummyGNUTranslations)
     641  
     642  
     643  class ESC[4;38;5;81mMiscTestCase(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     644      def test__all__(self):
     645          support.check__all__(self, gettext,
     646                               not_exported={'c2py', 'ENOENT'})
     647  
     648  
     649  if __name__ == '__main__':
     650      unittest.main()
     651  
     652  
     653  # For reference, here's the .po file used to created the GNU_MO_DATA above.
     654  #
     655  # The original version was automatically generated from the sources with
     656  # pygettext. Later it was manually modified to add plural forms support.
     657  
     658  b'''
     659  # Dummy translation for the Python test_gettext.py module.
     660  # Copyright (C) 2001 Python Software Foundation
     661  # Barry Warsaw <barry@python.org>, 2000.
     662  #
     663  msgid ""
     664  msgstr ""
     665  "Project-Id-Version: 2.0\n"
     666  "PO-Revision-Date: 2003-04-11 14:32-0400\n"
     667  "Last-Translator: J. David Ibanez <j-david@noos.fr>\n"
     668  "Language-Team: XX <python-dev@python.org>\n"
     669  "MIME-Version: 1.0\n"
     670  "Content-Type: text/plain; charset=iso-8859-1\n"
     671  "Content-Transfer-Encoding: 8bit\n"
     672  "Generated-By: pygettext.py 1.1\n"
     673  "Plural-Forms: nplurals=2; plural=n!=1;\n"
     674  
     675  #: test_gettext.py:19 test_gettext.py:25 test_gettext.py:31 test_gettext.py:37
     676  #: test_gettext.py:51 test_gettext.py:80 test_gettext.py:86 test_gettext.py:92
     677  #: test_gettext.py:98
     678  msgid "nudge nudge"
     679  msgstr "wink wink"
     680  
     681  msgctxt "my context"
     682  msgid "nudge nudge"
     683  msgstr "wink wink (in \"my context\")"
     684  
     685  msgctxt "my other context"
     686  msgid "nudge nudge"
     687  msgstr "wink wink (in \"my other context\")"
     688  
     689  #: test_gettext.py:16 test_gettext.py:22 test_gettext.py:28 test_gettext.py:34
     690  #: test_gettext.py:77 test_gettext.py:83 test_gettext.py:89 test_gettext.py:95
     691  msgid "albatross"
     692  msgstr ""
     693  
     694  #: test_gettext.py:18 test_gettext.py:24 test_gettext.py:30 test_gettext.py:36
     695  #: test_gettext.py:79 test_gettext.py:85 test_gettext.py:91 test_gettext.py:97
     696  msgid "Raymond Luxury Yach-t"
     697  msgstr "Throatwobbler Mangrove"
     698  
     699  #: test_gettext.py:17 test_gettext.py:23 test_gettext.py:29 test_gettext.py:35
     700  #: test_gettext.py:56 test_gettext.py:78 test_gettext.py:84 test_gettext.py:90
     701  #: test_gettext.py:96
     702  msgid "mullusk"
     703  msgstr "bacon"
     704  
     705  #: test_gettext.py:40 test_gettext.py:101
     706  msgid ""
     707  "This module provides internationalization and localization\n"
     708  "support for your Python programs by providing an interface to the GNU\n"
     709  "gettext message catalog library."
     710  msgstr ""
     711  "Guvf zbqhyr cebivqrf vagreangvbanyvmngvba naq ybpnyvmngvba\n"
     712  "fhccbeg sbe lbhe Clguba cebtenzf ol cebivqvat na vagresnpr gb gur TAH\n"
     713  "trggrkg zrffntr pngnybt yvoenel."
     714  
     715  # Manually added, as neither pygettext nor xgettext support plural forms
     716  # in Python.
     717  msgid "There is %s file"
     718  msgid_plural "There are %s files"
     719  msgstr[0] "Hay %s fichero"
     720  msgstr[1] "Hay %s ficheros"
     721  
     722  # Manually added, as neither pygettext nor xgettext support plural forms
     723  # and context in Python.
     724  msgctxt "With context"
     725  msgid "There is %s file"
     726  msgid_plural "There are %s files"
     727  msgstr[0] "Hay %s fichero (context)"
     728  msgstr[1] "Hay %s ficheros (context)"
     729  '''
     730  
     731  # Here's the second example po file example, used to generate the UMO_DATA
     732  # containing utf-8 encoded Unicode strings
     733  
     734  b'''
     735  # Dummy translation for the Python test_gettext.py module.
     736  # Copyright (C) 2001 Python Software Foundation
     737  # Barry Warsaw <barry@python.org>, 2000.
     738  #
     739  msgid ""
     740  msgstr ""
     741  "Project-Id-Version: 2.0\n"
     742  "PO-Revision-Date: 2003-04-11 12:42-0400\n"
     743  "Last-Translator: Barry A. WArsaw <barry@python.org>\n"
     744  "Language-Team: XX <python-dev@python.org>\n"
     745  "MIME-Version: 1.0\n"
     746  "Content-Type: text/plain; charset=utf-8\n"
     747  "Content-Transfer-Encoding: 7bit\n"
     748  "Generated-By: manually\n"
     749  
     750  #: nofile:0
     751  msgid "ab\xc3\x9e"
     752  msgstr "\xc2\xa4yz"
     753  
     754  #: nofile:1
     755  msgctxt "mycontext\xc3\x9e"
     756  msgid "ab\xc3\x9e"
     757  msgstr "\xc2\xa4yz (context version)"
     758  '''
     759  
     760  # Here's the third example po file, used to generate MMO_DATA
     761  
     762  b'''
     763  msgid ""
     764  msgstr ""
     765  "Project-Id-Version: No Project 0.0\n"
     766  "POT-Creation-Date: Wed Dec 11 07:44:15 2002\n"
     767  "PO-Revision-Date: 2002-08-14 01:18:58+00:00\n"
     768  "Last-Translator: John Doe <jdoe@example.com>\n"
     769  "Jane Foobar <jfoobar@example.com>\n"
     770  "Language-Team: xx <xx@example.com>\n"
     771  "MIME-Version: 1.0\n"
     772  "Content-Type: text/plain; charset=iso-8859-15\n"
     773  "Content-Transfer-Encoding: quoted-printable\n"
     774  "Generated-By: pygettext.py 1.3\n"
     775  '''
     776  
     777  #
     778  # messages.po, used for bug 17898
     779  #
     780  
     781  b'''
     782  # test file for http://bugs.python.org/issue17898
     783  msgid ""
     784  msgstr ""
     785  "Plural-Forms: nplurals=2; plural=(n != 1);\n"
     786  "#-#-#-#-#  messages.po (EdX Studio)  #-#-#-#-#\n"
     787  "Content-Type: text/plain; charset=UTF-8\n"
     788  '''