patchtest: simplify test directory structure

Consolidate the various mbox tests into a new TestMbox class, metadata
tests into TestMetadata, and patch tests into TestPatch. Also update the
selftest filenames to match the changes. The test contents are not
significantly changed (other than to reference the new class names).
While this doesn't improve overall readability, it does result in more
obvious categorization, and more importantly reduces the number of calls
to setup tinfoil in the tests, resulting in a roughly 25% reduction in
runtime.

Before:

[tgamblin@megalith poky]$ time ./meta/lib/patchtest/selftest/selftest
XPASS: PatchSignedOffBy.test_signed_off_by_presence (file: PatchSignedOffBy.test_signed_off_by_presence.pass)
XFAIL: Shortlog.test_shortlog_format (file: Shortlog.test_shortlog_format.fail)
XFAIL: MboxFormat.test_mbox_format (file: MboxFormat.test_mbox_format.1.fail)
XPASS: Shortlog.test_shortlog_length (file: Shortlog.test_shortlog_length.pass)
XFAIL: CommitMessage.test_commit_message_presence (file: CommitMessage.test_commit_message_presence.fail)
XFAIL: SrcUri.test_src_uri_left_files (file: SrcUri.test_src_uri_left_files.fail)
XPASS: Author.test_author_valid (file: Author.test_author_valid.1.pass)
XFAIL: LicFilesChkSum.test_lic_files_chksum_modified_not_mentioned (file: LicFilesChkSum.test_lic_files_chksum_modified_not_mentioned.fail)
XPASS: CVE.test_cve_tag_format (file: CVE.test_cve_tag_format.pass)
XPASS: CVE.test_cve_presence_in_commit_message (file: CVE.test_cve_presence_in_commit_message.pass)
XFAIL: CVE.test_cve_tag_format (file: CVE.test_cve_tag_format.fail)
XFAIL: Author.test_author_valid (file: Author.test_author_valid.1.fail)
XFAIL: LicFilesChkSum.test_lic_files_chksum_presence (file: LicFilesChkSum.test_lic_files_chksum_presence.fail)
XSKIP: Merge.test_series_merge_on_head (file: Merge.test_series_merge_on_head.2.skip)
XPASS: MboxFormat.test_mbox_format (file: MboxFormat.test_mbox_format.pass)
XFAIL: SignedOffBy.test_signed_off_by_presence (file: SignedOffBy.test_signed_off_by_presence.1.fail)
XPASS: Shortlog.test_shortlog_format (file: Shortlog.test_shortlog_format.pass)
XFAIL: SignedOffBy.test_signed_off_by_presence (file: SignedOffBy.test_signed_off_by_presence.2.fail)
XFAIL: MboxFormat.test_mbox_format (file: MboxFormat.test_mbox_format.2.fail)
XFAIL: Summary.test_summary_presence (file: Summary.test_summary_presence.fail)
XPASS: Author.test_author_valid (file: Author.test_author_valid.2.pass)
XSKIP: Merge.test_series_merge_on_head (file: Merge.test_series_merge_on_head.1.skip)
XPASS: Bugzilla.test_bugzilla_entry_format (file: Bugzilla.test_bugzilla_entry_format.pass)
XFAIL: CVE.test_cve_presence_in_commit_message (file: CVE.test_cve_presence_in_commit_message.fail)
XPASS: SignedOffBy.test_signed_off_by_presence (file: SignedOffBy.test_signed_off_by_presence.pass)
XPASS: LicFilesChkSum.test_lic_files_chksum_presence (file: LicFilesChkSum.test_lic_files_chksum_presence.pass)
XPASS: CommitMessage.test_commit_message_presence (file: CommitMessage.test_commit_message_presence.pass)
XPASS: Summary.test_summary_presence (file: Summary.test_summary_presence.pass)
XPASS: LicFilesChkSum.test_lic_files_chksum_modified_not_mentioned (file: LicFilesChkSum.test_lic_files_chksum_modified_not_mentioned.pass)
XFAIL: Shortlog.test_shortlog_length (file: Shortlog.test_shortlog_length.fail)
XFAIL: PatchSignedOffBy.test_signed_off_by_presence (file: PatchSignedOffBy.test_signed_off_by_presence.fail)
XFAIL: Bugzilla.test_bugzilla_entry_format (file: Bugzilla.test_bugzilla_entry_format.fail)
XPASS: SrcUri.test_src_uri_left_files (file: SrcUri.test_src_uri_left_files.pass)
XFAIL: Author.test_author_valid (file: Author.test_author_valid.2.fail)
============================================================================
Testsuite summary for patchtest
============================================================================
============================================================================

real    24m14.386s
user    1m13.599s
sys     0m21.477s

After:

