1#!/usr/bin/env python 2# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 3# -*- coding: utf-8 -*- 4# -*- Mode: Python 5# 6# Author: Dodji Seketeli 7# 8# Based on some preliminary work from Chenxiong Qi, posted to 9# https://sourceware.org/ml/libabigail/2016-q3/msg00031.html. 10 11 12'''A local-only version of the fedabipkgdiff program 13 14This program behaves exactly like the fedabipkgdiff tool, except 15that it doesn't use the network (via the Koji client) to get Fedora 16packages. The Koji client is the interface that is used to access 17the Fedora build system to get Fedora binary packages. 18 19This program thus loads the fedabipkgdiff tool and overloads 20(replaces) its Koji Client session with a fake (also known as a 21mock) Koji client named MockClientSession that gets the packages 22locally. Packages are stored under the directory 23tests/data/test-fedabipkgdiff/packages. This program then invokes 24the fedabipkgdiff program just as if the user invoked it from the 25command line. 26 27This program is useful for tests that can be called from "make 28check" because those must be able to perform in environments where 29no network is avaible. 30 31Please note that the descriptions of the builds of each package are 32stored in at least three global variables below: packages, builds 33and rpms. 34 35Whenever a user wants to add a new build to the local set of builds 36locally available via MockClientSession, she must update these three 37variables. 38''' 39 40import os 41import tempfile 42import six 43 44try: 45 from mock import patch 46except ImportError: 47 import sys 48 six.print_('mock is required to run tests. Please install before running' 49 ' tests.', file=sys.stderr) 50 sys.exit(1) 51 52ABIPKGDIFF = '@abs_top_builddir@/tools/abipkgdiff' 53FEDABIPKGDIFF = '@abs_top_srcdir@/tools/fedabipkgdiff' 54INPUT_DIR = '@abs_top_srcdir@/tests/data/test-fedabipkgdiff' 55OUTPUT_DIR = '@abs_top_builddir@/tests/output/test-fedabipkgdiff' 56TEST_TOPDIR = 'file://{0}'.format(INPUT_DIR) 57 58DOWNLOAD_CACHE_DIR_PREFIX=os.path.join(OUTPUT_DIR, 'download-cache') 59if not os.path.exists(DOWNLOAD_CACHE_DIR_PREFIX): 60 os.makedirs(DOWNLOAD_CACHE_DIR_PREFIX) 61DOWNLOAD_CACHE_DIR=tempfile.mkdtemp(dir=DOWNLOAD_CACHE_DIR_PREFIX) 62 63 64def get_download_dir(): 65 """Mock get_download_dir""" 66 if not os.path.exists(DOWNLOAD_CACHE_DIR): 67 os.makedirs(DOWNLOAD_CACHE_DIR) 68 return DOWNLOAD_CACHE_DIR 69 70 71def load_source(name, path): 72 # Different version of Python want this be done differently. 73 try: 74 import importlib.machinery 75 loader = importlib.machinery.SourceFileLoader(name, path) 76 import importlib.util 77 spec = importlib.util.spec_from_loader(name, loader) 78 module = importlib.util.module_from_spec(spec) 79 spec.loader.exec_module(module) 80 import sys.modules 81 sys.modules[name] = module 82 return module 83 except: 84 pass 85 try: 86 import importlib.machinery 87 loader = importlib.machinery.SourceFileLoader(name, path) 88 module = loader.load_module() 89 return module 90 except: 91 pass 92 import imp 93 module = imp.load_source(name, path) 94 return module 95 96 97# Import the fedabipkgdiff program file from the source directory. 98fedabipkgdiff_mod = load_source('fedabipkgdiff', FEDABIPKGDIFF) 99 100 101# ----------------- Koji resource storage begins ------------------ 102# 103# List all necessary Koji resources for running tests within this test 104# module. Currently, packages, builds, and rpms are listed here, and their 105# relationship is maintained well. At the same time, all information including 106# the ID for each package, build and rpm is real and can be queried from Koji, 107# that is for convenience once developers need this. 108# 109# When additional packages, builds and rpms are required for test cases, here 110# is the right place to add them. Just think them as a super simple in-memory 111# database, and methods of MockClientSession knows well how to read them. 112 113packages = [ 114 {'id': 286, 'name': 'gnutls'}, 115 {'id': 612, 'name': 'dbus-glib'}, 116 {'id': 9041, 'name': 'nss-util'}, 117 {'id': 18494, 'name': 'vte291'}, 118 ] 119 120builds = [ 121 {'build_id': 428835, 'nvr': 'dbus-glib-0.100-4.fc20', 122 'name': 'dbus-glib', 'release': '4.fc20', 'version': '0.100', 123 'package_id': 612, 'package_name': 'dbus-glib', 'state': 1, 124 }, 125 {'build_id': 430720, 'nvr': 'dbus-glib-0.100.2-1.fc20', 126 'name': 'dbus-glib', 'release': '1.fc20', 'version': '0.100.2', 127 'package_id': 612, 'package_name': 'dbus-glib', 'state': 1, 128 }, 129 {'build_id': 442366, 'nvr': 'dbus-glib-0.100.2-2.fc20', 130 'name': 'dbus-glib', 'release': '2.fc20', 'version': '0.100.2', 131 'package_id': 612, 'package_name': 'dbus-glib', 'state': 1, 132 }, 133 {'build_id': 715478, 'nvr': 'dbus-glib-0.106-1.fc23', 134 'name': 'dbus-glib', 'release': '1.fc23', 'version': '0.106', 135 'package_id': 612, 'package_name': 'dbus-glib', 'state': 1, 136 }, 137 {'build_id': 648058, 'nvr': 'dbus-glib-0.104-3.fc23', 138 'name': 'dbus-glib', 'release': '3.fc23', 'version': '0.104', 139 'package_id': 612, 'package_name': 'dbus-glib', 'state': 1, 140 }, 141 {'build_id': 613769, 'nvr': 'dbus-glib-0.104-2.fc23', 142 'name': 'dbus-glib', 'release': '2.fc23', 'version': '0.104', 143 'package_id': 612, 'package_name': 'dbus-glib', 'state': 1, 144 }, 145 146 {'build_id': 160295, 'nvr': 'nss-util-3.12.6-1.fc14', 147 'name': 'nss-util', 'version': '3.12.6', 'release': '1.fc14', 148 'package_id': 9041, 'package_name': 'nss-util', 'state': 1, 149 }, 150 {'build_id': 767978, 'nvr': 'nss-util-3.24.0-2.0.fc25', 151 'name': 'nss-util', 'version': '3.24.0', 'release': '2.0.fc25', 152 'package_id': 9041, 'package_name': 'nss-util', 'state': 1, 153 }, 154 155 # builds of package gnutls 156 {'build_id': 767306, 'nvr': 'gnutls-3.4.12-1.fc23', 157 'name': 'gnutls', 'release': '1.fc23', 'version': '3.4.12', 158 'package_id': 286, 'package_name': 'gnutls', 'state': 1, 159 }, 160 {'build_id': 770965, 'nvr': 'gnutls-3.4.13-1.fc23', 161 'name': 'gnutls', 'release': '1.fc23', 'version': '3.4.13', 162 'package_id': 286, 'package_name': 'gnutls', 'state': 1, 163 }, 164 {'build_id': 649701, 'nvr': 'gnutls-3.4.2-1.fc23', 165 'name': 'gnutls', 'release': '1.fc23', 'version': '3.4.2', 166 'package_id': 286, 'package_name': 'gnutls', 'state': 1, 167 }, 168 169 # builds of package vte291 170 {'build_id': 600011, 'nvr': 'vte291-0.39.1-1.fc22', 171 'name': 'vte291', 'version': '0.39.1', 'release': '1.fc22', 172 'package_id': 18494, 'package_name': 'vte291', 'state': 1, 173 }, 174 {'build_id': 612610, 'nvr': 'vte291-0.39.90-1.fc22', 175 'name': 'vte291', 'version': '0.39.90', 'release': '1.fc22', 176 'package_id': 18494, 'package_name': 'vte291', 'state': 1, 177 }, 178 ] 179 180rpms = [ 181 {'build_id': 442366, 182 'name': 'dbus-glib', 'release': '2.fc20', 'version': '0.100.2', 183 'arch': 'x86_64', 'nvr': 'dbus-glib-0.100.2-2.fc20', 184 }, 185 {'build_id': 442366, 186 'name': 'dbus-glib-devel', 'release': '2.fc20', 'version': '0.100.2', 187 'arch': 'x86_64', 'nvr': 'dbus-glib-devel-0.100.2-2.fc20', 188 }, 189 {'build_id': 442366, 190 'name': 'dbus-glib-debuginfo', 'release': '2.fc20', 'version': '0.100.2', 191 'arch': 'x86_64', 'nvr': 'dbus-glib-debuginfo-0.100.2-2.fc20', 192 }, 193 {'build_id': 442366, 194 'name': 'dbus-glib-devel', 'release': '2.fc20', 'version': '0.100.2', 195 'arch': 'i686', 'nvr': 'dbus-glib-devel-0.100.2-2.fc20', 196 }, 197 {'build_id': 442366, 198 'name': 'dbus-glib-debuginfo', 'release': '2.fc20', 'version': '0.100.2', 199 'arch': 'i686', 'nvr': 'dbus-glib-debuginfo-0.100.2-2.fc20', 200 }, 201 {'build_id': 442366, 202 'name': 'dbus-glib', 'version': '0.100.2', 'release': '2.fc20', 203 'arch': 'i686', 'nvr': 'dbus-glib-0.100.2-2.fc20', 204 }, 205 206 {'build_id': 715478, 207 'name': 'dbus-glib-debuginfo', 'version': '0.106', 'release': '1.fc23', 208 'arch': 'i686', 'nvr': 'dbus-glib-debuginfo-0.106-1.fc23', 209 }, 210 {'build_id': 715478, 211 'name': 'dbus-glib', 'version': '0.106', 'release': '1.fc23', 212 'arch': 'i686', 'nvr': 'dbus-glib-0.106-1.fc23', 213 }, 214 {'build_id': 715478, 215 'name': 'dbus-glib-devel', 'version': '0.106', 'release': '1.fc23', 216 'arch': 'i686', 'nvr': 'dbus-glib-devel-0.106-1.fc23', 217 }, 218 {'build_id': 715478, 219 'name': 'dbus-glib', 'version': '0.106', 'release': '1.fc23', 220 'arch': 'x86_64', 'nvr': 'dbus-glib-0.106-1.fc23', 221 }, 222 {'build_id': 715478, 223 'name': 'dbus-glib-debuginfo', 'version': '0.106', 'release': '1.fc23', 224 'arch': 'x86_64', 'nvr': 'dbus-glib-debuginfo-0.106-1.fc23', 225 }, 226 {'build_id': 715478, 227 'name': 'dbus-glib-devel', 'release': '1.fc23', 'version': '0.106', 228 'arch': 'x86_64', 'nvr': 'dbus-glib-devel-0.106-1.fc23', 229 }, 230 231 # RPMs of build nss-util-3.12.6-1.fc14 232 {'build_id': 160295, 233 'name': 'nss-util', 'release': '1.fc14', 'version': '3.12.6', 234 'arch': 'x86_64', 'nvr': 'nss-util-3.12.6-1.fc14', 235 }, 236 {'build_id': 160295, 237 'name': 'nss-util-devel', 'release': '1.fc14', 'version': '3.12.6', 238 'arch': 'x86_64', 'nvr': 'nss-util-devel-3.12.6-1.fc14', 239 }, 240 {'build_id': 160295, 241 'name': 'nss-util-debuginfo', 'release': '1.fc14', 'version': '3.12.6', 242 'arch': 'x86_64', 'nvr': 'nss-util-debuginfo-3.12.6-1.fc14', 243 }, 244 245 # RPMs of build nss-util-3.24.0-2.0.fc25 246 {'build_id': 767978, 247 'name': 'nss-util-debuginfo', 'release': '2.0.fc25', 'version': '3.24.0', 248 'arch': 'x86_64', 'nvr': 'nss-util-debuginfo-3.24.0-2.0.fc25', 249 }, 250 {'build_id': 767978, 251 'name': 'nss-util', 'release': '2.0.fc25', 'version': '3.24.0', 252 'arch': 'x86_64', 'nvr': 'nss-util-3.24.0-2.0.fc25', 253 }, 254 {'build_id': 767978, 255 'name': 'nss-util-devel', 'release': '2.0.fc25', 'version': '3.24.0', 256 'arch': 'x86_64', 'nvr': 'nss-util-devel-3.24.0-2.0.fc25', 257 }, 258 # RPMs of build vte291-0.39.1-1.fc22 259 {'build_id': 600011, 260 'name': 'vte291', 'version': '0.39.1', 'release': '1.fc22', 261 'arch': 'x86_64', 'nvr': 'vte291-0.39.1-1.fc22', 262 }, 263 {'build_id': 600011, 264 'name': 'vte291-devel', 'version': '0.39.1', 'release': '1.fc22', 265 'arch': 'x86_64', 'nvr': 'vte291-0.39.1-1.fc22', 266 }, 267 {'build_id': 600011, 268 'name': 'vte291-debuginfo', 'version': '0.39.1', 'release': '1.fc22', 269 'arch': 'x86_64', 'nvr': 'vte291-0.39.1-1.fc22', 270 }, 271 272 # RPMs of build vte291-0.39.90-1.fc22 273 {'build_id': 612610, 274 'name': 'vte291', 'version': '0.39.90', 'release': '1.fc22', 275 'arch': 'x86_64', 'nvr': 'vte291-0.39.90-1.fc22', 276 }, 277 {'build_id': 612610, 278 'name': 'vte291-devel', 'version': '0.39.90', 'release': '1.fc22', 279 'arch': 'x86_64', 'nvr': 'vte291-0.39.90-1.fc22', 280 }, 281 {'build_id': 612610, 282 'name': 'vte291-debuginfo', 'version': '0.39.90', 'release': '1.fc22', 283 'arch': 'x86_64', 'nvr': 'vte291-0.39.90-1.fc22', 284 }, 285 ] 286 287# ----------------- End of Koji resource storage ------------------ 288 289 290class MockClientSession(object): 291 """Mock koji.ClientSession 292 293 This mock ClientSession aims to avoid touching a real Koji instance to 294 interact with XMLRPC APIs required by fedabipkgdiff. 295 296 For the tests within this module, methods do not necessarily to return 297 complete RPM and build information. So, if you need more additional 298 information, here is the right place to add them. 299 """ 300 301 def __init__(self, baseurl): 302 """Initialize a mock ClientSession 303 304 :param str baseurl: the URL to remote kojihub service. As of writing 305 this mock class, `baseurl` is not used at all, just keep it here if 306 it's useful in the future. 307 308 All mock methods have same signature as corresponding kojihub.* 309 methods, and type of parameters may be different and only satify 310 fedabipkgdiff requirement. 311 """ 312 self.baseurl = baseurl 313 314 def getPackage(self, name): 315 """Mock kojihub.getPackage 316 317 :param str name: name of package to find and return 318 :return: the found package 319 :rtype: dict 320 """ 321 assert isinstance(name, six.string_types) 322 323 def selector(package): 324 return package['name'] == name 325 326 return [p for p in packages if selector(p)][0] 327 328 def getBuild(self, build_id): 329 """Mock kojihub.getBuild 330 331 :param int build_id: ID of build to find and return 332 :return: the found build 333 :rtype: dict 334 """ 335 assert isinstance(build_id, int) 336 337 def selector(build): 338 return build['build_id'] == build_id 339 340 return [b for b in builds if selector(b)][0] 341 342 def listBuilds(self, packageID, state=None): 343 """Mock kojihub.listBuilds 344 345 :param int packageID: ID of package whose builds is found and returned 346 :param state: build state. If state is omitted, all builds of a package 347 are returned 348 :type state: int or None 349 """ 350 assert isinstance(packageID, int) 351 if state is not None: 352 assert isinstance(state, int) 353 354 def selector(build): 355 selected = build['package_id'] == packageID 356 if state is not None: 357 selected = selected and build['state'] == state 358 return selected 359 360 return filter(selector, builds) 361 362 def getRPM(self, rpminfo): 363 """Mock kojihub.getRPM 364 365 :param dict rpminfo: a mapping containing rpm information, at least, 366 it contains name, version, release, and arch. 367 """ 368 assert isinstance(rpminfo, dict) 369 370 def selector(rpm): 371 return rpm['name'] == rpminfo['name'] and \ 372 rpm['version'] == rpminfo['version'] and \ 373 rpm['release'] == rpminfo['release'] and \ 374 rpm['arch'] == rpminfo['arch'] 375 376 return [rpm for rpm in rpms if selector(rpm)][0] 377 378 def listRPMs(self, buildID, arches=None): 379 """Mock kojihub.listRPMs 380 381 :param int buildID: ID of build from which to list rpms 382 :param arches: to list rpms built for specific arches. If arches is 383 omitted, rpms of all arches will be listed. 384 :type arches: list, tuple, str, or None 385 :return: list of rpms 386 :rtype: list 387 """ 388 assert isinstance(buildID, int) 389 if arches is not None: 390 assert isinstance(arches, (tuple, list, six.string_types)) 391 392 if arches is not None and isinstance(arches, six.string_types): 393 arches = [arches] 394 395 def selector(rpm): 396 selected = rpm['build_id'] == buildID 397 if arches is not None: 398 selected = selected and rpm['arch'] in arches 399 return selected 400 401 return [rpm for rpm in rpms if selector(rpm)] 402 403 404@patch('koji.ClientSession', new=MockClientSession) 405@patch('fedabipkgdiff.DEFAULT_KOJI_TOPURL', new=TEST_TOPDIR) 406@patch('fedabipkgdiff.DEFAULT_ABIPKGDIFF', new=ABIPKGDIFF) 407@patch('fedabipkgdiff.get_download_dir', new=get_download_dir) 408def run_fedabipkgdiff(): 409 return fedabipkgdiff_mod.main() 410 411 412def do_main(): 413 run_fedabipkgdiff() 414 415 416if __name__ == '__main__': 417 do_main() 418