You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

245 lines
8.8 KiB

  1. """Tests to cover the Tools/i18n package"""
  2. import os
  3. import sys
  4. import unittest
  5. from textwrap import dedent
  6. from test.support.script_helper import assert_python_ok
  7. from test.test_tools import skip_if_missing, toolsdir
  8. from test.support import temp_cwd, temp_dir
  9. skip_if_missing()
  10. class Test_pygettext(unittest.TestCase):
  11. """Tests for the pygettext.py tool"""
  12. script = os.path.join(toolsdir,'i18n', 'pygettext.py')
  13. def get_header(self, data):
  14. """ utility: return the header of a .po file as a dictionary """
  15. headers = {}
  16. for line in data.split('\n'):
  17. if not line or line.startswith(('#', 'msgid','msgstr')):
  18. continue
  19. line = line.strip('"')
  20. key, val = line.split(':',1)
  21. headers[key] = val.strip()
  22. return headers
  23. def get_msgids(self, data):
  24. """ utility: return all msgids in .po file as a list of strings """
  25. msgids = []
  26. reading_msgid = False
  27. cur_msgid = []
  28. for line in data.split('\n'):
  29. if reading_msgid:
  30. if line.startswith('"'):
  31. cur_msgid.append(line.strip('"'))
  32. else:
  33. msgids.append('\n'.join(cur_msgid))
  34. cur_msgid = []
  35. reading_msgid = False
  36. continue
  37. if line.startswith('msgid '):
  38. line = line[len('msgid '):]
  39. cur_msgid.append(line.strip('"'))
  40. reading_msgid = True
  41. else:
  42. if reading_msgid:
  43. msgids.append('\n'.join(cur_msgid))
  44. return msgids
  45. def extract_docstrings_from_str(self, module_content):
  46. """ utility: return all msgids extracted from module_content """
  47. filename = 'test_docstrings.py'
  48. with temp_cwd(None) as cwd:
  49. with open(filename, 'w') as fp:
  50. fp.write(module_content)
  51. assert_python_ok(self.script, '-D', filename)
  52. with open('messages.pot') as fp:
  53. data = fp.read()
  54. return self.get_msgids(data)
  55. def test_header(self):
  56. """Make sure the required fields are in the header, according to:
  57. http://www.gnu.org/software/gettext/manual/gettext.html#Header-Entry
  58. """
  59. with temp_cwd(None) as cwd:
  60. assert_python_ok(self.script)
  61. with open('messages.pot') as fp:
  62. data = fp.read()
  63. header = self.get_header(data)
  64. self.assertIn("Project-Id-Version", header)
  65. self.assertIn("POT-Creation-Date", header)
  66. self.assertIn("PO-Revision-Date", header)
  67. self.assertIn("Last-Translator", header)
  68. self.assertIn("Language-Team", header)
  69. self.assertIn("MIME-Version", header)
  70. self.assertIn("Content-Type", header)
  71. self.assertIn("Content-Transfer-Encoding", header)
  72. self.assertIn("Generated-By", header)
  73. # not clear if these should be required in POT (template) files
  74. #self.assertIn("Report-Msgid-Bugs-To", header)
  75. #self.assertIn("Language", header)
  76. #"Plural-Forms" is optional
  77. @unittest.skipIf(sys.platform.startswith('aix'),
  78. 'bpo-29972: broken test on AIX')
  79. def test_POT_Creation_Date(self):
  80. """ Match the date format from xgettext for POT-Creation-Date """
  81. from datetime import datetime
  82. with temp_cwd(None) as cwd:
  83. assert_python_ok(self.script)
  84. with open('messages.pot') as fp:
  85. data = fp.read()
  86. header = self.get_header(data)
  87. creationDate = header['POT-Creation-Date']
  88. # peel off the escaped newline at the end of string
  89. if creationDate.endswith('\\n'):
  90. creationDate = creationDate[:-len('\\n')]
  91. # This will raise if the date format does not exactly match.
  92. datetime.strptime(creationDate, '%Y-%m-%d %H:%M%z')
  93. def test_funcdocstring(self):
  94. for doc in ('"""doc"""', "r'''doc'''", "R'doc'", 'u"doc"'):
  95. with self.subTest(doc):
  96. msgids = self.extract_docstrings_from_str(dedent('''\
  97. def foo(bar):
  98. %s
  99. ''' % doc))
  100. self.assertIn('doc', msgids)
  101. def test_funcdocstring_bytes(self):
  102. msgids = self.extract_docstrings_from_str(dedent('''\
  103. def foo(bar):
  104. b"""doc"""
  105. '''))
  106. self.assertFalse([msgid for msgid in msgids if 'doc' in msgid])
  107. def test_funcdocstring_fstring(self):
  108. msgids = self.extract_docstrings_from_str(dedent('''\
  109. def foo(bar):
  110. f"""doc"""
  111. '''))
  112. self.assertFalse([msgid for msgid in msgids if 'doc' in msgid])
  113. def test_classdocstring(self):
  114. for doc in ('"""doc"""', "r'''doc'''", "R'doc'", 'u"doc"'):
  115. with self.subTest(doc):
  116. msgids = self.extract_docstrings_from_str(dedent('''\
  117. class C:
  118. %s
  119. ''' % doc))
  120. self.assertIn('doc', msgids)
  121. def test_classdocstring_bytes(self):
  122. msgids = self.extract_docstrings_from_str(dedent('''\
  123. class C:
  124. b"""doc"""
  125. '''))
  126. self.assertFalse([msgid for msgid in msgids if 'doc' in msgid])
  127. def test_classdocstring_fstring(self):
  128. msgids = self.extract_docstrings_from_str(dedent('''\
  129. class C:
  130. f"""doc"""
  131. '''))
  132. self.assertFalse([msgid for msgid in msgids if 'doc' in msgid])
  133. def test_msgid(self):
  134. msgids = self.extract_docstrings_from_str(
  135. '''_("""doc""" r'str' u"ing")''')
  136. self.assertIn('docstring', msgids)
  137. def test_msgid_bytes(self):
  138. msgids = self.extract_docstrings_from_str('_(b"""doc""")')
  139. self.assertFalse([msgid for msgid in msgids if 'doc' in msgid])
  140. def test_msgid_fstring(self):
  141. msgids = self.extract_docstrings_from_str('_(f"""doc""")')
  142. self.assertFalse([msgid for msgid in msgids if 'doc' in msgid])
  143. def test_funcdocstring_annotated_args(self):
  144. """ Test docstrings for functions with annotated args """
  145. msgids = self.extract_docstrings_from_str(dedent('''\
  146. def foo(bar: str):
  147. """doc"""
  148. '''))
  149. self.assertIn('doc', msgids)
  150. def test_funcdocstring_annotated_return(self):
  151. """ Test docstrings for functions with annotated return type """
  152. msgids = self.extract_docstrings_from_str(dedent('''\
  153. def foo(bar) -> str:
  154. """doc"""
  155. '''))
  156. self.assertIn('doc', msgids)
  157. def test_funcdocstring_defvalue_args(self):
  158. """ Test docstring for functions with default arg values """
  159. msgids = self.extract_docstrings_from_str(dedent('''\
  160. def foo(bar=()):
  161. """doc"""
  162. '''))
  163. self.assertIn('doc', msgids)
  164. def test_funcdocstring_multiple_funcs(self):
  165. """ Test docstring extraction for multiple functions combining
  166. annotated args, annotated return types and default arg values
  167. """
  168. msgids = self.extract_docstrings_from_str(dedent('''\
  169. def foo1(bar: tuple=()) -> str:
  170. """doc1"""
  171. def foo2(bar: List[1:2]) -> (lambda x: x):
  172. """doc2"""
  173. def foo3(bar: 'func'=lambda x: x) -> {1: 2}:
  174. """doc3"""
  175. '''))
  176. self.assertIn('doc1', msgids)
  177. self.assertIn('doc2', msgids)
  178. self.assertIn('doc3', msgids)
  179. def test_classdocstring_early_colon(self):
  180. """ Test docstring extraction for a class with colons occurring within
  181. the parentheses.
  182. """
  183. msgids = self.extract_docstrings_from_str(dedent('''\
  184. class D(L[1:2], F({1: 2}), metaclass=M(lambda x: x)):
  185. """doc"""
  186. '''))
  187. self.assertIn('doc', msgids)
  188. def test_files_list(self):
  189. """Make sure the directories are inspected for source files
  190. bpo-31920
  191. """
  192. text1 = 'Text to translate1'
  193. text2 = 'Text to translate2'
  194. text3 = 'Text to ignore'
  195. with temp_cwd(None), temp_dir(None) as sdir:
  196. os.mkdir(os.path.join(sdir, 'pypkg'))
  197. with open(os.path.join(sdir, 'pypkg', 'pymod.py'), 'w') as sfile:
  198. sfile.write(f'_({text1!r})')
  199. os.mkdir(os.path.join(sdir, 'pkg.py'))
  200. with open(os.path.join(sdir, 'pkg.py', 'pymod2.py'), 'w') as sfile:
  201. sfile.write(f'_({text2!r})')
  202. os.mkdir(os.path.join(sdir, 'CVS'))
  203. with open(os.path.join(sdir, 'CVS', 'pymod3.py'), 'w') as sfile:
  204. sfile.write(f'_({text3!r})')
  205. assert_python_ok(self.script, sdir)
  206. with open('messages.pot') as fp:
  207. data = fp.read()
  208. self.assertIn(f'msgid "{text1}"', data)
  209. self.assertIn(f'msgid "{text2}"', data)
  210. self.assertNotIn(text3, data)