[tgamblin@megalith poky]$ time ./meta/lib/patchtest/selftest/selftest
XFAIL: TestMbox.test_bugzilla_entry_format (file: TestMbox.test_bugzilla_entry_format.fail)
XPASS: TestMetadata.test_summary_presence (file: TestMetadata.test_summary_presence.pass)
XFAIL: TestMbox.test_mbox_format (file: TestMbox.test_mbox_format.1.fail)
XFAIL: TestMetadata.test_src_uri_left_files (file: TestMetadata.test_src_uri_left_files.fail)
XSKIP: TestMbox.test_series_merge_on_head (file: TestMbox.test_series_merge_on_head.2.skip)
XPASS: TestMbox.test_commit_message_presence (file: TestMbox.test_commit_message_presence.pass)
XFAIL: TestMbox.test_commit_message_presence (file: TestMbox.test_commit_message_presence.fail)
XPASS: TestMbox.test_signed_off_by_presence (file: TestMbox.test_signed_off_by_presence.pass)
XFAIL: TestPatch.test_cve_tag_format (file: TestPatch.test_cve_tag_format.fail)
XFAIL: TestMbox.test_author_valid (file: TestMbox.test_author_valid.1.fail)
XFAIL: TestMbox.test_shortlog_length (file: TestMbox.test_shortlog_length.fail)
XPASS: TestMbox.test_mbox_format (file: TestMbox.test_mbox_format.pass)
XFAIL: TestPatch.test_signed_off_by_presence (file: TestPatch.test_signed_off_by_presence.fail)
XFAIL: TestMbox.test_shortlog_format (file: TestMbox.test_shortlog_format.fail)
XFAIL: TestMbox.test_mbox_format (file: TestMbox.test_mbox_format.2.fail)
XPASS: TestPatch.test_cve_tag_format (file: TestPatch.test_cve_tag_format.pass)
XSKIP: TestMbox.test_series_merge_on_head (file: TestMbox.test_series_merge_on_head.1.skip)
XPASS: TestMbox.test_author_valid (file: TestMbox.test_author_valid.2.pass)
XPASS: TestMetadata.test_lic_files_chksum_modified_not_mentioned (file: TestMetadata.test_lic_files_chksum_modified_not_mentioned.pass)
XPASS: TestMbox.test_bugzilla_entry_format (file: TestMbox.test_bugzilla_entry_format.pass)
XPASS: TestMetadata.test_src_uri_left_files (file: TestMetadata.test_src_uri_left_files.pass)
XPASS: TestMetadata.test_lic_files_chksum_presence (file: TestMetadata.test_lic_files_chksum_presence.pass)
XPASS: TestMbox.test_cve_presence_in_commit_message (file: TestMbox.test_cve_presence_in_commit_message.pass)
XFAIL: TestMbox.test_signed_off_by_presence (file: TestMbox.test_signed_off_by_presence.2.fail)
XFAIL: TestMbox.test_author_valid (file: TestMbox.test_author_valid.2.fail)
XFAIL: TestMetadata.test_lic_files_chksum_presence (file: TestMetadata.test_lic_files_chksum_presence.fail)
XPASS: TestMbox.test_shortlog_format (file: TestMbox.test_shortlog_format.pass)
XPASS: TestMbox.test_author_valid (file: TestMbox.test_author_valid.1.pass)
XPASS: TestPatch.test_signed_off_by_presence (file: TestPatch.test_signed_off_by_presence.pass)
XFAIL: TestMetadata.test_lic_files_chksum_modified_not_mentioned (file: TestMetadata.test_lic_files_chksum_modified_not_mentioned.fail)
XPASS: TestMbox.test_shortlog_length (file: TestMbox.test_shortlog_length.pass)
XFAIL: TestMbox.test_signed_off_by_presence (file: TestMbox.test_signed_off_by_presence.1.fail)
XFAIL: TestMbox.test_cve_presence_in_commit_message (file: TestMbox.test_cve_presence_in_commit_message.fail)
XFAIL: TestMetadata.test_summary_presence (file: TestMetadata.test_summary_presence.fail)
============================================================================
Testsuite summary for patchtest
============================================================================
============================================================================
real    18m39.749s
user    0m41.857s
sys     0m14.708s

(From OE-Core rev: 497e128546b37d6cf5fb86188fff4c7a22526ec8)

Signed-off-by: Trevor Gamblin <tgamblin@baylibre.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
(cherry picked from commit f788592da2fd0e21638ce2c3326675a060ba51cf)
Signed-off-by: Steve Sakoman <steve@sakoman.com>
This commit is contained in:
Trevor Gamblin
2023-10-26 10:07:46 -04:00
committed by Steve Sakoman
parent 5f1c17d70c
commit f575a3bdd5
53 changed files with 436 additions and 603 deletions

