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' |
| 28 | |
| 29 | def test_license_presence(self): |
| 30 | if not self.added: |
| 31 | self.skip('No added recipes, skipping test') |
| 32 | |
| 33 | # TODO: this is a workaround so we can parse the recipe not |
| 34 | # containing the LICENSE var: add some default license instead |
| 35 | # of INVALID into auto.conf, then remove this line at the end |
| 36 | auto_conf = os.path.join(os.environ.get('BUILDDIR'), 'conf', 'auto.conf') |
| 37 | open_flag = 'w' |
| 38 | if os.path.exists(auto_conf): |
| 39 | open_flag = 'a' |
| 40 | with open(auto_conf, open_flag) as fd: |
| 41 | for pn in self.added: |
| 42 | fd.write('LICENSE ??= "%s"\n' % self.invalid_license) |
| 43 | |
| 44 | no_license = False |
| 45 | for pn in self.added: |
| 46 | rd = self.tinfoil.parse_recipe(pn) |
| 47 | license = rd.getVar(self.metadata_lic) |
| 48 | if license == self.invalid_license: |
| 49 | no_license = True |
| 50 | break |
| 51 | |
| 52 | # remove auto.conf line or the file itself |
| 53 | if open_flag == 'w': |
| 54 | os.remove(auto_conf) |
| 55 | else: |
| 56 | fd = open(auto_conf, 'r') |
| 57 | lines = fd.readlines() |
| 58 | fd.close() |
| 59 | with open(auto_conf, 'w') as fd: |
| 60 | fd.write(''.join(lines[:-1])) |
| 61 | |
| 62 | if no_license: |
| 63 | self.fail('Recipe does not have the LICENSE field set.') |
| 64 | |
| 65 | def test_lic_files_chksum_presence(self): |
| 66 | if not self.added: |
| 67 | self.skip('No added recipes, skipping test') |
| 68 | |
| 69 | for pn in self.added: |
| 70 | rd = self.tinfoil.parse_recipe(pn) |
| 71 | pathname = rd.getVar('FILE') |
| 72 | # we are not interested in images |
| 73 | if '/images/' in pathname: |
| 74 | continue |
| 75 | lic_files_chksum = rd.getVar(self.metadata_chksum) |
| 76 | if rd.getVar(self.license_var) == self.closed: |
| 77 | continue |
| 78 | if not lic_files_chksum: |
| 79 | self.fail('%s is missing in newly added recipe' % self.metadata_chksum) |
| 80 | |
| 81 | def test_lic_files_chksum_modified_not_mentioned(self): |
| 82 | if not self.modified: |
| 83 | self.skip('No modified recipes, skipping test') |
| 84 | |
| 85 | for patch in self.patchset: |
| 86 | # for the moment, we are just interested in metadata |
| 87 | if patch.path.endswith('.patch'): |
| 88 | continue |
| 89 | payload = str(patch) |
| 90 | if (self.lic_chksum_added.search_string(payload) or self.lic_chksum_removed.search_string(payload)): |
| 91 | # if any patch on the series contain reference on the metadata, fail |
| 92 | for commit in self.commits: |
| 93 | if self.lictag_re.search_string(commit.commit_message): |
| 94 | break |
| 95 | else: |
| 96 | self.fail('LIC_FILES_CHKSUM changed without "License-Update:" tag and description in commit message') |
| 97 | |
| 98 | def test_max_line_length(self): |
| 99 | for patch in self.patchset: |
| 100 | # for the moment, we are just interested in metadata |
| 101 | if patch.path.endswith('.patch'): |
| 102 | continue |
| 103 | payload = str(patch) |
| 104 | for line in payload.splitlines(): |
| 105 | if self.add_mark.search_string(line): |
| 106 | current_line_length = len(line[1:]) |
| 107 | if current_line_length > self.max_length: |
| 108 | self.fail('Patch line too long (current length %s, maximum is %s)' % (current_line_length, self.max_length), |
| 109 | data=[('Patch', patch.path), ('Line', '%s ...' % line[0:80])]) |
| 110 | |
| 111 | def pretest_src_uri_left_files(self): |
| 112 | # these tests just make sense on patches that can be merged |
| 113 | if not PatchTestInput.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['%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)] = rd.getVar(self.metadata_src_uri) |
| 125 | |
| 126 | def test_src_uri_left_files(self): |
| 127 | # these tests just make sense on patches that can be merged |
| 128 | if not PatchTestInput.repo.canbemerged: |
| 129 | self.skip('Patch cannot be merged') |
| 130 | if not self.modified: |
| 131 | self.skip('No modified recipes, skipping pretest') |
| 132 | |
| 133 | # get the proper metadata values |
| 134 | for pn in self.modified: |
| 135 | # we are not interested in images |
| 136 | if 'core-image' in pn: |
| 137 | continue |
| 138 | rd = self.tinfoil.parse_recipe(pn) |
| 139 | PatchTestDataStore['%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)] = rd.getVar(self.metadata_src_uri) |
| 140 | |
| 141 | for pn in self.modified: |
| 142 | pretest_src_uri = PatchTestDataStore['pre%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)].split() |
| 143 | test_src_uri = PatchTestDataStore['%s-%s-%s' % (self.shortid(), self.metadata_src_uri, pn)].split() |
| 144 | |
| 145 | pretest_files = set([os.path.basename(patch) for patch in pretest_src_uri if patch.startswith('file://')]) |
| 146 | test_files = set([os.path.basename(patch) for patch in test_src_uri if patch.startswith('file://')]) |
| 147 | |
| 148 | # check if files were removed |
| 149 | if len(test_files) < len(pretest_files): |
| 150 | |
| 151 | # get removals from patchset |
| 152 | filesremoved_from_patchset = set() |
| 153 | for patch in self.patchset: |
| 154 | if patch.is_removed_file: |
| 155 | filesremoved_from_patchset.add(os.path.basename(patch.path)) |
| 156 | |
| 157 | # get the deleted files from the SRC_URI |
| 158 | filesremoved_from_usr_uri = pretest_files - test_files |
| 159 | |
| 160 | # finally, get those patches removed at SRC_URI and not removed from the patchset |
| 161 | # TODO: we are not taking into account renames, so test may raise false positives |
| 162 | not_removed = filesremoved_from_usr_uri - filesremoved_from_patchset |
| 163 | if not_removed: |
| 164 | self.fail('Patches not removed from tree. Remove them and amend the submitted mbox', |
| 165 | data=[('Patch', f) for f in not_removed]) |
| 166 | |
| 167 | def test_summary_presence(self): |
| 168 | if not self.added: |
| 169 | self.skip('No added recipes, skipping test') |
| 170 | |
| 171 | for pn in self.added: |
| 172 | # we are not interested in images |
| 173 | if 'core-image' in pn: |
| 174 | continue |
| 175 | rd = self.tinfoil.parse_recipe(pn) |
| 176 | summary = rd.getVar(self.metadata_summary) |
| 177 | |
| 178 | # "${PN} version ${PN}-${PR}" is the default, so fail if default |
| 179 | if summary.startswith('%s version' % pn): |
| 180 | self.fail('%s is missing in newly added recipe' % self.metadata_summary) |