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