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, strict=False): 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, strict) 71 72 def from_patch_glob(self, name, strict=False): 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], strict) 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('0001-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 True) 260 msg = 'ChangeLog, DATESTAMP, BASE-VER and DEV-PHASE updates should ' \ 261 'be done separately from normal commits' 262 assert email.errors[0].message == msg 263 264 def test_strict_mode_normal_patch(self): 265 email = self.get_git_email('0001-Just-test-it.patch', True) 266 assert not email.errors 267 268 def test_strict_mode_datestamp_only(self): 269 email = self.get_git_email('0002-Bump-date.patch', True) 270 assert not email.errors 271 272 def test_wrong_changelog_entry(self): 273 email = self.from_patch_glob('0020-IPA-Avoid') 274 msg = 'first line should start with a tab, an asterisk and a space' 275 assert (email.errors[0].message == msg) 276 277 def test_cherry_pick_format(self): 278 email = self.from_patch_glob('0001-c-Alias.patch') 279 assert not email.errors 280 281 def test_signatures(self): 282 email = self.from_patch_glob('0001-RISC-V-Make-unique.patch') 283 assert not email.errors 284 assert len(email.changelog_entries) == 1 285 286 def test_duplicate_top_level_author(self): 287 email = self.from_patch_glob('0001-Fortran-ProcPtr-function.patch') 288 assert not email.errors 289 assert len(email.changelog_entries[0].author_lines) == 1 290 291 def test_dr_entry(self): 292 email = self.from_patch_glob('0001-c-C-20-DR-2237.patch') 293 assert email.changelog_entries[0].prs == ['DR 2237'] 294 295 def test_changes_only_in_ignored_location(self): 296 email = self.from_patch_glob('0001-go-in-ignored-location.patch') 297 assert not email.errors 298 299 def test_changelog_for_ignored_location(self): 300 email = self.from_patch_glob('0001-Update-merge.sh-to-reflect.patch') 301 assert (email.changelog_entries[0].lines[0] 302 == '\t* LOCAL_PATCHES: Use git hash instead of SVN id.') 303 304 def test_multiline_file_list(self): 305 email = self.from_patch_glob( 306 '0001-Ada-Reuse-Is_Package_Or_Generic_Package-where-possib.patch') 307 assert (email.changelog_entries[0].files 308 == ['contracts.adb', 'einfo.adb', 'exp_ch9.adb', 309 'sem_ch12.adb', 'sem_ch4.adb', 'sem_ch7.adb', 310 'sem_ch8.adb', 'sem_elab.adb', 'sem_type.adb', 311 'sem_util.adb']) 312 313 @unittest.skipIf(not unidiff_supports_renaming, 314 'Newer version of unidiff is needed (0.6.0+)') 315 def test_renamed_file(self): 316 email = self.from_patch_glob( 317 '0001-Ada-Add-support-for-XDR-streaming-in-the-default-run.patch') 318 assert not email.errors 319 320 def test_duplicite_author_lines(self): 321 email = self.from_patch_glob('0001-Fortran-type-is-real-kind-1.patch') 322 assert (email.changelog_entries[0].author_lines[0][0] 323 == 'Steven G. Kargl <kargl@gcc.gnu.org>') 324 assert (email.changelog_entries[0].author_lines[1][0] 325 == 'Mark Eggleston <markeggleston@gcc.gnu.org>') 326 327 def test_missing_change_description(self): 328 email = self.from_patch_glob('0001-Missing-change-description.patch') 329 assert len(email.errors) == 2 330 assert email.errors[0].message == 'missing description of a change' 331 assert email.errors[1].message == 'missing description of a change' 332 333 def test_libstdcxx_html_regenerated(self): 334 email = self.from_patch_glob('0001-Fix-text-of-hyperlink') 335 assert not email.errors 336 email = self.from_patch_glob('0002-libstdc-Fake-test-change-1.patch') 337 assert len(email.errors) == 1 338 msg = "pattern doesn't match any changed files" 339 assert email.errors[0].message == msg 340 assert email.errors[0].line == 'libstdc++-v3/doc/html/' 341 email = self.from_patch_glob('0003-libstdc-Fake-test-change-2.patch') 342 assert len(email.errors) == 1 343 msg = 'changed file not mentioned in a ChangeLog' 344 assert email.errors[0].message == msg 345 346 def test_not_deduce(self): 347 email = self.from_patch_glob('0001-configure.patch') 348 assert not email.errors 349 assert len(email.changelog_entries) == 2 350 351 def test_parse_git_name_status(self): 352 modified_files = GitCommit.parse_git_name_status(NAME_STATUS1) 353 assert len(modified_files) == 3 354 assert modified_files[1] == ('gcc/ada/libgnat/s-atopar.adb', 'D') 355 assert modified_files[2] == ('gcc/ada/libgnat/s-aoinar.adb', 'A') 356 357 def test_backport(self): 358 email = self.from_patch_glob('0001-asan-fix-RTX-emission.patch') 359 assert not email.errors 360 expected_hash = '8cff672cb9a132d3d3158c2edfc9a64b55292b80' 361 assert email.cherry_pick_commit == expected_hash 362 assert len(email.changelog_entries) == 1 363 entry = list(email.to_changelog_entries())[0][1] 364 assert entry.startswith('2020-06-11 Martin Liska <mliska@suse.cz>') 365 assert '\tBackported from master:' in entry 366 assert '\t2020-06-11 Martin Liska <mliska@suse.cz>' in entry 367 assert '\t\t Jakub Jelinek <jakub@redhat.com>' in entry 368 369 def test_backport_double_cherry_pick(self): 370 email = self.from_patch_glob('double-cherry-pick.patch') 371 assert email.errors[0].message.startswith('multiple cherry pick lines') 372 373 def test_square_and_lt_gt(self): 374 email = self.from_patch_glob('0001-Check-for-more-missing') 375 assert not email.errors 376 377 def test_empty_parenthesis(self): 378 email = self.from_patch_glob('0001-tree-optimization-97633-fix') 379 assert len(email.errors) == 1 380 assert email.errors[0].message == 'empty group "()" found' 381 382 def test_emptry_entry_desc(self): 383 email = self.from_patch_glob('0001-c-Set-CALL_FROM_NEW_OR') 384 assert len(email.errors) == 1 385 assert email.errors[0].message == 'missing description of a change' 386 387 def test_emptry_entry_desc_2(self): 388 email = self.from_patch_glob('0001-lto-fix-LTO-debug') 389 assert not email.errors 390 assert len(email.changelog_entries) == 1 391 392 def test_wildcard_in_subdir(self): 393 email = self.from_patch_glob('0001-Wildcard-subdirs.patch') 394 assert len(email.changelog_entries) == 1 395 err = email.errors[0] 396 assert err.message == "pattern doesn't match any changed files" 397 assert err.line == 'libstdc++-v3/testsuite/28_regex_not-existing/' 398 399 def test_unicode_chars_in_filename(self): 400 email = self.from_patch_glob('0001-Add-horse.patch') 401 assert not email.errors 402 403 def test_bad_unicode_chars_in_filename(self): 404 email = self.from_patch_glob('0001-Add-horse2.patch') 405 assert not email.errors 406 assert email.changelog_entries[0].files == ['koníček.txt'] 407