View File

@@ -56,7 +56,7 @@ index 547587bef4..76975a6729 100644
-SRC_URI = "file://helloworld.c"
+SRC_URI = "file://helloworld.c \
+ file://CVE-1234-56789.patch \
+ file://0001-Fix-CVE-1234-56789.patch \
+ "
S = "${WORKDIR}"

View File

@@ -0,0 +1,183 @@
# 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_cve_tags
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+".*"')
prog = parse_cve_tags.cve_tag
patch_prog = parse_cve_tags.patch_cve_tag
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. Rebase your series and ensure the target is correct',
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. Check the project\'s README (%s) and send the patch to the indicated list' % match.group('project'),
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. Send the series again to the correct mailing list (ML)',
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. Send the series again to the correct mailing list (ML)',
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 cannot be parsed correctly due to malformed diff lines. Create the series again using git-format-patch and ensure it can be applied 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('Mbox is missing a descriptive commit message. Please include a commit message on your patch explaining the change', commit=commit)
def test_cve_presence_in_commit_message(self):
if self.unidiff_parse_error:
self.skip('Parse error %s' % self.unidiff_parse_error)
# we are just interested in series that introduce CVE patches, thus discard other
# possibilities: modification to current CVEs, patch directly introduced into the
# recipe, upgrades already including the CVE, etc.
new_patches = [p for p in self.patchset if p.path.endswith('.patch') and p.is_added_file]
if not new_patches:
self.skip('No new patches introduced')
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.patch_prog.search_string(commit.payload):
self.skip("No CVE tag in added patch, so not needed in mbox")
elif not self.prog.search_string(commit.payload):
self.fail('Missing or incorrectly formatted CVE tag in mbox. Correct or include the CVE tag in the mbox with format: "CVE: CVE-YYYY-XXXX"',
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)

View File

@@ -1,29 +0,0 @@
# Checks related to the patch's author
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
import base
import pyparsing
class Author(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")]
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)

View File

@@ -1,20 +0,0 @@
# Checks related to the patch's bugzilla tag
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
import pyparsing
import base
class Bugzilla(base.Base):
rexp_detect = pyparsing.Regex('\[\s?YOCTO.*\]')
rexp_validation = pyparsing.Regex('\[(\s?YOCTO\s?#\s?(\d+)\s?,?)+\]')
def test_bugzilla_entry_format(self):
for commit in Bugzilla.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)

View File

@@ -1,38 +0,0 @@
# Checks related to the patch's CVE lines
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
#
import base
import parse_cve_tags
import pyparsing
class CVE(base.Base):
revert_shortlog_regex = pyparsing.Regex('Revert\s+".*"')
prog = parse_cve_tags.cve_tag
patch_prog = parse_cve_tags.patch_cve_tag
def setUp(self):
if self.unidiff_parse_error:
self.skip('Parse error %s' % self.unidiff_parse_error)
# we are just interested in series that introduce CVE patches, thus discard other
# possibilities: modification to current CVEs, patch directly introduced into the
# recipe, upgrades already including the CVE, etc.
new_patches = [p for p in self.patchset if p.path.endswith('.patch') and p.is_added_file]
if not new_patches:
self.skip('No new patches introduced')
def test_cve_presence_in_commit_message(self):
for commit in CVE.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.patch_prog.search_string(commit.payload):
self.skip("No CVE tag in added patch, so not needed in mbox")
elif not self.prog.search_string(commit.payload):
self.fail('Missing or incorrectly formatted CVE tag in mbox. Correct or include the CVE tag in the mbox with format: "CVE: CVE-YYYY-XXXX"',
commit=commit)

View File

@@ -1,15 +0,0 @@
# Checks related to the patch's commit_message
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
import base
class CommitMessage(base.Base):
def test_commit_message_presence(self):
for commit in CommitMessage.commits:
if not commit.commit_message.strip():
self.fail('Mbox is missing a descriptive commit message. Please include a commit message on your patch explaining the change', commit=commit)

View File

@@ -1,14 +0,0 @@
# Checks correct parsing of mboxes
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
import base
class MboxFormat(base.Base):
def test_mbox_format(self):
if self.unidiff_parse_error:
self.fail('Series cannot be parsed correctly due to malformed diff lines. Create the series again using git-format-patch and ensure it can be applied using git am',
data=[('Diff line',self.unidiff_parse_error)])

View File

