test_mbox.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. # Checks related to the patch's author
  2. #
  3. # Copyright (C) 2016 Intel Corporation
  4. #
  5. # SPDX-License-Identifier: GPL-2.0-only
  6. import base
  7. import collections
  8. import patchtest_patterns
  9. import pyparsing
  10. import re
  11. import subprocess
  12. from patchtest_parser import PatchtestParser
  13. def headlog():
  14. output = subprocess.check_output(
  15. "cd %s; git log --pretty='%%h#%%aN#%%cD:#%%s' -1" % PatchtestParser.repodir,
  16. universal_newlines=True,
  17. shell=True
  18. )
  19. return output.split('#')
  20. class TestMbox(base.Base):
  21. # base paths of main yocto project sub-projects
  22. paths = {
  23. 'oe-core': ['meta-selftest', 'meta-skeleton', 'meta', 'scripts'],
  24. 'bitbake': ['bitbake'],
  25. 'documentation': ['documentation'],
  26. 'poky': ['meta-poky','meta-yocto-bsp'],
  27. 'oe': ['meta-gpe', 'meta-gnome', 'meta-efl', 'meta-networking', 'meta-multimedia','meta-initramfs', 'meta-ruby', 'contrib', 'meta-xfce', 'meta-filesystems', 'meta-perl', 'meta-webserver', 'meta-systemd', 'meta-oe', 'meta-python']
  28. }
  29. # scripts folder is a mix of oe-core and poky, most is oe-core code except:
  30. poky_scripts = ['scripts/yocto-bsp', 'scripts/yocto-kernel', 'scripts/yocto-layer', 'scripts/lib/bsp']
  31. Project = collections.namedtuple('Project', ['name', 'listemail', 'gitrepo', 'paths'])
  32. bitbake = Project(name='Bitbake', listemail='bitbake-devel@lists.openembedded.org', gitrepo='http://git.openembedded.org/bitbake/', paths=paths['bitbake'])
  33. doc = Project(name='Documentantion', listemail='yocto@yoctoproject.org', gitrepo='http://git.yoctoproject.org/cgit/cgit.cgi/yocto-docs/', paths=paths['documentation'])
  34. poky = Project(name='Poky', listemail='poky@yoctoproject.org', gitrepo='http://git.yoctoproject.org/cgit/cgit.cgi/poky/', paths=paths['poky'])
  35. oe = Project(name='oe', listemail='openembedded-devel@lists.openembedded.org', gitrepo='http://git.openembedded.org/meta-openembedded/', paths=paths['oe'])
  36. def test_signed_off_by_presence(self):
  37. for commit in self.commits:
  38. # skip those patches that revert older commits, these do not required the tag presence
  39. if patchtest_patterns.mbox_revert_shortlog_regex.search_string(commit.shortlog):
  40. continue
  41. if not patchtest_patterns.signed_off_by.search_string(commit.payload):
  42. self.fail(
  43. 'Mbox is missing Signed-off-by. Add it manually or with "git commit --amend -s"',
  44. commit=commit,
  45. )
  46. def test_shortlog_format(self):
  47. for commit in self.commits:
  48. shortlog = commit.shortlog
  49. if not shortlog.strip():
  50. self.skip('Empty shortlog, no reason to execute shortlog format test')
  51. else:
  52. # no reason to re-check on revert shortlogs
  53. if shortlog.startswith('Revert "'):
  54. continue
  55. try:
  56. patchtest_patterns.shortlog.parseString(shortlog)
  57. except pyparsing.ParseException as pe:
  58. self.fail('Commit shortlog (first line of commit message) should follow the format "<target>: <summary>"',
  59. commit=commit)
  60. def test_shortlog_length(self):
  61. for commit in self.commits:
  62. # no reason to re-check on revert shortlogs
  63. shortlog = re.sub(r'^(\[.*?\])+ ', '', commit.shortlog)
  64. if shortlog.startswith('Revert "'):
  65. continue
  66. l = len(shortlog)
  67. if l > patchtest_patterns.mbox_shortlog_maxlength:
  68. self.fail(
  69. "Edit shortlog so that it is %d characters or less (currently %d characters)"
  70. % (patchtest_patterns.mbox_shortlog_maxlength, l),
  71. commit=commit,
  72. )
  73. def test_series_merge_on_head(self):
  74. self.skip("Merge test is disabled for now")
  75. if PatchtestParser.repo.patch.branch != "master":
  76. self.skip(
  77. "Skipping merge test since patch is not intended"
  78. " for master branch. Target detected is %s"
  79. % PatchtestParser.repo.patch.branch
  80. )
  81. if not PatchtestParser.repo.canbemerged():
  82. commithash, author, date, shortlog = headlog()
  83. self.fail(
  84. "Series does not apply on top of target branch %s"
  85. % PatchtestParser.repo.patch.branch,
  86. data=[
  87. (
  88. "Targeted branch",
  89. "%s (currently at %s)"
  90. % (PatchtestParser.repo.patch.branch, commithash),
  91. )
  92. ],
  93. )
  94. def test_target_mailing_list(self):
  95. """Check for other targeted projects"""
  96. # a meta project may be indicted in the message subject, if this is the case, just fail
  97. # TODO: there may be other project with no-meta prefix, we also need to detect these
  98. project_regex = pyparsing.Regex(r"\[(?P<project>meta-.+)\]")
  99. for commit in self.commits:
  100. match = project_regex.search_string(commit.subject)
  101. if match:
  102. self.fail('Series sent to the wrong mailing list or some patches from the series correspond to different mailing lists',
  103. commit=commit)
  104. for patch in self.patchset:
  105. folders = patch.path.split('/')
  106. base_path = folders[0]
  107. for project in [self.bitbake, self.doc, self.oe, self.poky]:
  108. if base_path in project.paths:
  109. self.fail('Series sent to the wrong mailing list or some patches from the series correspond to different mailing lists',
  110. data=[('Suggested ML', '%s [%s]' % (project.listemail, project.gitrepo)),
  111. ('Patch\'s path:', patch.path)])
  112. # check for poky's scripts code
  113. if base_path.startswith('scripts'):
  114. for poky_file in self.poky_scripts:
  115. if patch.path.startswith(poky_file):
  116. self.fail('Series sent to the wrong mailing list or some patches from the series correspond to different mailing lists',
  117. data=[('Suggested ML', '%s [%s]' % (self.poky.listemail, self.poky.gitrepo)),('Patch\'s path:', patch.path)])
  118. def test_mbox_format(self):
  119. if self.unidiff_parse_error:
  120. self.fail('Series has malformed diff lines. Create the series again using git-format-patch and ensure it applies using git am',
  121. data=[('Diff line',self.unidiff_parse_error)])
  122. def test_commit_message_presence(self):
  123. for commit in self.commits:
  124. if not commit.commit_message.strip():
  125. self.fail('Please include a commit message on your patch explaining the change', commit=commit)
  126. # This may incorrectly report a failure if something such as a
  127. # Python decorator is included in the commit message, but this
  128. # scenario is much less common than the username case it is written
  129. # to protect against
  130. def test_commit_message_user_tags(self):
  131. for commit in self.commits:
  132. if patchtest_patterns.mbox_github_username.search_string(commit.commit_message):
  133. self.fail('Mbox includes one or more GitHub-style username tags. Ensure that any "@" symbols are stripped out of usernames', commit=commit)
  134. def test_bugzilla_entry_format(self):
  135. for commit in self.commits:
  136. if not patchtest_patterns.mbox_bugzilla.search_string(commit.commit_message):
  137. self.skip("No bug ID found")
  138. elif not patchtest_patterns.mbox_bugzilla_validation.search_string(
  139. commit.commit_message
  140. ):
  141. self.fail(
  142. 'Bugzilla issue ID is not correctly formatted - specify it with format: "[YOCTO #<bugzilla ID>]"',
  143. commit=commit,
  144. )
  145. def test_author_valid(self):
  146. for commit in self.commits:
  147. for invalid in patchtest_patterns.invalid_submitters:
  148. if invalid.search_string(commit.author):
  149. self.fail('Invalid author %s. Resend the series with a valid patch author' % commit.author, commit=commit)
  150. def test_non_auh_upgrade(self):
  151. for commit in self.commits:
  152. if patchtest_patterns.auh_email in commit.commit_message:
  153. self.fail(
  154. "Invalid author %s. Resend the series with a valid patch author"
  155. % patchtest_patterns.auh_email,
  156. commit=commit,
  157. )