1# Checks related to the patch's LIC_FILES_CHKSUM metadata variable 2# 3# Copyright (C) 2016 Intel Corporation 4# 5# SPDX-License-Identifier: GPL-2.0-only 6 7import base 8import collections 9import os 10import patchtest_patterns 11import pyparsing 12from patchtest_parser import PatchtestParser 13 14# Data store commonly used to share values between pre and post-merge tests 15PatchTestDataStore = collections.defaultdict(str) 16 17class TestMetadata(base.Metadata): 18 19 def test_license_presence(self): 20 if not self.added: 21 self.skip('No added recipes, skipping test') 22 23 # TODO: this is a workaround so we can parse the recipe not 24 # containing the LICENSE var: add some default license instead 25 # of INVALID into auto.conf, then remove this line at the end 26 auto_conf = os.path.join(os.environ.get('BUILDDIR'), 'conf', 'auto.conf') 27 open_flag = 'w' 28 if os.path.exists(auto_conf): 29 open_flag = 'a' 30 with open(auto_conf, open_flag) as fd: 31 for pn in self.added: 32 fd.write('LICENSE ??= "%s"\n' % patchtest_patterns.invalid_license) 33 34 no_license = False 35 for pn in self.added: 36 rd = self.tinfoil.parse_recipe(pn) 37 license = rd.getVar(patchtest_patterns.metadata_lic) 38 if license == patchtest_patterns.invalid_license: 39 no_license = True 40 break 41 42 # remove auto.conf line or the file itself 43 if open_flag == 'w': 44 os.remove(auto_conf) 45 else: 46 fd = open(auto_conf, 'r') 47 lines = fd.readlines() 48 fd.close() 49 with open(auto_conf, 'w') as fd: 50 fd.write(''.join(lines[:-1])) 51 52 if no_license: 53 self.fail('Recipe does not have the LICENSE field set.') 54 55 def test_lic_files_chksum_presence(self): 56 if not self.added: 57 self.skip('No added recipes, skipping test') 58 59 for pn in self.added: 60 rd = self.tinfoil.parse_recipe(pn) 61 pathname = rd.getVar('FILE') 62 # we are not interested in images 63 if '/images/' in pathname: 64 continue 65 lic_files_chksum = rd.getVar(patchtest_patterns.metadata_chksum) 66 if rd.getVar(patchtest_patterns.license_var) == patchtest_patterns.closed: 67 continue 68 if not lic_files_chksum: 69 self.fail( 70 "%s is missing in newly added recipe" % patchtest_patterns.metadata_chksum 71 ) 72 73 def test_lic_files_chksum_modified_not_mentioned(self): 74 if not self.modified: 75 self.skip('No modified recipes, skipping test') 76 77 for patch in self.patchset: 78 # for the moment, we are just interested in metadata 79 if patch.path.endswith('.patch'): 80 continue 81 payload = str(patch) 82 if patchtest_patterns.lic_chksum_added.search_string( 83 payload 84 ) or patchtest_patterns.lic_chksum_removed.search_string(payload): 85 # if any patch on the series contain reference on the metadata, fail 86 for commit in self.commits: 87 if patchtest_patterns.lictag_re.search_string(commit.commit_message): 88 break 89 else: 90 self.fail('LIC_FILES_CHKSUM changed without "License-Update:" tag and description in commit message') 91 92 def test_max_line_length(self): 93 for patch in self.patchset: 94 # for the moment, we are just interested in metadata 95 if patch.path.endswith('.patch'): 96 continue 97 payload = str(patch) 98 for line in payload.splitlines(): 99 if patchtest_patterns.add_mark.search_string(line): 100 current_line_length = len(line[1:]) 101 if current_line_length > patchtest_patterns.patch_max_line_length: 102 self.fail( 103 "Patch line too long (current length %s, maximum is %s)" 104 % (current_line_length, patchtest_patterns.patch_max_line_length), 105 data=[ 106 ("Patch", patch.path), 107 ("Line", "%s ..." % line[0:80]), 108 ], 109 ) 110 111 def pretest_src_uri_left_files(self): 112 # these tests just make sense on patches that can be merged 113 if not PatchtestParser.repo.canbemerged: 114 self.skip("Patch cannot be merged") 115 if not self.modified: 116 self.skip('No modified recipes, skipping pretest') 117 118 # get the proper metadata values 119 for pn in self.modified: 120 # we are not interested in images 121 if 'core-image' in pn: 122 continue 123 rd = self.tinfoil.parse_recipe(pn) 124 PatchTestDataStore[ 125 "%s-%s-%s" % (self.shortid(), patchtest_patterns.metadata_src_uri, pn) 126 ] = rd.getVar(patchtest_patterns.metadata_src_uri) 127 128 def test_src_uri_left_files(self): 129 # these tests just make sense on patches that can be merged 130 if not PatchtestParser.repo.canbemerged: 131 self.skip("Patch cannot be merged") 132 if not self.modified: 133 self.skip('No modified recipes, skipping pretest') 134 135 # get the proper metadata values 136 for pn in self.modified: 137 # we are not interested in images 138 if 'core-image' in pn: 139 continue 140 rd = self.tinfoil.parse_recipe(pn) 141 PatchTestDataStore[ 142 "%s-%s-%s" % (self.shortid(), patchtest_patterns.metadata_src_uri, pn) 143 ] = rd.getVar(patchtest_patterns.metadata_src_uri) 144 145 for pn in self.modified: 146 pretest_src_uri = PatchTestDataStore[ 147 "pre%s-%s-%s" % (self.shortid(), patchtest_patterns.metadata_src_uri, pn) 148 ].split() 149 test_src_uri = PatchTestDataStore[ 150 "%s-%s-%s" % (self.shortid(), patchtest_patterns.metadata_src_uri, pn) 151 ].split() 152 153 pretest_files = set([os.path.basename(patch) for patch in pretest_src_uri if patch.startswith('file://')]) 154 test_files = set([os.path.basename(patch) for patch in test_src_uri if patch.startswith('file://')]) 155 156 # check if files were removed 157 if len(test_files) < len(pretest_files): 158 159 # get removals from patchset 160 filesremoved_from_patchset = set() 161 for patch in self.patchset: 162 if patch.is_removed_file: 163 filesremoved_from_patchset.add(os.path.basename(patch.path)) 164 165 # get the deleted files from the SRC_URI 166 filesremoved_from_usr_uri = pretest_files - test_files 167 168 # finally, get those patches removed at SRC_URI and not removed from the patchset 169 # TODO: we are not taking into account renames, so test may raise false positives 170 not_removed = filesremoved_from_usr_uri - filesremoved_from_patchset 171 if not_removed: 172 self.fail('Patches not removed from tree. Remove them and amend the submitted mbox', 173 data=[('Patch', f) for f in not_removed]) 174 175 def test_summary_presence(self): 176 if not self.added: 177 self.skip('No added recipes, skipping test') 178 179 for pn in self.added: 180 # we are not interested in images 181 if 'core-image' in pn: 182 continue 183 rd = self.tinfoil.parse_recipe(pn) 184 summary = rd.getVar(patchtest_patterns.metadata_summary) 185 186 # "${PN} version ${PN}-${PR}" is the default, so fail if default 187 if summary.startswith("%s version" % pn): 188 self.fail( 189 "%s is missing in newly added recipe" % patchtest_patterns.metadata_summary 190 ) 191 192 def test_cve_check_ignore(self): 193 # Skip if we neither modified a recipe or target branches are not 194 # Nanbield and newer. CVE_CHECK_IGNORE was first deprecated in Nanbield. 195 if ( 196 not self.modified 197 or PatchtestParser.repo.patch.branch == "kirkstone" 198 or PatchtestParser.repo.patch.branch == "dunfell" 199 ): 200 self.skip("No modified recipes or older target branch, skipping test") 201 for pn in self.modified: 202 # we are not interested in images 203 if 'core-image' in pn: 204 continue 205 rd = self.tinfoil.parse_recipe(pn) 206 cve_check_ignore = rd.getVar(patchtest_patterns.cve_check_ignore_var) 207 208 if cve_check_ignore is not None: 209 self.fail( 210 "%s is deprecated and should be replaced by %s" 211 % (patchtest_patterns.cve_check_ignore_var, patchtest_patterns.cve_status_var) 212 ) 213