@@ -1,62 +0,0 @@
# Check if the series was intended for other project (not OE-Core)
#
# Copyright (C) 2017 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
import collections
import base
import pyparsing
from data import PatchTestInput
class MailingList(base.Base):
# 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_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 MailingList.commits:
match = project_regex.search_string(commit.subject)
if match:
self.fail('Series sent to the wrong mailing list. Check the project\'s README (%s) and send the patch to the indicated list' % match.group('project'),
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. Send the series again to the correct mailing list (ML)',
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. Send the series again to the correct mailing list (ML)',
data=[('Suggested ML', '%s [%s]' % (self.poky.listemail, self.poky.gitrepo)),('Patch\'s path:', patch.path)])

View File

@@ -1,27 +0,0 @@
# Check if mbox was merged by patchtest
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
import subprocess
import base
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 Merge(base.Base):
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. Rebase your series and ensure the target is correct',
data=[('Targeted branch', '%s (currently at %s)' % (PatchTestInput.repo.branch, commithash))])

View File

@@ -1,39 +0,0 @@
# Checks related to the patch's summary
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
import base
import parse_shortlog
import pyparsing
maxlength = 90
class Shortlog(base.Base):
def test_shortlog_format(self):
for commit in Shortlog.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 Shortlog.commits:
# no reason to re-check on revert shortlogs
shortlog = commit.shortlog
if shortlog.startswith('Revert "'):
continue
l = len(shortlog)
if l > maxlength:
self.fail('Edit shortlog so that it is %d characters or less (currently %d characters)' % (maxlength, l),
commit=commit)

View File

@@ -1,27 +0,0 @@
# Checks related to the patch's signed-off-by lines
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
import base
import parse_signed_off_by
import pyparsing
class SignedOffBy(base.Base):
revert_shortlog_regex = pyparsing.Regex('Revert\s+".*"')
@classmethod
def setUpClassLocal(cls):
# match self.mark with no '+' preceding it
cls.prog = parse_signed_off_by.signed_off_by
def test_signed_off_by_presence(self):
for commit in SignedOffBy.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 SignedOffBy.prog.search_string(commit.payload):
self.fail('Mbox is missing Signed-off-by. Add it manually or with "git commit --amend -s"',
commit=commit)

View File

