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