Patrick Williams | ac13d5f | 2023-11-24 18:59:46 -0600 | [diff] [blame] | 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 | |
| 7 | import base |
| 8 | import os |
| 9 | import pyparsing |
| 10 | from data import PatchTestInput, PatchTestDataStore |
| 11 | |
| 12 | class TestMetadata(base.Metadata): |
| 13 | metadata_lic = 'LICENSE' |
| 14 | invalid_license = 'PATCHTESTINVALID' |
| 15 | metadata_chksum = 'LIC_FILES_CHKSUM' |
| 16 | license_var = 'LICENSE' |
| 17 | closed = 'CLOSED' |
| 18 | lictag_re = pyparsing.AtLineStart("License-Update:") |
| 19 | lic_chksum_added = pyparsing.AtLineStart("+" + metadata_chksum) |
| 20 | lic_chksum_removed = pyparsing.AtLineStart("-" + metadata_chksum) |
| 21 | add_mark = pyparsing.Regex('\+ ') |
| 22 | max_length = 200 |
| 23 | metadata_src_uri = 'SRC_URI' |
| 24 | md5sum = 'md5sum' |
| 25 | sha256sum = 'sha256sum' |
| 26 | git_regex = pyparsing.Regex('^git\:\/\/.*') |
| 27 | metadata_summary = 'SUMMARY' |
Patrick Williams | 169d7bc | 2024-01-05 11:33:25 -0600 | [diff] [blame] | 28 | cve_check_ignore_var = 'CVE_CHECK_IGNORE' |
| 29 | cve_status_var = 'CVE_STATUS' |
Patrick Williams | ac13d5f | 2023-11-24 18:59:46 -0600 | [diff] [blame] | 30 | |
| 31 | def test_license_presence(self): |
| 32 | if not self.added: |
| 33 | self.skip('No added recipes, skipping test') |
| 34 | |
| 35 | # TODO: this is a workaround so we can parse the recipe not |
| 36 | # containing the LICENSE var: add some default license instead |
| 37 | # of INVALID into auto.conf, then remove this line at the end |
| 38 | auto_conf = os.path.join(os.environ.get('BUILDDIR'), 'conf', 'auto.conf') |
| 39 | open_flag = 'w' |
| 40 | if os.path.exists(auto_conf): |
| 41 | open_flag = 'a' |
| 42 | with open(auto_conf, open_flag) as fd: |
| 43 | for pn in self.added: |
| 44 | fd.write('LICENSE ??= "%s"\n' % self.invalid_license) |
| 45 | |
| 46 | no_license = False |
| 47 | for pn in self.added: |
| 48 | rd = self.tinfoil.parse_recipe(pn) |
| 49 | license = rd.getVar(self.metadata_lic) |
| 50 | if license == self.invalid_license: |
| 51 | no_license = True |
| 52 | break |
| 53 | |
| 54 | # remove auto.conf line or the file itself |
| 55 | if open_flag == 'w': |
| 56 | os.remove(auto_conf) |
| 57 | else: |
| 58 | fd = open(auto_conf, 'r') |
| 59 | lines = fd.readlines() |
| 60 | fd.close() |
| 61 | with open(auto_conf, 'w') as fd: |
| 62 | fd.write(''.join(lines[:-1])) |
| 63 | |
| 64 | if no_license: |
| 65 | self.fail('Recipe does not have the LICENSE field set.') |
| 66 | |
| 67 | def test_lic_files_chksum_presence(self): |
| 68 | if not self.added: |
| 69 | self.skip('No added recipes, skipping test') |
| 70 | |
| 71 | for pn in self.added: |
| 72 | rd = self.tinfoil.parse_recipe(pn) |
| 73 | pathname = rd.getVar('FILE') |
| 74 | # we are not interested in images |
| 75 | if '/images/' in pathname: |
| 76 | continue |
| 77 | lic_files_chksum = rd.getVar(self.metadata_chksum) |
| 78 | if rd.getVar(self.license_var) == self.closed: |
| 79 | continue |
| 80 | if not lic_files_chksum: |
| 81 | self.fail('%s is missing in newly added recipe' % self.metadata_chksum) |
| 82 | |
| 83 | def test_lic_files_chksum_modified_not_mentioned(self): |
| 84 | if not self.modified: |
| 85 | self.skip('No modified recipes, skipping test') |
| 86 | |
| 87 | for patch in self.patchset: |
| 88 | # for the moment, we are just interested in metadata |
| 89 | if patch.path.endswith('.patch'): |
| 90 | continue |
| 91 | payload = str(patch) |
| 92 | if (self.lic_chksum_added.search_string(payload) or self.lic_chksum_removed.search_string(payload)): |
| 93 | # if any patch on the series contain reference on the metadata, fail |
| 94 | for commit in self.commits: |
| 95 | if self.lictag_re.search_string(commit.commit_message): |
| 96 | break |
| 97 | else: |
| 98 | self.fail('LIC_FILES_CHKSUM changed without "License-Update:" tag and description in commit message') |
| 99 | |
| 100 | def test_max_line_length(self): |
| 101 | for patch in self.patchset: |
| 102 | # for the moment, we are just interested in metadata |
| 103 | if patch.path.endswith('.patch'): |
| 104 | continue |
| 105 | payload = str(patch) |
| 106 | for line in payload.splitlines(): |
| 107 | if self.add_mark.search_string(line): |
| 108 | current_line_length = len(line[1:]) |
| 109 | if current_line_length > self.max_length: |
| 110 | self.fail('Patch line too long (current length %s, maximum is %s)' % (current_line_length, self.max_length), |
| 111 | data=[('Patch', patch.path), ('Line', '%s ...' % line[0:80])]) |
| 112 | |
| 113 | def pretest_src_uri_left_files(self): |
| 114 | # these tests just make sense on patches that can be merged |
| 115 | if not PatchTestInput.repo.canbemerged: |
| 116 | self.skip('Patch cannot be merged') |
| 117 | if not self.modified: |
| 118 | self.skip('No modified recipes, skipping pretest') |
| 119 | |
| 120 | # get the proper metadata values |
| 121 | for pn in self.modified: |
| 122 | # we are not interested in images |
| 123 | if 'core-image' in pn: |
| 124 | continue |
| 125 | rd = self.tinfoil.parse_recipe(pn) |
| 126 | PatchTestDataStore['%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)] = rd.getVar(self.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 PatchTestInput.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['%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)] = rd.getVar(self.metadata_src_uri) |
| 142 | |
| 143 | for pn in self.modified: |
| 144 | pretest_src_uri = PatchTestDataStore['pre%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)].split() |
| 145 | test_src_uri = PatchTestDataStore['%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)].split() |
| 146 | |
| 147 | pretest_files = set([os.path.basename(patch) for patch in pretest_src_uri if patch.startswith('file://')]) |
| 148 | test_files = set([os.path.basename(patch) for patch in test_src_uri if patch.startswith('file://')]) |
| 149 | |
| 150 | # check if files were removed |
| 151 | if len(test_files) < len(pretest_files): |
| 152 | |
| 153 | # get removals from patchset |
| 154 | filesremoved_from_patchset = set() |
| 155 | for patch in self.patchset: |
| 156 | if patch.is_removed_file: |
| 157 | filesremoved_from_patchset.add(os.path.basename(patch.path)) |
| 158 | |
| 159 | # get the deleted files from the SRC_URI |
| 160 | filesremoved_from_usr_uri = pretest_files - test_files |
| 161 | |
| 162 | # finally, get those patches removed at SRC_URI and not removed from the patchset |
| 163 | # TODO: we are not taking into account renames, so test may raise false positives |
| 164 | not_removed = filesremoved_from_usr_uri - filesremoved_from_patchset |
| 165 | if not_removed: |
| 166 | self.fail('Patches not removed from tree. Remove them and amend the submitted mbox', |
| 167 | data=[('Patch', f) for f in not_removed]) |
| 168 | |
| 169 | def test_summary_presence(self): |
| 170 | if not self.added: |
| 171 | self.skip('No added recipes, skipping test') |
| 172 | |
| 173 | for pn in self.added: |
| 174 | # we are not interested in images |
| 175 | if 'core-image' in pn: |
| 176 | continue |
| 177 | rd = self.tinfoil.parse_recipe(pn) |
| 178 | summary = rd.getVar(self.metadata_summary) |
| 179 | |
| 180 | # "${PN} version ${PN}-${PR}" is the default, so fail if default |
| 181 | if summary.startswith('%s version' % pn): |
| 182 | self.fail('%s is missing in newly added recipe' % self.metadata_summary) |
Patrick Williams | 169d7bc | 2024-01-05 11:33:25 -0600 | [diff] [blame] | 183 | |
| 184 | def test_cve_check_ignore(self): |
| 185 | if not self.modified: |
| 186 | self.skip('No modified recipes, skipping test') |
| 187 | for pn in self.modified: |
| 188 | # we are not interested in images |
| 189 | if 'core-image' in pn: |
| 190 | continue |
| 191 | rd = self.tinfoil.parse_recipe(pn) |
| 192 | cve_check_ignore = rd.getVar(self.cve_check_ignore_var) |
| 193 | |
| 194 | if cve_check_ignore is not None: |
| 195 | self.fail('%s is deprecated and should be replaced by %s' % (self.cve_check_ignore_var, self.cve_status_var)) |