|  | # Checks related to the patch's author | 
|  | # | 
|  | # Copyright (C) 2016 Intel Corporation | 
|  | # | 
|  | # SPDX-License-Identifier: GPL-2.0-only | 
|  |  | 
|  | import base | 
|  | import collections | 
|  | import parse_shortlog | 
|  | import parse_signed_off_by | 
|  | import pyparsing | 
|  | import subprocess | 
|  | from data import PatchTestInput | 
|  |  | 
|  | def headlog(): | 
|  | output = subprocess.check_output( | 
|  | "cd %s; git log --pretty='%%h#%%aN#%%cD:#%%s' -1" % PatchTestInput.repodir, | 
|  | universal_newlines=True, | 
|  | shell=True | 
|  | ) | 
|  | return output.split('#') | 
|  |  | 
|  | class TestMbox(base.Base): | 
|  |  | 
|  | auh_email = 'auh@auh.yoctoproject.org' | 
|  |  | 
|  | invalids = [pyparsing.Regex("^Upgrade Helper.+"), | 
|  | pyparsing.Regex(auh_email), | 
|  | pyparsing.Regex("uh@not\.set"), | 
|  | pyparsing.Regex("\S+@example\.com")] | 
|  |  | 
|  | rexp_detect = pyparsing.Regex('\[\s?YOCTO.*\]') | 
|  | rexp_validation = pyparsing.Regex('\[(\s?YOCTO\s?#\s?(\d+)\s?,?)+\]') | 
|  | revert_shortlog_regex = pyparsing.Regex('Revert\s+".*"') | 
|  | signoff_prog = parse_signed_off_by.signed_off_by | 
|  | revert_shortlog_regex = pyparsing.Regex('Revert\s+".*"') | 
|  | maxlength = 90 | 
|  |  | 
|  | # base paths of main yocto project sub-projects | 
|  | paths = { | 
|  | 'oe-core': ['meta-selftest', 'meta-skeleton', 'meta', 'scripts'], | 
|  | 'bitbake': ['bitbake'], | 
|  | 'documentation': ['documentation'], | 
|  | 'poky': ['meta-poky','meta-yocto-bsp'], | 
|  | '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'] | 
|  | } | 
|  |  | 
|  | # scripts folder is a mix of oe-core and poky, most is oe-core code except: | 
|  | poky_scripts = ['scripts/yocto-bsp', 'scripts/yocto-kernel', 'scripts/yocto-layer', 'scripts/lib/bsp'] | 
|  |  | 
|  | Project = collections.namedtuple('Project', ['name', 'listemail', 'gitrepo', 'paths']) | 
|  |  | 
|  | bitbake = Project(name='Bitbake', listemail='bitbake-devel@lists.openembedded.org', gitrepo='http://git.openembedded.org/bitbake/', paths=paths['bitbake']) | 
|  | doc     = Project(name='Documentantion', listemail='yocto@yoctoproject.org', gitrepo='http://git.yoctoproject.org/cgit/cgit.cgi/yocto-docs/', paths=paths['documentation']) | 
|  | poky    = Project(name='Poky', listemail='poky@yoctoproject.org', gitrepo='http://git.yoctoproject.org/cgit/cgit.cgi/poky/', paths=paths['poky']) | 
|  | oe      = Project(name='oe', listemail='openembedded-devel@lists.openembedded.org', gitrepo='http://git.openembedded.org/meta-openembedded/', paths=paths['oe']) | 
|  |  | 
|  |  | 
|  | def test_signed_off_by_presence(self): | 
|  | for commit in TestMbox.commits: | 
|  | # skip those patches that revert older commits, these do not required the tag presence | 
|  | if self.revert_shortlog_regex.search_string(commit.shortlog): | 
|  | continue | 
|  | if not self.signoff_prog.search_string(commit.payload): | 
|  | self.fail('Mbox is missing Signed-off-by. Add it manually or with "git commit --amend -s"', | 
|  | commit=commit) | 
|  |  | 
|  | def test_shortlog_format(self): | 
|  | for commit in TestMbox.commits: | 
|  | shortlog = commit.shortlog | 
|  | if not shortlog.strip(): | 
|  | self.skip('Empty shortlog, no reason to execute shortlog format test') | 
|  | else: | 
|  | # no reason to re-check on revert shortlogs | 
|  | if shortlog.startswith('Revert "'): | 
|  | continue | 
|  | try: | 
|  | parse_shortlog.shortlog.parseString(shortlog) | 
|  | except pyparsing.ParseException as pe: | 
|  | self.fail('Commit shortlog (first line of commit message) should follow the format "<target>: <summary>"', | 
|  | commit=commit) | 
|  |  | 
|  | def test_shortlog_length(self): | 
|  | for commit in TestMbox.commits: | 
|  | # no reason to re-check on revert shortlogs | 
|  | shortlog = commit.shortlog | 
|  | if shortlog.startswith('Revert "'): | 
|  | continue | 
|  | l = len(shortlog) | 
|  | if l > self.maxlength: | 
|  | self.fail('Edit shortlog so that it is %d characters or less (currently %d characters)' % (self.maxlength, l), | 
|  | commit=commit) | 
|  |  | 
|  | def test_series_merge_on_head(self): | 
|  | self.skip("Merge test is disabled for now") | 
|  | if PatchTestInput.repo.branch != "master": | 
|  | self.skip("Skipping merge test since patch is not intended for master branch. Target detected is %s" % PatchTestInput.repo.branch) | 
|  | if not PatchTestInput.repo.ismerged: | 
|  | commithash, author, date, shortlog = headlog() | 
|  | self.fail('Series does not apply on top of target branch %s' % PatchTestInput.repo.branch, | 
|  | data=[('Targeted branch', '%s (currently at %s)' % (PatchTestInput.repo.branch, commithash))]) | 
|  |  | 
|  | def test_target_mailing_list(self): | 
|  | """In case of merge failure, check for other targeted projects""" | 
|  | if PatchTestInput.repo.ismerged: | 
|  | self.skip('Series merged, no reason to check other mailing lists') | 
|  |  | 
|  | # a meta project may be indicted in the message subject, if this is the case, just fail | 
|  | # TODO: there may be other project with no-meta prefix, we also need to detect these | 
|  | project_regex = pyparsing.Regex("\[(?P<project>meta-.+)\]") | 
|  | for commit in TestMbox.commits: | 
|  | match = project_regex.search_string(commit.subject) | 
|  | if match: | 
|  | self.fail('Series sent to the wrong mailing list or some patches from the series correspond to different mailing lists', | 
|  | commit=commit) | 
|  |  | 
|  | for patch in self.patchset: | 
|  | folders = patch.path.split('/') | 
|  | base_path = folders[0] | 
|  | for project in [self.bitbake, self.doc, self.oe, self.poky]: | 
|  | if base_path in  project.paths: | 
|  | self.fail('Series sent to the wrong mailing list or some patches from the series correspond to different mailing lists', | 
|  | data=[('Suggested ML', '%s [%s]' % (project.listemail, project.gitrepo)), | 
|  | ('Patch\'s path:', patch.path)]) | 
|  |  | 
|  | # check for poky's scripts code | 
|  | if base_path.startswith('scripts'): | 
|  | for poky_file in self.poky_scripts: | 
|  | if patch.path.startswith(poky_file): | 
|  | self.fail('Series sent to the wrong mailing list or some patches from the series correspond to different mailing lists', | 
|  | data=[('Suggested ML', '%s [%s]' % (self.poky.listemail, self.poky.gitrepo)),('Patch\'s path:', patch.path)]) | 
|  |  | 
|  | def test_mbox_format(self): | 
|  | if self.unidiff_parse_error: | 
|  | self.fail('Series has malformed diff lines. Create the series again using git-format-patch and ensure it applies using git am', | 
|  | data=[('Diff line',self.unidiff_parse_error)]) | 
|  |  | 
|  | def test_commit_message_presence(self): | 
|  | for commit in TestMbox.commits: | 
|  | if not commit.commit_message.strip(): | 
|  | self.fail('Please include a commit message on your patch explaining the change', commit=commit) | 
|  |  | 
|  | def test_bugzilla_entry_format(self): | 
|  | for commit in TestMbox.commits: | 
|  | if not self.rexp_detect.search_string(commit.commit_message): | 
|  | self.skip("No bug ID found") | 
|  | elif not self.rexp_validation.search_string(commit.commit_message): | 
|  | self.fail('Bugzilla issue ID is not correctly formatted - specify it with format: "[YOCTO #<bugzilla ID>]"', commit=commit) | 
|  |  | 
|  | def test_author_valid(self): | 
|  | for commit in self.commits: | 
|  | for invalid in self.invalids: | 
|  | if invalid.search_string(commit.author): | 
|  | self.fail('Invalid author %s. Resend the series with a valid patch author' % commit.author, commit=commit) | 
|  |  | 
|  | def test_non_auh_upgrade(self): | 
|  | for commit in self.commits: | 
|  | if self.auh_email in commit.payload: | 
|  | self.fail('Invalid author %s. Resend the series with a valid patch author' % self.auh_email, commit=commit) |