[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: a new tool fedabipkgdiff
An updated patch with a small fix.
Regards,
Chenxiong Qi
----- Original Message -----
> From: "Chenxiong Qi" <cqi@redhat.com>
> To: libabigail@sourceware.org, dodji@redhat.com
> Sent: Wednesday, February 17, 2016 6:38:31 PM
> Subject: a new tool fedabipkgdiff
>
> Hi,
>
> This is a new tool fedabipkgdiff that would be much convenient for
> Fedora packagers to check potential ABI/API differences quickly using
> abipkgdiff shipped with libabigail. This tool came from a cool idea from
> Dodji. Currently, as the first step, it supports following ways,
>
> fedabipkgdiff --from fc23 ./foo-0.1-1.fc23.x86_64.rpm
> fedabipkgdiff --from fc23 --to fc24 foo
> fedabipkgdiff foo-0.1-1.fc23 foo-0.1-1.fc24
> fedabipkgdiff foo-0.1-1.fc23.i686 foo-0.1-1.fc24.i686
>
> For more details, please refer to
> https://sourceware.org/bugzilla/show_bug.cgi?id=19428
>
> Next step is to support the 4th use case mentioned in bug 19428.
>
> fedabipkgdiff is being under development, still need to improve. Welcome
> any feedback. Thanks.
>
> Happy hacking :)
>
> Regards,
> Chenxiong Qi
>
From 245ade840f7515d0319bb25c6c94c7171f7054f1 Mon Sep 17 00:00:00 2001
From: Chenxiong Qi <cqi@redhat.com>
Date: Tue, 9 Feb 2016 18:05:33 +0800
Subject: [PATCH] new tool of fedabipkgdiff
fedabipkgdiff is a convenient way for Fedora packagers to inspect ABI
compatibility issues quickly.
Currently with the first version of fedabipkgdiff, you can invoke it in
following ways.
fedabipkgdiff --from fc23 foo-0.1-1.fc23.x86_64.rpm
fedabipkgdiff --from fc23 --to fc24 foo
fedabipkgdiff foo-0.1-1.fc23 foo-0.1-1.fc24
fedabipkgdiff foo-0.1-1.fc23.i686 foo-0.1-1.fc24.i686
Bug 19428
---
.gitignore | 3 +
tests/Makefile.am | 4 +
tests/runtestfedabipkgdiff.sh | 5 +
tools/fedabipkgdiff | 855 ++++++++++++++++++++++++++++++++++++++++++
4 files changed, 867 insertions(+)
create mode 100755 tests/runtestfedabipkgdiff.sh
create mode 100755 tools/fedabipkgdiff
diff --git a/.gitignore b/.gitignore
index bb7c42a..169400a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@ Makefile.in
*.lo
*.o
*~
+*.swp
/aclocal.m4
/autom4te.cache/
@@ -17,3 +18,5 @@ Makefile.in
/include/abg-version.h
/*.pc
+
+.tags
diff --git a/tests/Makefile.am b/tests/Makefile.am
index caf49e6..958995c 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -31,6 +31,7 @@ runtestlookupsyms \
runtestaltdwarf \
runtestcorediff \
runtestabidiffexit \
+runtestfedabipkgdiff.sh \
$(CXX11_TESTS)
EXTRA_DIST = runtestcanonicalizetypes.sh.in
@@ -114,6 +115,9 @@ printdifftree_LDADD = $(top_builddir)/src/libabigail.la
runtestcanonicalizetypes_sh_SOURCES =
runtestcanonicalizetypes.sh$(EXEEXT):
+runtestfedabipkgdiff_sh_SOURCES =
+runtestfedabipkgdiff.sh$(EXEEXT):
+
AM_CPPFLAGS=-I${abs_top_srcdir}/include \
-I${abs_top_builddir}/include -I${abs_top_srcdir}/tools -fPIC
diff --git a/tests/runtestfedabipkgdiff.sh b/tests/runtestfedabipkgdiff.sh
new file mode 100755
index 0000000..4144419
--- /dev/null
+++ b/tests/runtestfedabipkgdiff.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/bash
+
+export FEDABIPKGDIFF_TESTS=1
+
+../../tools/fedabipkgdiff
\ No newline at end of file
diff --git a/tools/fedabipkgdiff b/tools/fedabipkgdiff
new file mode 100755
index 0000000..5d62db2
--- /dev/null
+++ b/tools/fedabipkgdiff
@@ -0,0 +1,855 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import argparse
+import glob
+import logging
+import os
+import re
+import shlex
+import subprocess
+import sys
+
+from itertools import groupby
+from urlparse import urlparse
+
+import koji
+
+"""
+Find proper RPM packages from koji to run abipkgdiff.
+
+Internal structure is
+
+fc23 fc24
+i686 i686
+ foo-0.1-1.fc23.i686.rpm foo-0.2-1.fc24.i686.rpm
+ foo-debuginfo-0.1-1.fc23.i686.rpm foo-debuginfo-0.2-1.fc23.i686.rpm
+x86_64 x86_64
+ foo-0.1-1.fc23.x86_64.rpm foo-0.2-1.fc24.x86_64.rpm
+ foo-debuginfo-0.1-1.fc23.x86_64.rpm foo-debuginfo-0.2-1.fc23.x86_64.rpm
+"""
+
+DEFAULT_KOJI_TOPDIR = 'https://kojipkgs.fedoraproject.org'
+DEFAULT_KOJI_SERVER = 'http://koji.fedoraproject.org/kojihub'
+
+HOME_DIR = os.path.join('/tmp',
+ os.path.splitext(os.path.basename(__file__))[0])
+
+global_config = None
+
+logging.basicConfig(format='%(levelname)s %(asctime)s %(name)s %(message)s',
+ level=logging.DEBUG)
+logger = logging.getLogger(os.path.basename(__file__))
+
+
+class KojiPackageNotFound(Exception):
+ """Package is not found in Koji"""
+
+
+class PackageNotFound(Exception):
+ """Package is not found locally"""
+
+
+class InvalidFedoraDistro(Exception):
+ """Invalid Fedora Distro"""
+
+
+class CannotFindLatestBuildError(Exception):
+ """Cannot find latest build from a package"""
+
+
+def is_fedora_distro(distro):
+ """Adjust if a distro is specific to Fedora
+
+ :param str distro: a string representing a distro value.
+ :return: True if distro is the one specific to Fedora, like fc5, fc24.
+ "rtype: bool
+ """
+ return re.match(r'^fc\d{1,2}$', distro) is not None
+
+
+class Brew(object):
+ """Proxy to kojihub XMLRPC with additional extensions to fedabipkgdiff"""
+
+ def __init__(self, baseurl):
+ self.session = koji.ClientSession(baseurl)
+
+ def listRPMs(self, **kwargs):
+ selector = kwargs.pop('selector')
+ rpms = self.session.listRPMs(**kwargs)
+
+ if selector:
+ rpms = [rpm for rpm in rpms if selector(rpm)]
+
+ return rpms
+
+ def getRPM(self, *args, **kwargs):
+ rpm = self.session.getRPM(*args, **kwargs)
+ if rpm is None:
+ raise KojipackageNotFound()
+ return rpm
+
+ def listBuilds(self, topone=None, selector=None, order_by=None,
+ reverse=None, **kwargs):
+ """Proxy to kojihub.listBuilds
+
+ Suport additional two keyword parameters:
+
+ - topone: return the top first one
+ - selector: a callable object used to select specific subset of builds
+ """
+ if 'state' not in kwargs:
+ kwargs['state'] = koji.BUILD_STATES['COMPLETE']
+
+ if selector is not None and not hasattr(selector, '__call__'):
+ raise TypeError(
+ '{0} is not a callable object.'.foramt(str(selector)))
+
+ if order_by is not None and not isinstance(order_by, basestring):
+ raise TypeError('order_by {0} is invalid.'.format(order_by))
+
+ builds = self.session.listBuilds(**kwargs)
+ if selector is not None:
+ builds = [build for build in builds if selector(build)]
+ if order_by is not None:
+ # FIXME: is it possible to sort builds by using opts parameter of
+ # listBuilds
+ builds = sorted(builds,
+ key=lambda item: item[order_by],
+ reverse=reverse)
+ if topone:
+ builds = builds[0:1]
+
+ return builds
+
+ def getPackage(self, name):
+ package = self.session.getPackage(name)
+ if package is None:
+ package = self.session.getPackage(name.rsplit('-', 1)[0])
+ if package is None:
+ raise KojiPackageNotFound()
+ return package
+
+ def getBuild(self, *args, **kwargs):
+ return self.session.getBuild(*args, **kwargs)
+
+ def get_rpm_build_id(self, name, version, release, arch=None):
+ """Get build ID that contains a rpm with specific nvra
+
+ If arch is omitted, a rpm can be identified easily by its N-V-R-A.
+
+ If arch is omitted, name is used to get associated package, and then
+ to get the build.
+
+ :param str name: name of a rpm
+ :param str version: version of a rpm
+ :param str release: release of a rpm
+ :param arch: arch of a rpm
+ :type arch: str or None
+ :return: the build from where the rpm is built
+ :rtype: dict
+ :raises KojiPackageNotFound: if name is not found from koji when arch
+ is None
+ """
+ if arch is None:
+ package = self.getPackage(name)
+ selector = lambda item: item['version'] == version and \
+ item['release'] == release
+ builds = self.listBuilds(packageID=package['id'],
+ selector=selector)
+ return builds[0]['build_id']
+ else:
+ rpm = self.getRPM({'name': name,
+ 'version': version,
+ 'release': release,
+ 'arch': arch,
+ })
+ return rpm['build_id']
+
+ def get_package_latest_build(self, package_name, distro):
+ """Get latest build from a package
+
+ :param str package_name: from which package to get the latest build
+ :param str distro: which distro the latest build belongs to
+ :return: the found build
+ :rtype: dict or None
+ """
+ package = self.getPackage(package_name)
+ selector = lambda item: item['release'].endswith(distro)
+ builds = self.listBuilds(packageID=package['id'],
+ selector=selector,
+ order_by='nvr',
+ reverse=True)
+ return builds[0] if builds else None
+
+ def select_rpms_from_a_build(self, build_id, package_name, arches=None):
+ """Select specific RPMs within a build
+
+ rpms could be filtered be specific criterias by the parameters.
+
+ :param int build_id: from which build to select rpms.
+ :param str package_name: which rpm to select that matches this name.
+ :param arches: which arches to select. If arches omits, rpms with all
+ arches except noarch and src will be selected.
+ :type arches: list, tuple or None
+ :return: a list of rpms returned from listRPMs
+ :rtype: list
+ """
+ def rpms_selector(package_name, excluded_arches):
+ return lambda rpm: \
+ rpm['arch'] not in excluded_arches and \
+ (rpm['name'] == package_name or
+ rpm['name'].endswith('-debuginfo'))
+
+ selector = rpms_selector(package_name, ('noarch', 'src'))
+ return self.listRPMs(buildID=build_id,
+ arches=arches,
+ selector=selector)
+
+ def get_latest_built_rpms(self, package_name, distro, arches=None):
+ """Get rpms from latest build of a package
+
+ By default, debuginfo rpm is also retrieved.
+
+ :param str package_name: from which package to get the rpms
+ :param str distro: which distro the rpms belong to
+ :param arches: which arches the rpms belong to
+ :type arches: str or None
+ :return: the selected rpms
+ :rtype: list
+ """
+ latest_build = self.get_package_latest_build(package_name, distro)
+
+ # Get rpm and debuginfo rpm from each arch
+ return self.select_rpms_from_a_build(latest_build['build_id'],
+ package_name,
+ arches=arches)
+
+
+def get_session():
+ return Brew(global_config.koji_server)
+
+
+def get_download_dir():
+ download_dir = os.path.join(HOME_DIR, 'downloads')
+ if not os.path.exists(download_dir):
+ os.makedirs(download_dir)
+ return download_dir
+
+
+def download_rpm(url):
+ cmd = shlex.split('wget -q -P {0} -c {1}'.format(get_download_dir(), url))
+ proc = subprocess.Popen(cmd)
+ s_stdout, s_stderr = proc.communicate()
+ if proc.returncode > 0:
+ logger.error('wget fails. returned code: %d. message: %s',
+ proc.returncode, s_stderr)
+ return False
+ return True
+
+
+def download_rpms(pkg_info):
+ def _download(pkg_info):
+ if os.path.exists(pkg_info['downloaded_rpm_file']):
+ logger.info('Reuse %s', pkg_info['downloaded_rpm_file'])
+ else:
+ logger.info('Download %s', pkg_info['download_url'])
+ download_rpm(pkg_info['download_url'])
+
+ for arch, rpm_infos in pkg_info.iteritems():
+ map(_download, rpm_infos)
+
+
+def find_rpm_filepath(rpm):
+ """Build RPM download URL"""
+ path_info = koji.PathInfo(topdir=global_config.koji_topdir)
+ session = get_session()
+ build = session.getBuild(rpm['build_id'])
+ return os.path.join(path_info.build(build), path_info.rpm(rpm))
+
+
+def find_local_debuginfo_rpm(rpm_file):
+ """Find debuginfo rpm package from a directory
+
+ :param str rpm_file: the rpm file name
+ :return: the absolute file name of the found debuginfo rpm
+ :rtype: str or None
+ """
+ search_dir = os.path.dirname(os.path.abspath(rpm_file))
+ nvra = koji.parse_NVRA(os.path.basename(rpm_file))
+ debuginfo_rpm_file_glob = os.path.join(
+ search_dir,
+ '%(name)s-debuginfo-%(version)s-%(release)s.%(arch)s.rpm' % nvra)
+
+ try:
+ debuginfo_rpm = glob.glob(debuginfo_rpm_file_glob)[0]
+ except IndexError:
+ return None
+
+ return debuginfo_rpm
+
+
+def abipkgdiff(pkg1_info, pkg2_info):
+ """Run abipkgdiff against found two RPM packages"""
+
+ pkg1_rpm, pkg1_debuginfo_rpm = pkg1_info
+ pkg2_rpm, pkg2_debuginfo_rpm = pkg2_info
+
+ cmd = 'abipkgdiff --d1 {0} --d2 {1} {2} {3}'.format(
+ pkg1_debuginfo_rpm, pkg2_debuginfo_rpm,
+ pkg1_rpm, pkg2_rpm)
+
+ if global_config.dry_run:
+ logger.info('DRY-RUN: %s', cmd)
+ return
+
+ logger.debug('Run: %s', cmd)
+
+ proc = subprocess.Popen(shlex.split(cmd))
+ return proc.wait()
+
+
+def run_abipkgdiff(pkg1_infos, pkg2_infos):
+ """Run abipkgdiff
+
+ If one of the executions finds ABI differences, the return code is the
+ return code from abipkgdiff.
+ """
+ arches = pkg1_infos.keys()
+ arches.sort()
+
+ return_code = 0
+
+ for arch in arches:
+ pkg1_info = pkg1_infos[arch]
+ pkg2_info = pkg2_infos[arch]
+
+ ret = abipkgdiff((pkg1_info[0]['downloaded_rpm_file'],
+ pkg1_info[1]['downloaded_rpm_file']),
+ (pkg2_info[0]['downloaded_rpm_file'],
+ pkg2_info[1]['downloaded_rpm_file']))
+ if ret > 0:
+ return_code = ret
+
+ return return_code
+
+
+def diff_local_rpm_with_latest_rpm_from_koji():
+ """Diff against local rpm and remove latest rpm
+
+ This operation handles a local rpm and debuginfo rpm and remote ones
+ located in remote Koji server, that has specific distro specificed by
+ argument --from.
+
+ 1/ Suppose the packager has just locally built a package named
+ foo-3.0.fc24.rpm. To compare the ABI of this locally build package with the
+ latest stable package from Fedora 23, one would do:
+
+ fedabidiff --from f23 ./foo-3.0.fc24.rpm
+ """
+
+ if not is_fedora_distro(global_config.from_distro):
+ raise InvalidFedoraDistro('Invalid Fedora distro {0}'.format(distro))
+
+ local_rpm_file = global_config.NVR[0]
+ if not os.path.exists(local_rpm_file):
+ raise ValueError('{0} does not exist.'.format(local_rpm_file))
+
+ local_debuginfo_rpm = find_local_debuginfo_rpm(local_rpm_file)
+ logger.debug('Found local debuginfo rpm %s', local_debuginfo_rpm)
+ if local_debuginfo_rpm is None:
+ raise ValueError(
+ 'debuginfo rpm {0} does not exist.'.format(local_debuginfo_rpm))
+
+ nvra = koji.parse_NVRA(os.path.basename(local_rpm_file))
+ session = get_session()
+ rpms = session.get_latest_built_rpms(nvra['name'],
+ global_config.from_distro,
+ arches=nvra['arch'])
+ pkg_infos = make_rpms_usable_for_abipkgdiff(rpms)
+ download_rpms(pkg_infos)
+
+ pkg_info = pkg_infos.values()[0]
+ return abipkgdiff((local_rpm_file, local_debuginfo_rpm),
+ (pkg_info[0]['downloaded_rpm_file'],
+ pkg_info[1]['downloaded_rpm_file']))
+
+
+def make_rpms_usable_for_abipkgdiff(rpms):
+ """
+ Construct result that contains mappings from arch to download url and
+ downloaded rpm filename of rpm and debuginfo rpm
+
+ :return: a mapping from an arch to a list of dict objects that contains
+ a URL from where to download the rpm, and an absolute path of a
+ predictable downloaded rpm filename.
+ :rtype: dict
+ """
+
+ result = {}
+
+ rpms_iter = groupby(sorted(rpms, key=lambda rpm: rpm['arch']),
+ key=lambda item: item['arch'])
+
+ for arch, rpms in rpms_iter:
+ l = []
+ # sorted ensures the order of rpm and associated debuginfo rpm
+ for item in sorted(rpms, key=lambda item: item['name']):
+ download_url = find_rpm_filepath(item)
+ l.append({
+ 'download_url': download_url,
+ 'downloaded_rpm_file': os.path.join(
+ get_download_dir(),
+ os.path.basename(urlparse(download_url).path)),
+ })
+ result[arch] = l
+
+ return result
+
+
+def diff_latest_rpms_based_on_distros():
+ """abipkgdiff rpms based on two distros
+
+ 2/ Suppose the packager wants to see how the ABIs of the package foo
+ evolved between fedora 19 and fedora 22. She would thus type the command:
+
+ fedabidiff --from f19 --to f22 foo
+ """
+
+ from_distro = global_config.from_distro
+ to_distro = global_config.to_distro
+
+ if not is_fedora_distro(from_distro):
+ raise InvalidFedoraDistro(
+ 'Invalid Fedora distro {0}'.format(from_distro))
+
+ if not is_fedora_distro(to_distro):
+ raise InvalidFedoraDistro(
+ 'Invalid Fedora distro {0}'.format(distro))
+
+ package_name = global_config.NVR[0]
+
+ session = get_session()
+
+ rpms = session.get_latest_built_rpms(package_name,
+ distro=global_config.from_distro)
+ pkg1_infos = make_rpms_usable_for_abipkgdiff(rpms)
+ download_rpms(pkg1_infos)
+
+ rpms = session.get_latest_built_rpms(package_name,
+ distro=global_config.to_distro)
+ pkg2_infos = make_rpms_usable_for_abipkgdiff(rpms)
+ download_rpms(pkg2_infos)
+
+ return run_abipkgdiff(pkg1_infos, pkg2_infos)
+
+
+def diff_rpms_with_nvra(name, version, release, arch=None):
+ session = get_session()
+
+ build_id = session.get_rpm_build_id(name, version, release, arch)
+ rpms = session.select_rpms_from_a_build(build_id, name, arches=arch)
+ return make_rpms_usable_for_abipkgdiff(rpms)
+
+
+def diff_two_nvras_from_koji():
+ """Diff two nvras from koji
+
+ The arch probably omits, that means febabipkgdiff will diff all arches. If
+ specificed, the specific arch will be handled.
+
+ 3/ Suppose the packager wants to compare the ABI of two packages designated
+ by their name and version. She would issue a command like this:
+
+ fedabidiff foo-1.0.fc19 foo-3.0.fc24
+ fedabidiff foo-1.0.fc19.i686 foo-1.0.fc24.i686
+ """
+ left_rpm = koji.parse_NVRA(global_config.NVR[0])
+ right_rpm = koji.parse_NVRA(global_config.NVR[1])
+
+ if is_fedora_distro(left_rpm['arch']) and \
+ is_fedora_distro(right_rpm['arch']):
+ nvr = koji.parse_NVR(global_config.NVR[0])
+ params1 = (nvr['name'], nvr['version'], nvr['release'])
+
+ nvr = koji.parse_NVR(global_config.NVR[1])
+ params2 = (nvr['name'], nvr['version'], nvr['release'])
+ else:
+ params1 = (left_rpm['name'],
+ left_rpm['version'],
+ left_rpm['release'],
+ left_rpm['arch'])
+ params2 = (right_rpm['name'],
+ right_rpm['version'],
+ right_rpm['release'],
+ right_rpm['arch'])
+
+ pkg1_infos = diff_rpms_with_nvra(*params1)
+ download_rpms(pkg1_infos)
+
+ pkg2_infos = diff_rpms_with_nvra(*params2)
+ download_rpms(pkg2_infos)
+
+ return run_abipkgdiff(pkg1_infos, pkg2_infos)
+
+
+def build_commandline_args_parser():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('NVR', nargs='+')
+ parser.add_argument('--server', required=False, dest='koji_server',
+ default=DEFAULT_KOJI_SERVER)
+ parser.add_argument('--topdir', required=False, dest='koji_topdir',
+ default=DEFAULT_KOJI_TOPDIR)
+ parser.add_argument('--dry-run', required=False, dest='dry_run',
+ action='store_true')
+ parser.add_argument('--from', required=False, metavar='DISTRO',
+ dest='from_distro')
+ parser.add_argument('--to', required=False, metavar='DISTRO',
+ dest='to_distro')
+
+ return parser
+
+
+def main():
+ parser = build_commandline_args_parser()
+
+ args = parser.parse_args()
+
+ global global_config
+ global_config = args
+
+ if global_config.from_distro and global_config.to_distro is None and \
+ global_config.NVR:
+ returncode = diff_local_rpm_with_latest_rpm_from_koji()
+
+ elif global_config.from_distro and global_config.to_distro and \
+ global_config.NVR:
+ returncode = diff_latest_rpms_based_on_distros()
+
+ elif global_config.from_distro is None and \
+ global_config.to_distro is None and len(global_config.NVR) > 1:
+ returncode = diff_two_nvras_from_koji()
+
+ else:
+ print >>sys.stderr, 'Unknown arguments. Please refer to -h.'
+ returncode = 1
+
+ return returncode
+
+
+invoked_from_cmd = __name__ == '__main__'
+
+if 'FEDABIPKGDIFF_TESTS' not in os.environ and invoked_from_cmd:
+ try:
+ sys.exit(main())
+ except Exception as e:
+ print >>sys.stderr, str(e)
+ sys.exit(1)
+
+
+import itertools
+import shutil
+import unittest
+
+
+try:
+ from mock import patch
+except ImportError:
+ print >>sys.stderr, \
+ 'mock is not installed. Please install it before running tests.'
+ sys.exit(1)
+
+counter = itertools.count(0)
+
+
+class UtilsTest(unittest.TestCase):
+
+ def test_is_fedora_distro(self):
+ distro = 'fc5'
+ self.assertTrue(is_fedora_distro(distro))
+
+ distro = 'f5'
+ self.assertFalse(is_fedora_distro(distro))
+
+ distro = 'fc23'
+ self.assertTrue(is_fedora_distro(distro))
+
+ distro = 'fc'
+ self.assertFalse(is_fedora_distro(distro))
+
+ distro = 'fc234'
+ self.assertFalse(is_fedora_distro(distro))
+
+
+class RunAbipkgdiffTest(unittest.TestCase):
+ """Test case for method run_abipkgdiff"""
+
+ def setUp(self):
+ self.pkg1_single_info = {
+ 'i686': [{'downloaded_rpm_file': 'dummy file path'},
+ {'downloaded_rpm_file': 'dummy file path'}],
+ }
+
+ self.pkg2_single_info = {
+ 'i686': [{'downloaded_rpm_file': 'dummy file path'},
+ {'downloaded_rpm_file': 'dummy file path'}],
+ }
+
+ self.pkg1_infos = {
+ 'i686': [{'downloaded_rpm_file': 'dummy file path'},
+ {'downloaded_rpm_file': 'dummy file path'}],
+ 'x86_64': [{'downloaded_rpm_file': 'dummy file path'},
+ {'downloaded_rpm_file': 'dummy file path'}],
+ 'armv7hl': [{'downloaded_rpm_file': 'dummy file path'},
+ {'downloaded_rpm_file': 'dummy file path'}],
+ }
+
+ self.pkg2_infos = {
+ 'i686': [{'downloaded_rpm_file': 'dummy file path'},
+ {'downloaded_rpm_file': 'dummy file path'}],
+ 'x86_64': [{'downloaded_rpm_file': 'dummy file path'},
+ {'downloaded_rpm_file': 'dummy file path'}],
+ 'armv7hl': [{'downloaded_rpm_file': 'dummy file path'},
+ {'downloaded_rpm_file': 'dummy file path'}],
+ }
+
+ @patch('__main__.abipkgdiff')
+ def test_all_success(self, mock_abipkgdiff):
+ mock_abipkgdiff.return_value = 0
+
+ result = run_abipkgdiff(self.pkg1_single_info, self.pkg2_single_info)
+ self.assertEquals(0, result)
+
+ result = run_abipkgdiff(self.pkg1_infos, self.pkg2_infos)
+ self.assertEquals(0, result)
+
+ @patch('__main__.abipkgdiff')
+ def test_all_failure(self, mock_abipkgdiff):
+ mock_abipkgdiff.return_value = 4
+
+ result = run_abipkgdiff(self.pkg1_single_info, self.pkg2_single_info)
+ self.assertEquals(4, result)
+
+ result = run_abipkgdiff(self.pkg1_infos, self.pkg2_infos)
+ self.assertEquals(4, result)
+
+ @patch('__main__.abipkgdiff', new=lambda param1, param2: counter.next())
+ def test_partial_failure(self):
+ result = run_abipkgdiff(self.pkg1_infos, self.pkg2_infos)
+ self.assertTrue(result > 0)
+
+
+fake_rpm_file = 'foo-0.1-1.fc24.x86_64.rpm'
+fake_debuginfo_rpm_file = 'foo-debuginfo-0.1-1.fc24.x86_64.rpm'
+
+
+class MockGlobalConfig(object):
+ koji_server = DEFAULT_KOJI_SERVER
+
+
+def mock_get_session():
+ return MockKojiClientSession(baseurl=DEFAULT_KOJI_SERVER)
+
+
+class MockKojiClientSession(object):
+
+ def __init__(self, *args, **kwargs):
+ """Accept arbitrary parameters but do nothing for this mock"""
+ self.args = args
+ self.kwargs = kwargs
+
+ def getPackage(self, *args, **kwargs):
+ return {
+ 'id': 1,
+ 'name': 'whatever a name of a package',
+ }
+
+ def listRPMs(self, *args, **kwargs):
+ return [{'arch': 'i686',
+ 'name': 'httpd-debuginfo',
+ 'nvr': 'httpd-debuginfo-2.4.18-1.fc22',
+ 'release': '1.fc22',
+ 'version': '2.4.18'},
+ {'arch': 'i686',
+ 'name': 'mod_session',
+ 'nvr': 'mod_session-2.4.18-1.fc22',
+ 'release': '1.fc22',
+ 'version': '2.4.18'},
+ {'arch': 'i686',
+ 'name': 'httpd',
+ 'nvr': 'httpd-2.4.18-1.fc22',
+ 'release': '1.fc22',
+ 'version': '2.4.18'},
+ {'arch': 'i686',
+ 'name': 'mod_proxy_html',
+ 'nvr': 'mod_proxy_html-2.4.18-1.fc22',
+ 'release': '1.fc22',
+ 'version': '2.4.18'},
+ {'arch': 'i686',
+ 'name': 'mod_ldap',
+ 'nvr': 'mod_ldap-2.4.18-1.fc22',
+ 'release': '1.fc22',
+ 'version': '2.4.18'},
+ {'arch': 'i686',
+ 'name': 'mod_ssl',
+ 'nvr': 'mod_ssl-2.4.18-1.fc22',
+ 'release': '1.fc22',
+ 'version': '2.4.18'}]
+
+ def listBuilds(self, *args, **kwargs):
+ return [
+ {'build_id': 720222,
+ 'name': 'httpd',
+ 'nvr': 'httpd-2.4.18-2.fc24',
+ 'release': '2.fc24',
+ 'version': '2.4.18'},
+ {'build_id': 708769,
+ 'name': 'httpd',
+ 'nvr': 'httpd-2.4.18-1.fc22',
+ 'release': '1.fc22',
+ 'version': '2.4.18'},
+ {'build_id': 708711,
+ 'name': 'httpd',
+ 'nvr': 'httpd-2.4.18-1.fc23',
+ 'release': '1.fc23',
+ 'version': '2.4.18'},
+ {'build_id': 705335,
+ 'name': 'httpd',
+ 'nvr': 'httpd-2.4.18-1.fc24',
+ 'release': '1.fc24',
+ 'version': '2.4.18'},
+ {'build_id': 704434,
+ 'name': 'httpd',
+ 'nvr': 'httpd-2.4.17-4.fc24',
+ 'release': '4.fc24',
+ 'version': '2.4.17'},
+ {'build_id': 704433,
+ 'name': 'httpd',
+ 'nvr': 'httpd-2.4.17-4.fc23',
+ 'release': '4.fc23',
+ 'version': '2.4.17'},
+ ]
+
+
+class SelectRpmsFromABuildTest(unittest.TestCase):
+ """Test case for select_rpms_from_a_build"""
+
+ def assert_rpms(self, rpms):
+ for item in rpms:
+ self.assertTrue(item['arch'] in ['i686', 'x86_64'])
+ self.assertTrue(item['name'] in ('httpd', 'httpd-debuginfo'))
+
+ @patch('__main__.Brew.listRPMs')
+ @patch('__main__.global_config', new=MockGlobalConfig)
+ def test_select_rpms_from_all_arches(self, mock_listRPMs):
+ mock_listRPMs.return_value = [
+ {'arch': 'i686',
+ 'name': 'httpd-debuginfo',
+ 'release': '1.fc22',
+ 'version': '2.4.18'},
+ {'arch': 'i686',
+ 'name': 'httpd',
+ 'release': '1.fc22',
+ 'version': '2.4.18'},
+ {'arch': 'x86_64',
+ 'name': 'httpd-debuginfo',
+ 'release': '1.fc22',
+ 'version': '2.4.18'},
+ {'arch': 'x86_64',
+ 'name': 'httpd',
+ 'release': '1.fc22',
+ 'version': '2.4.18'},
+ ]
+
+ session = get_session()
+ rpms = session.select_rpms_from_a_build(1, 'httpd')
+ self.assert_rpms(rpms)
+
+ @patch('__main__.Brew.listRPMs')
+ @patch('__main__.global_config', new=MockGlobalConfig)
+ def test_select_rpms_from_one_arch(self, mock_listRPMs):
+ mock_listRPMs.return_value = [
+ {'arch': 'i686',
+ 'name': 'httpd-debuginfo',
+ 'release': '1.fc22',
+ 'version': '2.4.18'},
+ {'arch': 'i686',
+ 'name': 'httpd',
+ 'release': '1.fc22',
+ 'version': '2.4.18'},
+ ]
+
+ session = get_session()
+ rpms = session.select_rpms_from_a_build(1, 'httpd')
+ self.assert_rpms(rpms)
+
+
+class GetPackageLatestBuildTest(unittest.TestCase):
+ """Test case for get_package_latest_build"""
+
+ @patch('__main__.global_config', new=MockGlobalConfig)
+ @patch('koji.ClientSession', new=MockKojiClientSession)
+ def test_get_latest_one(self):
+ session = get_session()
+ build = session.get_package_latest_build('httpd', 'fc23')
+ self.assertEquals('httpd-2.4.18-1.fc23', build['nvr'])
+
+ @patch('__main__.global_config', new=MockGlobalConfig)
+ @patch('koji.ClientSession', new=MockKojiClientSession)
+ def test_fail_to_find_latest_build(self):
+ session = get_session()
+ latest_build = session.get_package_latest_build('httpd', 'xxxx')
+ self.assertEquals(None, latest_build)
+
+
+class BrewListRPMsTest(unittest.TestCase):
+ """Test case for Brew.listRPMs"""
+
+ @patch('__main__.global_config', new=MockGlobalConfig)
+ @patch('koji.ClientSession', new=MockKojiClientSession)
+ def test_select_specific_rpms(self):
+ session = get_session()
+ selector = lambda rpm: rpm['name'].startswith('httpd')
+ rpms = session.listRPMs(buildID=1000, selector=selector)
+ self.assertTrue(
+ len(rpms) > 0,
+ 'More than one rpms should be selected. But, it\'s empty.')
+ for rpm in rpms:
+ self.assertTrue(rpm['name'] in ('httpd', 'httpd-debuginfo'),
+ '{0} should not be selected'.format(rpm['name']))
+
+
+class FindLocalDebuginfoRPMTest(unittest.TestCase):
+ """Test case for find_local_debuginfo_rpm"""
+
+ def setUp(self):
+ # FIXME: is it possible to patch glob or the underlying methods glob
+ # depends on
+ self.test_dir = './test_dir'
+ os.makedirs(self.test_dir)
+ os.system('touch {0}'.format(
+ os.path.join(self.test_dir, fake_debuginfo_rpm_file)))
+ os.system('touch {0}'.format(
+ os.path.join(self.test_dir,
+ fake_debuginfo_rpm_file.replace('foo', 'another'))))
+
+ def tearDown(self):
+ shutil.rmtree(self.test_dir)
+
+ def test_find_debuginfo_rpm(self):
+ debuginfo_rpm = find_local_debuginfo_rpm(
+ os.path.join(self.test_dir, fake_rpm_file))
+
+ expected_debuginfo_rpm_file = os.path.abspath(
+ os.path.join(self.test_dir,
+ fake_debuginfo_rpm_file))
+ self.assertEquals(expected_debuginfo_rpm_file, debuginfo_rpm)
+
+ def test_no_suitable_debuginfo_rpm(self):
+ debuginfo_rpm = find_local_debuginfo_rpm(
+ os.path.join(self.test_dir, 'abc-0.1-1.i686.rpm'))
+ self.assertEquals(None, debuginfo_rpm)
+
+
+if invoked_from_cmd:
+ unittest.main()
--
2.5.0