test_email.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. #!/usr/bin/env python3
  2. #
  3. # This file is part of GCC.
  4. #
  5. # GCC is free software; you can redistribute it and/or modify it under
  6. # the terms of the GNU General Public License as published by the Free
  7. # Software Foundation; either version 3, or (at your option) any later
  8. # version.
  9. #
  10. # GCC is distributed in the hope that it will be useful, but WITHOUT ANY
  11. # WARRANTY; without even the implied warranty of MERCHANTABILITY or
  12. # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  13. # for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with GCC; see the file COPYING3. If not see
  17. # <http://www.gnu.org/licenses/>. */
  18. import os
  19. import tempfile
  20. import unittest
  21. from git_commit import GitCommit
  22. from git_email import GitEmail
  23. import unidiff
  24. script_path = os.path.dirname(os.path.realpath(__file__))
  25. unidiff_supports_renaming = hasattr(unidiff.PatchedFile(), 'is_rename')
  26. NAME_STATUS1 = """
  27. M gcc/ada/impunit.adb'
  28. R097 gcc/ada/libgnat/s-atopar.adb gcc/ada/libgnat/s-aoinar.adb
  29. """
  30. class TestGccChangelog(unittest.TestCase):
  31. def setUp(self):
  32. self.patches = {}
  33. self.temps = []
  34. filename = None
  35. patch_lines = []
  36. with open(os.path.join(script_path, 'test_patches.txt')) as f:
  37. lines = f.read()
  38. for line in lines.split('\n'):
  39. if line.startswith('==='):
  40. if patch_lines:
  41. self.patches[filename] = patch_lines
  42. filename = line.split(' ')[1]
  43. patch_lines = []
  44. else:
  45. patch_lines.append(line)
  46. if patch_lines:
  47. self.patches[filename] = patch_lines
  48. def tearDown(self):
  49. for t in self.temps:
  50. assert t.endswith('.patch')
  51. os.remove(t)
  52. def get_git_email(self, filename):
  53. with tempfile.NamedTemporaryFile(mode='w+', suffix='.patch',
  54. delete=False) as f:
  55. f.write('\n'.join(self.patches[filename]))
  56. self.temps.append(f.name)
  57. return GitEmail(f.name)
  58. def from_patch_glob(self, name):
  59. files = [f for f in self.patches.keys() if f.startswith(name)]
  60. assert len(files) == 1
  61. return self.get_git_email(files[0])
  62. def test_simple_patch_format(self):
  63. email = self.get_git_email('0577-aarch64-Add-an-and.patch')
  64. assert not email.errors
  65. assert len(email.changelog_entries) == 2
  66. entry = email.changelog_entries[0]
  67. assert (entry.author_lines ==
  68. [('Richard Sandiford <richard.sandiford@arm.com>',
  69. '2020-02-06')])
  70. assert len(entry.authors) == 1
  71. assert (entry.authors[0]
  72. == 'Richard Sandiford <richard.sandiford@arm.com>')
  73. assert entry.folder == 'gcc'
  74. assert entry.prs == ['PR target/87763']
  75. assert len(entry.files) == 3
  76. assert entry.files[0] == 'config/aarch64/aarch64-protos.h'
  77. def test_daily_bump(self):
  78. email = self.get_git_email('0085-Daily-bump.patch')
  79. assert not email.errors
  80. assert not email.changelog_entries
  81. def test_deduce_changelog_entries(self):
  82. email = self.from_patch_glob('0040')
  83. assert len(email.changelog_entries) == 2
  84. assert email.changelog_entries[0].folder == 'gcc/cp'
  85. assert email.changelog_entries[0].prs == ['PR c++/90916']
  86. assert email.changelog_entries[0].files == ['pt.c']
  87. # this one is added automatically
  88. assert email.changelog_entries[1].folder == 'gcc/testsuite'
  89. def test_only_changelog_updated(self):
  90. email = self.from_patch_glob('0129')
  91. assert not email.errors
  92. assert not email.changelog_entries
  93. def test_wrong_mentioned_filename(self):
  94. email = self.from_patch_glob('0096')
  95. assert email.errors
  96. err = email.errors[0]
  97. assert err.message == 'unchanged file mentioned in a ChangeLog (did ' \
  98. 'you mean "gcc/testsuite/gcc.target/aarch64/' \
  99. 'advsimd-intrinsics/vdot-3-1.c"?)'
  100. assert err.line == 'gcc/testsuite/gcc.target/aarch64/' \
  101. 'advsimd-intrinsics/vdot-compile-3-1.c'
  102. def test_missing_tab(self):
  103. email = self.from_patch_glob('0031')
  104. assert len(email.errors) == 2
  105. err = email.errors[0]
  106. assert err.message == 'line should start with a tab'
  107. assert err.line == ' * cfgloopanal.c (average_num_loop_insns): ' \
  108. 'Free bbs when early'
  109. def test_leading_changelog_format(self):
  110. email = self.from_patch_glob('0184')
  111. assert len(email.errors) == 4
  112. assert email.errors[0].line == 'gcc/c-family/c-cppbuiltins.c'
  113. assert email.errors[2].line == 'gcc/c-family/c-cppbuiltin.c'
  114. def test_cannot_deduce_no_blank_line(self):
  115. email = self.from_patch_glob('0334')
  116. assert len(email.errors) == 1
  117. assert len(email.changelog_entries) == 1
  118. assert email.changelog_entries[0].folder is None
  119. def test_author_lines(self):
  120. email = self.from_patch_glob('0814')
  121. assert not email.errors
  122. assert (email.changelog_entries[0].author_lines ==
  123. [('Martin Jambor <mjambor@suse.cz>', '2020-02-19')])
  124. def test_multiple_authors_and_prs(self):
  125. email = self.from_patch_glob('0735')
  126. assert len(email.changelog_entries) == 1
  127. entry = email.changelog_entries[0]
  128. assert len(entry.author_lines) == 2
  129. assert len(entry.authors) == 2
  130. assert (entry.author_lines[1] ==
  131. ('Bernd Edlinger <bernd.edlinger@hotmail.de>', None))
  132. def test_multiple_prs(self):
  133. email = self.from_patch_glob('1699')
  134. assert len(email.changelog_entries) == 2
  135. assert len(email.changelog_entries[0].prs) == 2
  136. def test_missing_PR_component(self):
  137. email = self.from_patch_glob('0735')
  138. assert len(email.errors) == 1
  139. assert email.errors[0].message == 'missing PR component'
  140. def test_invalid_PR_component(self):
  141. email = self.from_patch_glob('0198')
  142. assert len(email.errors) == 1
  143. assert email.errors[0].message == 'invalid PR component'
  144. def test_additional_author_list(self):
  145. email = self.from_patch_glob('0342')
  146. msg = 'additional author must be indented ' \
  147. 'with one tab and four spaces'
  148. assert email.errors[1].message == msg
  149. def test_trailing_whitespaces(self):
  150. email = self.get_git_email('trailing-whitespaces.patch')
  151. assert len(email.errors) == 3
  152. def test_space_after_asterisk(self):
  153. email = self.from_patch_glob('1999')
  154. assert len(email.errors) == 1
  155. assert email.errors[0].message == 'one space should follow asterisk'
  156. def test_long_lines(self):
  157. email = self.get_git_email('long-lines.patch')
  158. assert len(email.errors) == 1
  159. assert email.errors[0].message == 'line exceeds 100 character limit'
  160. def test_new_files(self):
  161. email = self.from_patch_glob('0030')
  162. assert not email.errors
  163. def test_wrong_changelog_location(self):
  164. email = self.from_patch_glob('0043')
  165. assert len(email.errors) == 2
  166. assert (email.errors[0].message ==
  167. 'wrong ChangeLog location "gcc", should be "gcc/testsuite"')
  168. def test_single_author_name(self):
  169. email = self.from_patch_glob('1975')
  170. assert len(email.changelog_entries) == 2
  171. assert len(email.changelog_entries[0].author_lines) == 1
  172. assert len(email.changelog_entries[1].author_lines) == 1
  173. def test_bad_first_line(self):
  174. email = self.from_patch_glob('0413')
  175. assert len(email.errors) == 1
  176. def test_co_authored_by(self):
  177. email = self.from_patch_glob('1850')
  178. assert email.co_authors == ['Jakub Jelinek <jakub@redhat.com>']
  179. output_entries = list(email.to_changelog_entries())
  180. assert len(output_entries) == 2
  181. ent0 = output_entries[0]
  182. assert ent0[1].startswith('2020-04-16 Martin Liska '
  183. '<mliska@suse.cz>\n\t'
  184. ' Jakub Jelinek <jakub@redhat.com>')
  185. def test_multiple_co_author_formats(self):
  186. email = self.get_git_email('co-authored-by.patch')
  187. assert len(email.co_authors) == 3
  188. assert email.co_authors[0] == 'Jakub Jelinek <jakub@redhat.com>'
  189. assert email.co_authors[1] == 'John Miller <jm@example.com>'
  190. assert email.co_authors[2] == 'John Miller2 <jm2@example.com>'
  191. def test_new_file_added_entry(self):
  192. email = self.from_patch_glob('1957')
  193. output_entries = list(email.to_changelog_entries())
  194. assert len(output_entries) == 2
  195. needle = ('\t* g++.dg/cpp2a/lambda-generic-variadic20.C'
  196. ': New file.')
  197. assert output_entries[1][1].endswith(needle)
  198. assert email.changelog_entries[1].prs == ['PR c++/94546']
  199. def test_global_pr_entry(self):
  200. email = self.from_patch_glob('2004')
  201. assert not email.errors
  202. assert email.changelog_entries[0].prs == ['PR other/94629']
  203. def test_unique_prs(self):
  204. email = self.get_git_email('pr-check1.patch')
  205. assert not email.errors
  206. assert email.changelog_entries[0].prs == ['PR ipa/12345']
  207. assert email.changelog_entries[1].prs == []
  208. def test_multiple_prs_not_added(self):
  209. email = self.from_patch_glob('0002-Add-patch_are')
  210. assert not email.errors
  211. assert email.changelog_entries[0].prs == ['PR target/93492']
  212. assert email.changelog_entries[1].prs == ['PR target/12345']
  213. assert email.changelog_entries[2].prs == []
  214. assert email.changelog_entries[2].folder == 'gcc/testsuite'
  215. def test_strict_mode(self):
  216. email = self.from_patch_glob('0001-Add-patch_are')
  217. msg = 'ChangeLog, DATESTAMP, BASE-VER and DEV-PHASE updates should ' \
  218. 'be done separately from normal commits'
  219. assert email.errors[0].message.startswith(msg)
  220. def test_strict_mode_normal_patch(self):
  221. email = self.get_git_email('0001-Just-test-it.patch')
  222. assert not email.errors
  223. def test_strict_mode_datestamp_only(self):
  224. email = self.get_git_email('0002-Bump-date.patch')
  225. assert not email.errors
  226. def test_wrong_changelog_entry(self):
  227. email = self.from_patch_glob('0020-IPA-Avoid')
  228. msg = 'first line should start with a tab, an asterisk and a space'
  229. assert (email.errors[0].message == msg)
  230. def test_cherry_pick_format(self):
  231. email = self.from_patch_glob('0001-c-Alias.patch')
  232. assert not email.errors
  233. def test_signatures(self):
  234. email = self.from_patch_glob('0001-RISC-V-Make-unique.patch')
  235. assert not email.errors
  236. assert len(email.changelog_entries) == 1
  237. def test_duplicate_top_level_author(self):
  238. email = self.from_patch_glob('0001-Fortran-ProcPtr-function.patch')
  239. assert not email.errors
  240. assert len(email.changelog_entries[0].author_lines) == 1
  241. def test_dr_entry(self):
  242. email = self.from_patch_glob('0001-c-C-20-DR-2237.patch')
  243. assert email.changelog_entries[0].prs == ['DR 2237']
  244. def test_changes_only_in_ignored_location(self):
  245. email = self.from_patch_glob('0001-go-in-ignored-location.patch')
  246. assert not email.errors
  247. def test_changelog_for_ignored_location(self):
  248. email = self.from_patch_glob('0001-Update-merge.sh-to-reflect.patch')
  249. assert (email.changelog_entries[0].lines[0]
  250. == '\t* LOCAL_PATCHES: Use git hash instead of SVN id.')
  251. def test_multiline_file_list(self):
  252. email = self.from_patch_glob(
  253. '0001-Ada-Reuse-Is_Package_Or_Generic_Package-where-possib.patch')
  254. assert (email.changelog_entries[0].files
  255. == ['contracts.adb', 'einfo.adb', 'exp_ch9.adb',
  256. 'sem_ch12.adb', 'sem_ch4.adb', 'sem_ch7.adb',
  257. 'sem_ch8.adb', 'sem_elab.adb', 'sem_type.adb',
  258. 'sem_util.adb'])
  259. @unittest.skipIf(not unidiff_supports_renaming,
  260. 'Newer version of unidiff is needed (0.6.0+)')
  261. def test_renamed_file(self):
  262. email = self.from_patch_glob(
  263. '0001-Ada-Add-support-for-XDR-streaming-in-the-default-run.patch')
  264. assert not email.errors
  265. def test_duplicite_author_lines(self):
  266. email = self.from_patch_glob('0001-Fortran-type-is-real-kind-1.patch')
  267. assert (email.changelog_entries[0].author_lines[0][0]
  268. == 'Steven G. Kargl <kargl@gcc.gnu.org>')
  269. assert (email.changelog_entries[0].author_lines[1][0]
  270. == 'Mark Eggleston <markeggleston@gcc.gnu.org>')
  271. def test_missing_change_description(self):
  272. email = self.from_patch_glob('0001-Missing-change-description.patch')
  273. assert len(email.errors) == 2
  274. assert email.errors[0].message == 'missing description of a change'
  275. assert email.errors[1].message == 'missing description of a change'
  276. def test_libstdcxx_html_regenerated(self):
  277. email = self.from_patch_glob('0001-Fix-text-of-hyperlink')
  278. assert not email.errors
  279. email = self.from_patch_glob('0002-libstdc-Fake-test-change-1.patch')
  280. assert len(email.errors) == 1
  281. msg = "pattern doesn't match any changed files"
  282. assert email.errors[0].message == msg
  283. assert email.errors[0].line == 'libstdc++-v3/doc/html/'
  284. email = self.from_patch_glob('0003-libstdc-Fake-test-change-2.patch')
  285. assert len(email.errors) == 1
  286. msg = 'changed file not mentioned in a ChangeLog'
  287. assert email.errors[0].message == msg
  288. def test_not_deduce(self):
  289. email = self.from_patch_glob('0001-configure.patch')
  290. assert not email.errors
  291. assert len(email.changelog_entries) == 2
  292. def test_parse_git_name_status(self):
  293. modified_files = GitCommit.parse_git_name_status(NAME_STATUS1)
  294. assert len(modified_files) == 3
  295. assert modified_files[1] == ('gcc/ada/libgnat/s-atopar.adb', 'D')
  296. assert modified_files[2] == ('gcc/ada/libgnat/s-aoinar.adb', 'A')
  297. def test_backport(self):
  298. email = self.from_patch_glob('0001-asan-fix-RTX-emission.patch')
  299. assert not email.errors
  300. expected_hash = '8cff672cb9a132d3d3158c2edfc9a64b55292b80'
  301. assert email.cherry_pick_commit == expected_hash
  302. assert len(email.changelog_entries) == 1
  303. entry = list(email.to_changelog_entries())[0][1]
  304. assert entry.startswith('2020-06-11 Martin Liska <mliska@suse.cz>')
  305. assert '\tBackported from master:' in entry
  306. assert '\t2020-06-11 Martin Liska <mliska@suse.cz>' in entry
  307. assert '\t\t Jakub Jelinek <jakub@redhat.com>' in entry
  308. def test_backport_double_cherry_pick(self):
  309. email = self.from_patch_glob('double-cherry-pick.patch')
  310. assert email.errors[0].message.startswith('multiple cherry pick lines')
  311. def test_square_and_lt_gt(self):
  312. email = self.from_patch_glob('0001-Check-for-more-missing')
  313. assert not email.errors
  314. def test_empty_parenthesis(self):
  315. email = self.from_patch_glob('0001-tree-optimization-97633-fix')
  316. assert len(email.errors) == 1
  317. assert email.errors[0].message == 'empty group "()" found'
  318. def test_emptry_entry_desc(self):
  319. email = self.from_patch_glob('0001-c-Set-CALL_FROM_NEW_OR')
  320. assert len(email.errors) == 1
  321. assert email.errors[0].message == 'missing description of a change'
  322. def test_emptry_entry_desc_2(self):
  323. email = self.from_patch_glob('0001-lto-fix-LTO-debug')
  324. assert not email.errors
  325. assert len(email.changelog_entries) == 1
  326. def test_wildcard_in_subdir(self):
  327. email = self.from_patch_glob('0001-Wildcard-subdirs.patch')
  328. assert len(email.changelog_entries) == 1
  329. err = email.errors[0]
  330. assert err.message == "pattern doesn't match any changed files"
  331. assert err.line == 'libstdc++-v3/testsuite/28_regex_not-existing/'
  332. def test_unicode_chars_in_filename(self):
  333. email = self.from_patch_glob('0001-Add-horse.patch')
  334. assert not email.errors
  335. def test_bad_unicode_chars_in_filename(self):
  336. email = self.from_patch_glob('0001-Add-horse2.patch')
  337. assert not email.errors
  338. assert email.changelog_entries[0].files == ['koníček.txt']
  339. def test_modification_of_old_changelog(self):
  340. email = self.from_patch_glob('0001-fix-old-ChangeLog.patch')
  341. assert not email.errors
  342. def test_multiline_parentheses(self):
  343. email = self.from_patch_glob('0001-Add-macro.patch')
  344. assert not email.errors
  345. def test_multiline_bad_parentheses(self):
  346. email = self.from_patch_glob('0002-Wrong-macro-changelog.patch')
  347. assert email.errors[0].message == 'bad parentheses wrapping'
  348. assert email.errors[0].line == ' * config/i386/i386.md (*fix_trunc<mode>_i387_1,'
  349. def test_changelog_removal(self):
  350. email = self.from_patch_glob('0001-ChangeLog-removal.patch')
  351. assert not email.errors
  352. def test_long_filenames(self):
  353. email = self.from_patch_glob('0001-long-filenames')
  354. assert not email.errors
  355. def test_multi_same_file(self):
  356. email = self.from_patch_glob('0001-OpenMP-Fix-SIMT')
  357. assert email.errors[0].message == 'same file specified multiple times'
  358. def test_pr_only_in_subject(self):
  359. email = self.from_patch_glob('0001-rs6000-Support-doubleword')
  360. assert (email.errors[0].message ==
  361. 'PR 100085 in subject but not in changelog')
  362. def test_wrong_pr_comp_in_subject(self):
  363. email = self.from_patch_glob('pr-wrong-comp.patch')
  364. assert email.errors[0].message == 'invalid PR component in subject'
  365. def test_copyright_years(self):
  366. email = self.from_patch_glob('copyright-years.patch')
  367. assert not email.errors
  368. def test_non_ascii_email(self):
  369. email = self.from_patch_glob('non-ascii-email.patch')
  370. assert (email.errors[0].message ==
  371. 'non-ASCII characters in git commit email address (jbglaw@ług-owl.de)')
  372. def test_new_file_in_root_folder(self):
  373. email = self.from_patch_glob('toplev-new-file.patch')
  374. assert (email.errors[0].message ==
  375. 'new file in the top-level folder not mentioned in a ChangeLog')