• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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