# 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:") lic_chksum_added = pyparsing.AtLineStart("+" + metadata_chksum) lic_chksum_removed = pyparsing.AtLineStart("-" + metadata_chksum) add_mark = pyparsing.Regex('\\+ ') max_length = 200 metadata_src_uri = 'SRC_URI' md5sum = 'md5sum' sha256sum = 'sha256sum' git_regex = pyparsing.Regex('^git\\:\\/\\/.*') metadata_summary = 'SUMMARY' cve_check_ignore_var = 'CVE_CHECK_IGNORE' cve_status_var = 'CVE_STATUS' 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 test_lic_files_chksum_modified_not_mentioned(self): if not self.modified: self.skip('No modified recipes, skipping test') for patch in self.patchset: # for the moment, we are just interested in metadata if patch.path.endswith('.patch'): continue payload = str(patch) if (self.lic_chksum_added.search_string(payload) or self.lic_chksum_removed.search_string(payload)): # 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 without "License-Update:" tag and description in commit message') 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) def test_cve_check_ignore(self): # Skip if we neither modified a recipe or target branches are not # Nanbield and newer. CVE_CHECK_IGNORE was first deprecated in Nanbield. if not self.modified or PatchTestInput.repo.branch == "kirkstone" or PatchTestInput.repo.branch == "dunfell": self.skip('No modified recipes or older target branch, skipping test') for pn in self.modified: # we are not interested in images if 'core-image' in pn: continue rd = self.tinfoil.parse_recipe(pn) cve_check_ignore = rd.getVar(self.cve_check_ignore_var) if cve_check_ignore is not None: self.fail('%s is deprecated and should be replaced by %s' % (self.cve_check_ignore_var, self.cve_status_var))