@@ -0,0 +1,204 @@
# Checks related to the patch's LIC_FILES_CHKSUM metadata variable
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
import base
import os
import pyparsing
from data import PatchTestInput, PatchTestDataStore
class TestMetadata(base.Metadata):
metadata_lic = 'LICENSE'
invalid_license = 'PATCHTESTINVALID'
metadata_chksum = 'LIC_FILES_CHKSUM'
license_var = 'LICENSE'
closed = 'CLOSED'
lictag_re = pyparsing.AtLineStart("License-Update:")
add_mark = pyparsing.Regex('\+ ')
max_length = 200
metadata_src_uri = 'SRC_URI'
md5sum = 'md5sum'
sha256sum = 'sha256sum'
git_regex = pyparsing.Regex('^git\:\/\/.*')
metadata_summary = 'SUMMARY'
def test_license_presence(self):
if not self.added:
self.skip('No added recipes, skipping test')
# TODO: this is a workaround so we can parse the recipe not
# containing the LICENSE var: add some default license instead
# of INVALID into auto.conf, then remove this line at the end
auto_conf = os.path.join(os.environ.get('BUILDDIR'), 'conf', 'auto.conf')
open_flag = 'w'
if os.path.exists(auto_conf):
open_flag = 'a'
with open(auto_conf, open_flag) as fd:
for pn in self.added:
fd.write('LICENSE ??= "%s"\n' % self.invalid_license)
no_license = False
for pn in self.added:
rd = self.tinfoil.parse_recipe(pn)
license = rd.getVar(self.metadata_lic)
if license == self.invalid_license:
no_license = True
break
# remove auto.conf line or the file itself
if open_flag == 'w':
os.remove(auto_conf)
else:
fd = open(auto_conf, 'r')
lines = fd.readlines()
fd.close()
with open(auto_conf, 'w') as fd:
fd.write(''.join(lines[:-1]))
if no_license:
self.fail('Recipe does not have the LICENSE field set.')
def test_lic_files_chksum_presence(self):
if not self.added:
self.skip('No added recipes, skipping test')
for pn in self.added:
rd = self.tinfoil.parse_recipe(pn)
pathname = rd.getVar('FILE')
# we are not interested in images
if '/images/' in pathname:
continue
lic_files_chksum = rd.getVar(self.metadata_chksum)
if rd.getVar(self.license_var) == self.closed:
continue
if not lic_files_chksum:
self.fail('%s is missing in newly added recipe' % self.metadata_chksum)
def pretest_lic_files_chksum_modified_not_mentioned(self):
if not self.modified:
self.skip('No modified recipes, skipping pretest')
# get the proper metadata values
for pn in self.modified:
rd = self.tinfoil.parse_recipe(pn)
pathname = rd.getVar('FILE')
# we are not interested in images
if '/images/' in pathname:
continue
PatchTestDataStore['%s-%s-%s' % (self.shortid(),self.metadata_chksum,pn)] = rd.getVar(self.metadata_chksum)
def test_lic_files_chksum_modified_not_mentioned(self):
if not self.modified:
self.skip('No modified recipes, skipping test')
# get the proper metadata values
for pn in self.modified:
rd = self.tinfoil.parse_recipe(pn)
pathname = rd.getVar('FILE')
# we are not interested in images
if '/images/' in pathname:
continue
PatchTestDataStore['%s-%s-%s' % (self.shortid(),self.metadata_chksum,pn)] = rd.getVar(self.metadata_chksum)
# compare if there were changes between pre-merge and merge
for pn in self.modified:
pretest = PatchTestDataStore['pre%s-%s-%s' % (self.shortid(),self.metadata_chksum, pn)]
test = PatchTestDataStore['%s-%s-%s' % (self.shortid(),self.metadata_chksum, pn)]
# TODO: this is workaround to avoid false-positives when pretest metadata is empty (not reason found yet)
# For more info, check bug 12284
if not pretest:
return
if pretest != test:
# if any patch on the series contain reference on the metadata, fail
for commit in self.commits:
if self.lictag_re.search_string(commit.commit_message):
break
else:
self.fail('LIC_FILES_CHKSUM changed on target %s but there is no "License-Update:" tag in commit message. Include it with a brief description' % pn,
data=[('Current checksum', pretest), ('New checksum', test)])
def test_max_line_length(self):
for patch in self.patchset:
# for the moment, we are just interested in metadata
if patch.path.endswith('.patch'):
continue
payload = str(patch)
for line in payload.splitlines():
if self.add_mark.search_string(line):
current_line_length = len(line[1:])
if current_line_length > self.max_length:
self.fail('Patch line too long (current length %s, maximum is %s)' % (current_line_length, self.max_length),
data=[('Patch', patch.path), ('Line', '%s ...' % line[0:80])])
def pretest_src_uri_left_files(self):
# these tests just make sense on patches that can be merged
if not PatchTestInput.repo.canbemerged:
self.skip('Patch cannot be merged')
if not self.modified:
self.skip('No modified recipes, skipping pretest')
# get the proper metadata values
for pn in self.modified:
# we are not interested in images
if 'core-image' in pn:
continue
rd = self.tinfoil.parse_recipe(pn)
PatchTestDataStore['%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)] = rd.getVar(self.metadata_src_uri)
def test_src_uri_left_files(self):
# these tests just make sense on patches that can be merged
if not PatchTestInput.repo.canbemerged:
self.skip('Patch cannot be merged')
if not self.modified:
self.skip('No modified recipes, skipping pretest')
# get the proper metadata values
for pn in self.modified:
# we are not interested in images
if 'core-image' in pn:
continue
rd = self.tinfoil.parse_recipe(pn)
PatchTestDataStore['%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)] = rd.getVar(self.metadata_src_uri)
for pn in self.modified:
pretest_src_uri = PatchTestDataStore['pre%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)].split()
test_src_uri = PatchTestDataStore['%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)].split()
pretest_files = set([os.path.basename(patch) for patch in pretest_src_uri if patch.startswith('file://')])
test_files = set([os.path.basename(patch) for patch in test_src_uri if patch.startswith('file://')])
# check if files were removed
if len(test_files) < len(pretest_files):
# get removals from patchset
filesremoved_from_patchset = set()
for patch in self.patchset:
if patch.is_removed_file:
filesremoved_from_patchset.add(os.path.basename(patch.path))
# get the deleted files from the SRC_URI
filesremoved_from_usr_uri = pretest_files - test_files
# finally, get those patches removed at SRC_URI and not removed from the patchset
# TODO: we are not taking into account renames, so test may raise false positives
not_removed = filesremoved_from_usr_uri - filesremoved_from_patchset
if not_removed:
self.fail('Patches not removed from tree. Remove them and amend the submitted mbox',
data=[('Patch', f) for f in not_removed])
def test_summary_presence(self):
if not self.added:
self.skip('No added recipes, skipping test')
for pn in self.added:
# we are not interested in images
if 'core-image' in pn:
continue
rd = self.tinfoil.parse_recipe(pn)
summary = rd.getVar(self.metadata_summary)
# "${PN} version ${PN}-${PR}" is the default, so fail if default
if summary.startswith('%s version' % pn):
self.fail('%s is missing in newly added recipe' % self.metadata_summary)

View File

@@ -1,74 +0,0 @@
# Checks related to the patch's LIC_FILES_CHKSUM metadata variable
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
import base
import pyparsing
from data import PatchTestInput, PatchTestDataStore
class LicFilesChkSum(base.Metadata):
metadata = 'LIC_FILES_CHKSUM'
license = 'LICENSE'
closed = 'CLOSED'
lictag_re = pyparsing.AtLineStart("License-Update:")
def test_lic_files_chksum_presence(self):
if not self.added:
self.skip('No added recipes, skipping test')
for pn in self.added:
rd = self.tinfoil.parse_recipe(pn)
pathname = rd.getVar('FILE')
# we are not interested in images
if '/images/' in pathname:
continue
lic_files_chksum = rd.getVar(self.metadata)
if rd.getVar(self.license) == self.closed:
continue
if not lic_files_chksum:
self.fail('%s is missing in newly added recipe' % self.metadata)
def pretest_lic_files_chksum_modified_not_mentioned(self):
if not self.modified:
self.skip('No modified recipes, skipping pretest')
# get the proper metadata values
for pn in self.modified:
rd = self.tinfoil.parse_recipe(pn)
pathname = rd.getVar('FILE')
# we are not interested in images
if '/images/' in pathname:
continue
PatchTestDataStore['%s-%s-%s' % (self.shortid(),self.metadata,pn)] = rd.getVar(self.metadata)
def test_lic_files_chksum_modified_not_mentioned(self):
if not self.modified:
self.skip('No modified recipes, skipping test')
# get the proper metadata values
for pn in self.modified:
rd = self.tinfoil.parse_recipe(pn)
pathname = rd.getVar('FILE')
# we are not interested in images
if '/images/' in pathname:
continue
PatchTestDataStore['%s-%s-%s' % (self.shortid(),self.metadata,pn)] = rd.getVar(self.metadata)
# compare if there were changes between pre-merge and merge
for pn in self.modified:
pretest = PatchTestDataStore['pre%s-%s-%s' % (self.shortid(),self.metadata, pn)]
test = PatchTestDataStore['%s-%s-%s' % (self.shortid(),self.metadata, pn)]
# TODO: this is workaround to avoid false-positives when pretest metadata is empty (not reason found yet)
# For more info, check bug 12284
if not pretest:
return
if pretest != test:
# if any patch on the series contain reference on the metadata, fail
for commit in self.commits:
if self.lictag_re.search_string(commit.commit_message):
break
else:
self.fail('LIC_FILES_CHKSUM changed on target %s but there is no "License-Update:" tag in commit message. Include it with a brief description' % pn,
data=[('Current checksum', pretest), ('New checksum', test)])

View File

@@ -1,50 +0,0 @@
# Checks related to the patch's LIC_FILES_CHKSUM metadata variable
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
import base
import os
from data import PatchTestInput
class License(base.Metadata):
metadata = 'LICENSE'
invalid_license = 'PATCHTESTINVALID'
def test_license_presence(self):
if not self.added:
self.skip('No added recipes, skipping test')
# TODO: this is a workaround so we can parse the recipe not
# containing the LICENSE var: add some default license instead
# of INVALID into auto.conf, then remove this line at the end
auto_conf = os.path.join(os.environ.get('BUILDDIR'), 'conf', 'auto.conf')
open_flag = 'w'
if os.path.exists(auto_conf):
open_flag = 'a'
with open(auto_conf, open_flag) as fd:
for pn in self.added:
fd.write('LICENSE ??= "%s"\n' % self.invalid_license)
no_license = False
for pn in self.added:
rd = self.tinfoil.parse_recipe(pn)
license = rd.getVar(self.metadata)
if license == self.invalid_license:
no_license = True
break
# remove auto.conf line or the file itself
if open_flag == 'w':
os.remove(auto_conf)
else:
fd = open(auto_conf, 'r')
lines = fd.readlines()
fd.close()
with open(auto_conf, 'w') as fd:
fd.write(''.join(lines[:-1]))
if no_license:
self.fail('Recipe does not have the LICENSE field set.')

View File

@@ -1,25 +0,0 @@
# Checks related to patch line lengths
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
import base
import pyparsing
class MaxLength(base.Base):
add_mark = pyparsing.Regex('\+ ')
max_length = 200
def test_max_line_length(self):
for patch in self.patchset:
# for the moment, we are just interested in metadata
if patch.path.endswith('.patch'):
continue
payload = str(patch)
for line in payload.splitlines():
if self.add_mark.search_string(line):
current_line_length = len(line[1:])
if current_line_length > self.max_length:
self.fail('Patch line too long (current length %s, maximum is %s)' % (current_line_length, self.max_length),
data=[('Patch', patch.path), ('Line', '%s ...' % line[0:80])])

View File

@@ -1,73 +0,0 @@
# Checks related to the patch's SRC_URI metadata variable
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
import base
import os
import pyparsing
from data import PatchTestInput, PatchTestDataStore
class SrcUri(base.Metadata):
metadata = 'SRC_URI'
md5sum = 'md5sum'
sha256sum = 'sha256sum'
git_regex = pyparsing.Regex('^git\:\/\/.*')
def setUp(self):
# these tests just make sense on patches that can be merged
if not PatchTestInput.repo.canbemerged:
self.skip('Patch cannot be merged')
def pretest_src_uri_left_files(self):
if not self.modified:
self.skip('No modified recipes, skipping pretest')
# get the proper metadata values
for pn in self.modified:
# we are not interested in images
if 'core-image' in pn:
continue
rd = self.tinfoil.parse_recipe(pn)
PatchTestDataStore['%s-%s-%s' % (self.shortid(), self.metadata, pn)] = rd.getVar(self.metadata)
def test_src_uri_left_files(self):
if not self.modified:
self.skip('No modified recipes, skipping pretest')
# get the proper metadata values
for pn in self.modified:
# we are not interested in images
if 'core-image' in pn:
continue
rd = self.tinfoil.parse_recipe(pn)
PatchTestDataStore['%s-%s-%s' % (self.shortid(), self.metadata, pn)] = rd.getVar(self.metadata)
for pn in self.modified:
pretest_src_uri = PatchTestDataStore['pre%s-%s-%s' % (self.shortid(), self.metadata, pn)].split()
test_src_uri = PatchTestDataStore['%s-%s-%s' % (self.shortid(), self.metadata, pn)].split()
pretest_files = set([os.path.basename(patch) for patch in pretest_src_uri if patch.startswith('file://')])
test_files = set([os.path.basename(patch) for patch in test_src_uri if patch.startswith('file://')])
# check if files were removed
if len(test_files) < len(pretest_files):
# get removals from patchset
filesremoved_from_patchset = set()
for patch in self.patchset:
if patch.is_removed_file:
filesremoved_from_patchset.add(os.path.basename(patch.path))
# get the deleted files from the SRC_URI
filesremoved_from_usr_uri = pretest_files - test_files
# finally, get those patches removed at SRC_URI and not removed from the patchset
# TODO: we are not taking into account renames, so test may raise false positives
not_removed = filesremoved_from_usr_uri - filesremoved_from_patchset
if not_removed:
self.fail('Patches not removed from tree. Remove them and amend the submitted mbox',
data=[('Patch', f) for f in not_removed])

View File

@@ -1,26 +0,0 @@
# Checks related to the patch's summary metadata variable
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
import base
from data import PatchTestInput
class Summary(base.Metadata):
metadata = 'SUMMARY'
def test_summary_presence(self):
if not self.added:
self.skip('No added recipes, skipping test')
for pn in self.added:
# we are not interested in images
if 'core-image' in pn:
continue
rd = self.tinfoil.parse_recipe(pn)
summary = rd.getVar(self.metadata)
# "${PN} version ${PN}-${PR}" is the default, so fail if default
if summary.startswith('%s version' % pn):
self.fail('%s is missing in newly added recipe' % self.metadata)

View File

@@ -1,15 +1,19 @@
# Checks related to the patch's upstream-status lines
# Checks related to the patch's CVE lines
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
#
import base
import parse_signed_off_by
import parse_upstream_status
import pyparsing
class PatchUpstreamStatus(base.Base):
class TestPatch(base.Base):
re_cve_pattern = pyparsing.Regex("CVE\-\d{4}\-\d+")
re_cve_payload_tag = pyparsing.Regex("\+CVE:(\s+CVE\-\d{4}\-\d+)+")
upstream_status_regex = pyparsing.AtLineStart("+" + "Upstream-Status")
@classmethod
@@ -20,17 +24,30 @@ class PatchUpstreamStatus(base.Base):
if patch.path.endswith('.patch') and patch.is_added_file:
cls.newpatches.append(patch)
cls.mark = str(parse_signed_off_by.signed_off_by_mark).strip('"')
# match PatchSignedOffBy.mark with '+' preceding it
cls.prog = parse_signed_off_by.patch_signed_off_by
def setUp(self):
if self.unidiff_parse_error:
self.skip('Python-unidiff parse error')
self.skip('Parse error %s' % self.unidiff_parse_error)
self.valid_status = ', '.join(parse_upstream_status.upstream_status_nonliteral_valid_status)
self.standard_format = 'Upstream-Status: <Valid status>'
# we are just interested in series that introduce CVE patches, thus discard other
# possibilities: modification to current CVEs, patch directly introduced into the
# recipe, upgrades already including the CVE, etc.
new_cves = [p for p in self.patchset if p.path.endswith('.patch') and p.is_added_file]
if not new_cves:
self.skip('No new CVE patches introduced')
def test_upstream_status_presence_format(self):
if not PatchUpstreamStatus.newpatches:
if not TestPatch.newpatches:
self.skip("There are no new software patches, no reason to test Upstream-Status presence/format")
for newpatch in PatchUpstreamStatus.newpatches:
for newpatch in TestPatch.newpatches:
payload = newpatch.__str__()
if not self.upstream_status_regex.search_string(payload):
self.fail('Added patch file is missing Upstream-Status in the header. Add Upstream-Status: <Valid status> to the header',
@@ -57,3 +74,29 @@ class PatchUpstreamStatus(base.Base):
except pyparsing.ParseException as pe:
self.fail('Upstream-Status is in incorrect format',
data=[('Current', pe.pstr), ('Standard format', self.standard_format), ('Valid status', self.valid_status)])
def test_signed_off_by_presence(self):
if not TestPatch.newpatches:
self.skip("There are no new software patches, no reason to test %s presence" % PatchSignedOffBy.mark)
for newpatch in TestPatch.newpatches:
payload = newpatch.__str__()
for line in payload.splitlines():
if self.patchmetadata_regex.match(line):
continue
if TestPatch.prog.search_string(payload):
break
else:
self.fail('A patch file has been added, but does not have a Signed-off-by tag. Sign off the added patch file (%s)' % newpatch.path)
def test_cve_tag_format(self):
for commit in TestPatch.commits:
if self.re_cve_pattern.search_string(commit.shortlog) or self.re_cve_pattern.search_string(commit.commit_message):
tag_found = False
for line in commit.payload.splitlines():
if self.re_cve_payload_tag.search_string(line):
tag_found = True
break
if not tag_found:
self.fail('Missing or incorrectly formatted CVE tag in patch file. Correct or include the CVE tag in the patch with format: "CVE: CVE-YYYY-XXXX"',
commit=commit)

View File

@@ -1,37 +0,0 @@
# Checks related to the patch's CVE lines
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
#
import base
import pyparsing
class CVE(base.Base):
re_cve_pattern = pyparsing.Regex("CVE\-\d{4}\-\d+")
re_cve_payload_tag = pyparsing.Regex("\+CVE:(\s+CVE\-\d{4}\-\d+)+")
def setUp(self):
if self.unidiff_parse_error:
self.skip('Parse error %s' % self.unidiff_parse_error)
# we are just interested in series that introduce CVE patches, thus discard other
# possibilities: modification to current CVEs, patch directly introduced into the
# recipe, upgrades already including the CVE, etc.
new_cves = [p for p in self.patchset if p.path.endswith('.patch') and p.is_added_file]
if not new_cves:
self.skip('No new CVE patches introduced')
def test_cve_tag_format(self):
for commit in CVE.commits:
if self.re_cve_pattern.search_string(commit.shortlog) or self.re_cve_pattern.search_string(commit.commit_message):
tag_found = False
for line in commit.payload.splitlines():
if self.re_cve_payload_tag.search_string(line):
tag_found = True
break
if not tag_found:
self.fail('Missing or incorrectly formatted CVE tag in patch file. Correct or include the CVE tag in the patch with format: "CVE: CVE-YYYY-XXXX"',
commit=commit)

View File

@@ -1,41 +0,0 @@
# Checks related to the patch's signed-off-by lines
#
# Copyright (C) 2016 Intel Corporation
#
# SPDX-License-Identifier: GPL-2.0-only
import base
import parse_signed_off_by
class PatchSignedOffBy(base.Base):
@classmethod
def setUpClassLocal(cls):
cls.newpatches = []
# get just those relevant patches: new software patches
for patch in cls.patchset:
if patch.path.endswith('.patch') and patch.is_added_file:
cls.newpatches.append(patch)
cls.mark = str(parse_signed_off_by.signed_off_by_mark).strip('"')
# match PatchSignedOffBy.mark with '+' preceding it
cls.prog = parse_signed_off_by.patch_signed_off_by
def setUp(self):
if self.unidiff_parse_error:
self.skip('Parse error %s' % self.unidiff_parse_error)
def test_signed_off_by_presence(self):
if not PatchSignedOffBy.newpatches:
self.skip("There are no new software patches, no reason to test %s presence" % PatchSignedOffBy.mark)
for newpatch in PatchSignedOffBy.newpatches:
payload = newpatch.__str__()
for line in payload.splitlines():
if self.patchmetadata_regex.match(line):
continue
if PatchSignedOffBy.prog.search_string(payload):
break
else:
self.fail('A patch file has been added, but does not have a Signed-off-by tag. Sign off the added patch file (%s)' % newpatch.path)