diff options
Diffstat (limited to 'meta/lib/patchtest/tests/base.py')
| -rw-r--r-- | meta/lib/patchtest/tests/base.py | 252 |
1 files changed, 0 insertions, 252 deletions
diff --git a/meta/lib/patchtest/tests/base.py b/meta/lib/patchtest/tests/base.py deleted file mode 100644 index 919ca136bb..0000000000 --- a/meta/lib/patchtest/tests/base.py +++ /dev/null | |||
| @@ -1,252 +0,0 @@ | |||
| 1 | # Base class to be used by all test cases defined in the suite | ||
| 2 | # | ||
| 3 | # Copyright (C) 2016 Intel Corporation | ||
| 4 | # | ||
| 5 | # SPDX-License-Identifier: GPL-2.0-only | ||
| 6 | |||
| 7 | import unittest | ||
| 8 | import logging | ||
| 9 | import json | ||
| 10 | import unidiff | ||
| 11 | from patchtest_parser import PatchtestParser | ||
| 12 | import mailbox | ||
| 13 | import patchtest_patterns | ||
| 14 | import collections | ||
| 15 | import sys | ||
| 16 | import os | ||
| 17 | import re | ||
| 18 | |||
| 19 | logger = logging.getLogger("patchtest") | ||
| 20 | debug = logger.debug | ||
| 21 | info = logger.info | ||
| 22 | warn = logger.warn | ||
| 23 | error = logger.error | ||
| 24 | |||
| 25 | Commit = collections.namedtuple( | ||
| 26 | "Commit", ["author", "subject", "commit_message", "shortlog", "payload"] | ||
| 27 | ) | ||
| 28 | |||
| 29 | Commit = collections.namedtuple('Commit', ['author', 'subject', 'commit_message', 'shortlog', 'payload']) | ||
| 30 | |||
| 31 | class PatchtestOEError(Exception): | ||
| 32 | """Exception for handling patchtest-oe errors""" | ||
| 33 | def __init__(self, message, exitcode=1): | ||
| 34 | super().__init__(message) | ||
| 35 | self.exitcode = exitcode | ||
| 36 | |||
| 37 | class Base(unittest.TestCase): | ||
| 38 | # if unit test fails, fail message will throw at least the following JSON: {"id": <testid>} | ||
| 39 | |||
| 40 | @staticmethod | ||
| 41 | def msg_to_commit(msg): | ||
| 42 | payload = msg.get_payload() | ||
| 43 | return Commit(subject=msg['subject'].replace('\n', ' ').replace(' ', ' '), | ||
| 44 | author=msg.get('From'), | ||
| 45 | shortlog=Base.shortlog(msg['subject']), | ||
| 46 | commit_message=Base.commit_message(payload), | ||
| 47 | payload=payload) | ||
| 48 | |||
| 49 | @staticmethod | ||
| 50 | def commit_message(payload): | ||
| 51 | commit_message = payload.__str__() | ||
| 52 | match = patchtest_patterns.endcommit_messages_regex.search(payload) | ||
| 53 | if match: | ||
| 54 | commit_message = payload[:match.start()] | ||
| 55 | return commit_message | ||
| 56 | |||
| 57 | @staticmethod | ||
| 58 | def shortlog(shlog): | ||
| 59 | # remove possible prefix (between brackets) before colon | ||
| 60 | start = shlog.find(']', 0, shlog.find(':')) | ||
| 61 | # remove also newlines and spaces at both sides | ||
| 62 | return shlog[start + 1:].replace('\n', '').strip() | ||
| 63 | |||
| 64 | @classmethod | ||
| 65 | def setUpClass(cls): | ||
| 66 | |||
| 67 | # General objects: mailbox.mbox and patchset | ||
| 68 | cls.mbox = mailbox.mbox(PatchtestParser.repo.patch.path) | ||
| 69 | |||
| 70 | # Patch may be malformed, so try parsing it | ||
| 71 | cls.unidiff_parse_error = '' | ||
| 72 | cls.patchset = None | ||
| 73 | try: | ||
| 74 | cls.patchset = unidiff.PatchSet.from_filename( | ||
| 75 | PatchtestParser.repo.patch.path, encoding="UTF-8" | ||
| 76 | ) | ||
| 77 | except unidiff.UnidiffParseError as upe: | ||
| 78 | cls.patchset = [] | ||
| 79 | cls.unidiff_parse_error = str(upe) | ||
| 80 | |||
| 81 | # Easy to iterate list of commits | ||
| 82 | cls.commits = [] | ||
| 83 | for msg in cls.mbox: | ||
| 84 | if msg['subject'] and msg.get_payload(): | ||
| 85 | cls.commits.append(Base.msg_to_commit(msg)) | ||
| 86 | |||
| 87 | cls.setUpClassLocal() | ||
| 88 | |||
| 89 | @classmethod | ||
| 90 | def tearDownClass(cls): | ||
| 91 | cls.tearDownClassLocal() | ||
| 92 | |||
| 93 | @classmethod | ||
| 94 | def setUpClassLocal(cls): | ||
| 95 | pass | ||
| 96 | |||
| 97 | @classmethod | ||
| 98 | def tearDownClassLocal(cls): | ||
| 99 | pass | ||
| 100 | |||
| 101 | def fail(self, issue, fix=None, commit=None, data=None): | ||
| 102 | """ Convert to a JSON string failure data""" | ||
| 103 | value = {'id': self.id(), | ||
| 104 | 'issue': issue} | ||
| 105 | |||
| 106 | if fix: | ||
| 107 | value['fix'] = fix | ||
| 108 | if commit: | ||
| 109 | value['commit'] = {'subject': commit.subject, | ||
| 110 | 'shortlog': commit.shortlog} | ||
| 111 | |||
| 112 | # extend return value with other useful info | ||
| 113 | if data: | ||
| 114 | value['data'] = data | ||
| 115 | |||
| 116 | return super(Base, self).fail(json.dumps(value)) | ||
| 117 | |||
| 118 | def skip(self, issue, data=None): | ||
| 119 | """ Convert the skip string to JSON""" | ||
| 120 | value = {'id': self.id(), | ||
| 121 | 'issue': issue} | ||
| 122 | |||
| 123 | # extend return value with other useful info | ||
| 124 | if data: | ||
| 125 | value['data'] = data | ||
| 126 | |||
| 127 | return super(Base, self).skipTest(json.dumps(value)) | ||
| 128 | |||
| 129 | def shortid(self): | ||
| 130 | return self.id().split('.')[-1] | ||
| 131 | |||
| 132 | def __str__(self): | ||
| 133 | return json.dumps({'id': self.id()}) | ||
| 134 | |||
| 135 | class Metadata(Base): | ||
| 136 | @classmethod | ||
| 137 | def setUpClassLocal(cls): | ||
| 138 | cls.tinfoil = cls.setup_tinfoil() | ||
| 139 | |||
| 140 | # get info about added/modified/remove recipes | ||
| 141 | cls.added, cls.modified, cls.removed = cls.get_metadata_stats(cls.patchset) | ||
| 142 | |||
| 143 | @classmethod | ||
| 144 | def tearDownClassLocal(cls): | ||
| 145 | cls.tinfoil.shutdown() | ||
| 146 | |||
| 147 | @classmethod | ||
| 148 | def setup_tinfoil(cls, config_only=False): | ||
| 149 | """Initialize tinfoil api from bitbake""" | ||
| 150 | |||
| 151 | # import relevant libraries | ||
| 152 | try: | ||
| 153 | scripts_path = os.path.join(PatchtestParser.repodir, "scripts", "lib") | ||
| 154 | if scripts_path not in sys.path: | ||
| 155 | sys.path.insert(0, scripts_path) | ||
| 156 | import scriptpath | ||
| 157 | scriptpath.add_bitbake_lib_path() | ||
| 158 | import bb.tinfoil | ||
| 159 | except ImportError: | ||
| 160 | raise PatchtestOEError('Could not import tinfoil module') | ||
| 161 | |||
| 162 | orig_cwd = os.path.abspath(os.curdir) | ||
| 163 | |||
| 164 | # Load tinfoil | ||
| 165 | tinfoil = None | ||
| 166 | try: | ||
| 167 | builddir = os.environ.get('BUILDDIR') | ||
| 168 | if not builddir: | ||
| 169 | logger.warn('Bitbake environment not loaded?') | ||
| 170 | return tinfoil | ||
| 171 | os.chdir(builddir) | ||
| 172 | tinfoil = bb.tinfoil.Tinfoil() | ||
| 173 | tinfoil.prepare(config_only=config_only) | ||
| 174 | except bb.tinfoil.TinfoilUIException as te: | ||
| 175 | if tinfoil: | ||
| 176 | tinfoil.shutdown() | ||
| 177 | raise PatchtestOEError('Could not prepare properly tinfoil (TinfoilUIException)') | ||
| 178 | except Exception as e: | ||
| 179 | if tinfoil: | ||
| 180 | tinfoil.shutdown() | ||
| 181 | raise e | ||
| 182 | finally: | ||
| 183 | os.chdir(orig_cwd) | ||
| 184 | |||
| 185 | return tinfoil | ||
| 186 | |||
| 187 | @classmethod | ||
| 188 | def get_metadata_stats(cls, patchset): | ||
| 189 | """Get lists of added, modified and removed metadata files""" | ||
| 190 | |||
| 191 | def find_pn(data, path): | ||
| 192 | """Find the PN from data""" | ||
| 193 | pn = None | ||
| 194 | pn_native = None | ||
| 195 | for _path, _pn in data: | ||
| 196 | if path in _path: | ||
| 197 | if 'native' in _pn: | ||
| 198 | # store the native PN but look for the non-native one first | ||
| 199 | pn_native = _pn | ||
| 200 | else: | ||
| 201 | pn = _pn | ||
| 202 | break | ||
| 203 | else: | ||
| 204 | # sent the native PN if found previously | ||
| 205 | if pn_native: | ||
| 206 | return pn_native | ||
| 207 | |||
| 208 | # on renames (usually upgrades), we need to check (FILE) base names | ||
| 209 | # because the unidiff library does not provided the new filename, just the modified one | ||
| 210 | # and tinfoil datastore, once the patch is merged, will contain the new filename | ||
| 211 | path_basename = path.split('_')[0] | ||
| 212 | for _path, _pn in data: | ||
| 213 | _path_basename = _path.split('_')[0] | ||
| 214 | if path_basename == _path_basename: | ||
| 215 | pn = _pn | ||
| 216 | return pn | ||
| 217 | |||
| 218 | if not cls.tinfoil: | ||
| 219 | cls.tinfoil = cls.setup_tinfoil() | ||
| 220 | |||
| 221 | added_paths, modified_paths, removed_paths = [], [], [] | ||
| 222 | added, modified, removed = [], [], [] | ||
| 223 | |||
| 224 | # get metadata filename additions, modification and removals | ||
| 225 | for patch in patchset: | ||
| 226 | if patch.path.endswith('.bb') or patch.path.endswith('.bbappend') or patch.path.endswith('.inc'): | ||
| 227 | if patch.is_added_file: | ||
| 228 | added_paths.append( | ||
| 229 | os.path.join( | ||
| 230 | os.path.abspath(PatchtestParser.repodir), patch.path | ||
| 231 | ) | ||
| 232 | ) | ||
| 233 | elif patch.is_modified_file: | ||
| 234 | modified_paths.append( | ||
| 235 | os.path.join( | ||
| 236 | os.path.abspath(PatchtestParser.repodir), patch.path | ||
| 237 | ) | ||
| 238 | ) | ||
| 239 | elif patch.is_removed_file: | ||
| 240 | removed_paths.append( | ||
| 241 | os.path.join( | ||
| 242 | os.path.abspath(PatchtestParser.repodir), patch.path | ||
| 243 | ) | ||
| 244 | ) | ||
| 245 | |||
| 246 | data = cls.tinfoil.cooker.recipecaches[''].pkg_fn.items() | ||
| 247 | |||
| 248 | added = [find_pn(data,path) for path in added_paths] | ||
| 249 | modified = [find_pn(data,path) for path in modified_paths] | ||
| 250 | removed = [find_pn(data,path) for path in removed_paths] | ||
| 251 | |||
| 252 | return [a for a in added if a], [m for m in modified if m], [r for r in removed if r] | ||
