diff options
Diffstat (limited to 'meta/lib/patchtest/tests')
19 files changed, 435 insertions, 602 deletions
diff --git a/meta/lib/patchtest/tests/test_mbox.py b/meta/lib/patchtest/tests/test_mbox.py new file mode 100644 index 0000000000..95002c9e2a --- /dev/null +++ b/meta/lib/patchtest/tests/test_mbox.py @@ -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) diff --git a/meta/lib/patchtest/tests/test_mbox_author.py b/meta/lib/patchtest/tests/test_mbox_author.py deleted file mode 100644 index 74bc441250..0000000000 --- a/meta/lib/patchtest/tests/test_mbox_author.py +++ /dev/null @@ -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) diff --git a/meta/lib/patchtest/tests/test_mbox_bugzilla.py b/meta/lib/patchtest/tests/test_mbox_bugzilla.py deleted file mode 100644 index 99b529b755..0000000000 --- a/meta/lib/patchtest/tests/test_mbox_bugzilla.py +++ /dev/null @@ -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) - diff --git a/meta/lib/patchtest/tests/test_mbox_cve.py b/meta/lib/patchtest/tests/test_mbox_cve.py deleted file mode 100644 index 29ab12cbb5..0000000000 --- a/meta/lib/patchtest/tests/test_mbox_cve.py +++ /dev/null @@ -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) diff --git a/meta/lib/patchtest/tests/test_mbox_description.py b/meta/lib/patchtest/tests/test_mbox_description.py deleted file mode 100644 index 7874f9d038..0000000000 --- a/meta/lib/patchtest/tests/test_mbox_description.py +++ /dev/null @@ -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) - diff --git a/meta/lib/patchtest/tests/test_mbox_format.py b/meta/lib/patchtest/tests/test_mbox_format.py deleted file mode 100644 index fea3793e2e..0000000000 --- a/meta/lib/patchtest/tests/test_mbox_format.py +++ /dev/null @@ -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)]) diff --git a/meta/lib/patchtest/tests/test_mbox_mailinglist.py b/meta/lib/patchtest/tests/test_mbox_mailinglist.py deleted file mode 100644 index feff436089..0000000000 --- a/meta/lib/patchtest/tests/test_mbox_mailinglist.py +++ /dev/null @@ -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)]) diff --git a/meta/lib/patchtest/tests/test_mbox_merge.py b/meta/lib/patchtest/tests/test_mbox_merge.py deleted file mode 100644 index 535026209f..0000000000 --- a/meta/lib/patchtest/tests/test_mbox_merge.py +++ /dev/null @@ -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))]) diff --git a/meta/lib/patchtest/tests/test_mbox_shortlog.py b/meta/lib/patchtest/tests/test_mbox_shortlog.py deleted file mode 100644 index f5dbbc7807..0000000000 --- a/meta/lib/patchtest/tests/test_mbox_shortlog.py +++ /dev/null @@ -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) diff --git a/meta/lib/patchtest/tests/test_mbox_signed_off_by.py b/meta/lib/patchtest/tests/test_mbox_signed_off_by.py deleted file mode 100644 index f3c5770961..0000000000 --- a/meta/lib/patchtest/tests/test_mbox_signed_off_by.py +++ /dev/null @@ -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) diff --git a/meta/lib/patchtest/tests/test_metadata.py b/meta/lib/patchtest/tests/test_metadata.py new file mode 100644 index 0000000000..34e119174f --- /dev/null +++ b/meta/lib/patchtest/tests/test_metadata.py @@ -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) diff --git a/meta/lib/patchtest/tests/test_metadata_lic_files_chksum.py b/meta/lib/patchtest/tests/test_metadata_lic_files_chksum.py deleted file mode 100644 index fa4a28c7b2..0000000000 --- a/meta/lib/patchtest/tests/test_metadata_lic_files_chksum.py +++ /dev/null @@ -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)]) diff --git a/meta/lib/patchtest/tests/test_metadata_license.py b/meta/lib/patchtest/tests/test_metadata_license.py deleted file mode 100644 index 1a7f09b747..0000000000 --- a/meta/lib/patchtest/tests/test_metadata_license.py +++ /dev/null @@ -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.') - diff --git a/meta/lib/patchtest/tests/test_metadata_max_length.py b/meta/lib/patchtest/tests/test_metadata_max_length.py deleted file mode 100644 index 98c48ef787..0000000000 --- a/meta/lib/patchtest/tests/test_metadata_max_length.py +++ /dev/null @@ -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])]) diff --git a/meta/lib/patchtest/tests/test_metadata_src_uri.py b/meta/lib/patchtest/tests/test_metadata_src_uri.py deleted file mode 100644 index 87a24ea937..0000000000 --- a/meta/lib/patchtest/tests/test_metadata_src_uri.py +++ /dev/null @@ -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]) - diff --git a/meta/lib/patchtest/tests/test_metadata_summary.py b/meta/lib/patchtest/tests/test_metadata_summary.py deleted file mode 100644 index 170e79eb4b..0000000000 --- a/meta/lib/patchtest/tests/test_metadata_summary.py +++ /dev/null @@ -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) diff --git a/meta/lib/patchtest/tests/test_patch_upstream_status.py b/meta/lib/patchtest/tests/test_patch.py index a5b278304e..b6904b185f 100644 --- a/meta/lib/patchtest/tests/test_patch_upstream_status.py +++ b/meta/lib/patchtest/tests/test_patch.py @@ -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) diff --git a/meta/lib/patchtest/tests/test_patch_cve.py b/meta/lib/patchtest/tests/test_patch_cve.py deleted file mode 100644 index c77848de45..0000000000 --- a/meta/lib/patchtest/tests/test_patch_cve.py +++ /dev/null @@ -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) diff --git a/meta/lib/patchtest/tests/test_patch_signed_off_by.py b/meta/lib/patchtest/tests/test_patch_signed_off_by.py deleted file mode 100644 index b091ff6f10..0000000000 --- a/meta/lib/patchtest/tests/test_patch_signed_off_by.py +++ /dev/null @@ -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) |