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 imp
43import six
44
45try:
46    from mock import patch
47except ImportError:
48    import sys
49    six.print_('mock is required to run tests. Please install before running'
50               ' tests.', file=sys.stderr)
51    sys.exit(1)
52
53ABIPKGDIFF = '@abs_top_builddir@/tools/abipkgdiff'
54FEDABIPKGDIFF = '@abs_top_srcdir@/tools/fedabipkgdiff'
55INPUT_DIR =  '@abs_top_srcdir@/tests/data/test-fedabipkgdiff'
56OUTPUT_DIR = '@abs_top_builddir@/tests/output/test-fedabipkgdiff'
57TEST_TOPDIR = 'file://{0}'.format(INPUT_DIR)
58
59DOWNLOAD_CACHE_DIR_PREFIX=os.path.join(OUTPUT_DIR, 'download-cache')
60if not os.path.exists(DOWNLOAD_CACHE_DIR_PREFIX):
61    os.makedirs(DOWNLOAD_CACHE_DIR_PREFIX)
62DOWNLOAD_CACHE_DIR=tempfile.mkdtemp(dir=DOWNLOAD_CACHE_DIR_PREFIX)
63
64
65def get_download_dir():
66    """Mock get_download_dir"""
67    if not os.path.exists(DOWNLOAD_CACHE_DIR):
68        os.makedirs(DOWNLOAD_CACHE_DIR)
69    return DOWNLOAD_CACHE_DIR
70
71
72# Import the fedabipkgdiff program file from the source directory.
73fedabipkgdiff_mod = imp.load_source('fedabipkgdiff', FEDABIPKGDIFF)
74
75
76# -----------------  Koji resource storage begins ------------------
77#
78# List all necessary Koji resources for running tests within this test
79# module. Currently, packages, builds, and rpms are listed here, and their
80# relationship is maintained well. At the same time, all information including
81# the ID for each package, build and rpm is real and can be queried from Koji,
82# that is for convenience once developers need this.
83#
84# When additional packages, builds and rpms are required for test cases, here
85# is the right place to add them. Just think them as a super simple in-memory
86# database, and methods of MockClientSession knows well how to read them.
87
88packages = [
89    {'id': 286, 'name': 'gnutls'},
90    {'id': 612, 'name': 'dbus-glib'},
91    {'id': 9041, 'name': 'nss-util'},
92    {'id': 18494, 'name': 'vte291'},
93    ]
94
95builds = [
96    {'build_id': 428835, 'nvr': 'dbus-glib-0.100-4.fc20',
97     'name': 'dbus-glib', 'release': '4.fc20', 'version': '0.100',
98     'package_id': 612, 'package_name': 'dbus-glib', 'state': 1,
99     },
100    {'build_id': 430720, 'nvr': 'dbus-glib-0.100.2-1.fc20',
101     'name': 'dbus-glib', 'release': '1.fc20', 'version': '0.100.2',
102     'package_id': 612, 'package_name': 'dbus-glib', 'state': 1,
103     },
104    {'build_id': 442366, 'nvr': 'dbus-glib-0.100.2-2.fc20',
105     'name': 'dbus-glib', 'release': '2.fc20', 'version': '0.100.2',
106     'package_id': 612, 'package_name': 'dbus-glib', 'state': 1,
107     },
108    {'build_id': 715478, 'nvr': 'dbus-glib-0.106-1.fc23',
109     'name': 'dbus-glib', 'release': '1.fc23', 'version': '0.106',
110     'package_id': 612, 'package_name': 'dbus-glib', 'state': 1,
111     },
112    {'build_id': 648058, 'nvr': 'dbus-glib-0.104-3.fc23',
113     'name': 'dbus-glib', 'release': '3.fc23', 'version': '0.104',
114     'package_id': 612, 'package_name': 'dbus-glib', 'state': 1,
115     },
116    {'build_id': 613769, 'nvr': 'dbus-glib-0.104-2.fc23',
117     'name': 'dbus-glib', 'release': '2.fc23', 'version': '0.104',
118     'package_id': 612, 'package_name': 'dbus-glib', 'state': 1,
119     },
120
121    {'build_id': 160295, 'nvr': 'nss-util-3.12.6-1.fc14',
122     'name': 'nss-util', 'version': '3.12.6', 'release': '1.fc14',
123     'package_id': 9041, 'package_name': 'nss-util', 'state': 1,
124     },
125    {'build_id': 767978, 'nvr': 'nss-util-3.24.0-2.0.fc25',
126     'name': 'nss-util', 'version': '3.24.0', 'release': '2.0.fc25',
127     'package_id': 9041, 'package_name': 'nss-util', 'state': 1,
128     },
129
130    # builds of package gnutls
131    {'build_id': 767306, 'nvr': 'gnutls-3.4.12-1.fc23',
132     'name': 'gnutls', 'release': '1.fc23', 'version': '3.4.12',
133     'package_id': 286, 'package_name': 'gnutls', 'state': 1,
134     },
135    {'build_id': 770965, 'nvr': 'gnutls-3.4.13-1.fc23',
136     'name': 'gnutls', 'release': '1.fc23', 'version': '3.4.13',
137     'package_id': 286, 'package_name': 'gnutls', 'state': 1,
138     },
139    {'build_id': 649701, 'nvr': 'gnutls-3.4.2-1.fc23',
140     'name': 'gnutls', 'release': '1.fc23', 'version': '3.4.2',
141     'package_id': 286, 'package_name': 'gnutls', 'state': 1,
142     },
143
144    # builds of package vte291
145    {'build_id': 600011, 'nvr': 'vte291-0.39.1-1.fc22',
146     'name': 'vte291', 'version': '0.39.1', 'release': '1.fc22',
147     'package_id': 18494, 'package_name': 'vte291', 'state': 1,
148     },
149    {'build_id': 612610, 'nvr': 'vte291-0.39.90-1.fc22',
150     'name': 'vte291', 'version': '0.39.90', 'release': '1.fc22',
151     'package_id': 18494, 'package_name': 'vte291', 'state': 1,
152     },
153    ]
154
155rpms = [
156    {'build_id': 442366,
157     'name': 'dbus-glib', 'release': '2.fc20', 'version': '0.100.2',
158     'arch': 'x86_64', 'nvr': 'dbus-glib-0.100.2-2.fc20',
159     },
160    {'build_id': 442366,
161     'name': 'dbus-glib-devel', 'release': '2.fc20', 'version': '0.100.2',
162     'arch': 'x86_64', 'nvr': 'dbus-glib-devel-0.100.2-2.fc20',
163     },
164    {'build_id': 442366,
165     'name': 'dbus-glib-debuginfo', 'release': '2.fc20', 'version': '0.100.2',
166     'arch': 'x86_64', 'nvr': 'dbus-glib-debuginfo-0.100.2-2.fc20',
167     },
168    {'build_id': 442366,
169     'name': 'dbus-glib-devel', 'release': '2.fc20', 'version': '0.100.2',
170     'arch': 'i686', 'nvr': 'dbus-glib-devel-0.100.2-2.fc20',
171     },
172    {'build_id': 442366,
173     'name': 'dbus-glib-debuginfo', 'release': '2.fc20', 'version': '0.100.2',
174     'arch': 'i686', 'nvr': 'dbus-glib-debuginfo-0.100.2-2.fc20',
175     },
176    {'build_id': 442366,
177     'name': 'dbus-glib', 'version': '0.100.2', 'release': '2.fc20',
178     'arch': 'i686', 'nvr': 'dbus-glib-0.100.2-2.fc20',
179     },
180
181    {'build_id': 715478,
182     'name': 'dbus-glib-debuginfo', 'version': '0.106', 'release': '1.fc23',
183     'arch': 'i686', 'nvr': 'dbus-glib-debuginfo-0.106-1.fc23',
184     },
185    {'build_id': 715478,
186     'name': 'dbus-glib', 'version': '0.106', 'release': '1.fc23',
187     'arch': 'i686', 'nvr': 'dbus-glib-0.106-1.fc23',
188     },
189    {'build_id': 715478,
190     'name': 'dbus-glib-devel', 'version': '0.106', 'release': '1.fc23',
191     'arch': 'i686', 'nvr': 'dbus-glib-devel-0.106-1.fc23',
192     },
193    {'build_id': 715478,
194     'name': 'dbus-glib', 'version': '0.106', 'release': '1.fc23',
195     'arch': 'x86_64', 'nvr': 'dbus-glib-0.106-1.fc23',
196     },
197    {'build_id': 715478,
198     'name': 'dbus-glib-debuginfo', 'version': '0.106', 'release': '1.fc23',
199     'arch': 'x86_64', 'nvr': 'dbus-glib-debuginfo-0.106-1.fc23',
200     },
201    {'build_id': 715478,
202     'name': 'dbus-glib-devel', 'release': '1.fc23', 'version': '0.106',
203     'arch': 'x86_64', 'nvr': 'dbus-glib-devel-0.106-1.fc23',
204     },
205
206    # RPMs of build nss-util-3.12.6-1.fc14
207    {'build_id': 160295,
208     'name': 'nss-util', 'release': '1.fc14', 'version': '3.12.6',
209     'arch': 'x86_64', 'nvr': 'nss-util-3.12.6-1.fc14',
210     },
211    {'build_id': 160295,
212     'name': 'nss-util-devel', 'release': '1.fc14', 'version': '3.12.6',
213     'arch': 'x86_64', 'nvr': 'nss-util-devel-3.12.6-1.fc14',
214     },
215    {'build_id': 160295,
216     'name': 'nss-util-debuginfo', 'release': '1.fc14', 'version': '3.12.6',
217     'arch': 'x86_64', 'nvr': 'nss-util-debuginfo-3.12.6-1.fc14',
218     },
219
220    # RPMs of build nss-util-3.24.0-2.0.fc25
221    {'build_id': 767978,
222     'name': 'nss-util-debuginfo', 'release': '2.0.fc25', 'version': '3.24.0',
223     'arch': 'x86_64', 'nvr': 'nss-util-debuginfo-3.24.0-2.0.fc25',
224     },
225    {'build_id': 767978,
226     'name': 'nss-util', 'release': '2.0.fc25', 'version': '3.24.0',
227     'arch': 'x86_64', 'nvr': 'nss-util-3.24.0-2.0.fc25',
228     },
229    {'build_id': 767978,
230     'name': 'nss-util-devel', 'release': '2.0.fc25', 'version': '3.24.0',
231     'arch': 'x86_64', 'nvr': 'nss-util-devel-3.24.0-2.0.fc25',
232     },
233    # RPMs of build vte291-0.39.1-1.fc22
234    {'build_id': 600011,
235     'name': 'vte291', 'version': '0.39.1', 'release': '1.fc22',
236     'arch': 'x86_64', 'nvr': 'vte291-0.39.1-1.fc22',
237    },
238    {'build_id': 600011,
239     'name': 'vte291-devel', 'version': '0.39.1', 'release': '1.fc22',
240     'arch': 'x86_64', 'nvr': 'vte291-0.39.1-1.fc22',
241    },
242    {'build_id': 600011,
243     'name': 'vte291-debuginfo', 'version': '0.39.1', 'release': '1.fc22',
244     'arch': 'x86_64', 'nvr': 'vte291-0.39.1-1.fc22',
245    },
246
247    # RPMs of build vte291-0.39.90-1.fc22
248    {'build_id': 612610,
249     'name': 'vte291', 'version': '0.39.90', 'release': '1.fc22',
250     'arch': 'x86_64', 'nvr': 'vte291-0.39.90-1.fc22',
251    },
252    {'build_id': 612610,
253     'name': 'vte291-devel', 'version': '0.39.90', 'release': '1.fc22',
254     'arch': 'x86_64', 'nvr': 'vte291-0.39.90-1.fc22',
255    },
256    {'build_id': 612610,
257     'name': 'vte291-debuginfo', 'version': '0.39.90', 'release': '1.fc22',
258     'arch': 'x86_64', 'nvr': 'vte291-0.39.90-1.fc22',
259    },
260   ]
261
262# -----------------  End of Koji resource storage ------------------
263
264
265class MockClientSession(object):
266    """Mock koji.ClientSession
267
268    This mock ClientSession aims to avoid touching a real Koji instance to
269    interact with XMLRPC APIs required by fedabipkgdiff.
270
271    For the tests within this module, methods do not necessarily to return
272    complete RPM and build information. So, if you need more additional
273    information, here is the right place to add them.
274    """
275
276    def __init__(self, baseurl):
277        """Initialize a mock ClientSession
278
279        :param str baseurl: the URL to remote kojihub service. As of writing
280        this mock class, `baseurl` is not used at all, just keep it here if
281        it's useful in the future.
282
283        All mock methods have same signature as corresponding kojihub.*
284        methods, and type of parameters may be different and only satify
285        fedabipkgdiff requirement.
286        """
287        self.baseurl = baseurl
288
289    def getPackage(self, name):
290        """Mock kojihub.getPackage
291
292        :param str name: name of package to find and return
293        :return: the found package
294        :rtype: dict
295        """
296        assert isinstance(name, six.string_types)
297
298        def selector(package):
299            return package['name'] == name
300
301        return [p for p in packages if selector(p)][0]
302
303    def getBuild(self, build_id):
304        """Mock kojihub.getBuild
305
306        :param int build_id: ID of build to find and return
307        :return: the found build
308        :rtype: dict
309        """
310        assert isinstance(build_id, int)
311
312        def selector(build):
313            return build['build_id'] == build_id
314
315        return [b for b in builds if selector(b)][0]
316
317    def listBuilds(self, packageID, state=None):
318        """Mock kojihub.listBuilds
319
320        :param int packageID: ID of package whose builds is found and returned
321        :param state: build state. If state is omitted, all builds of a package
322        are returned
323        :type state: int or None
324        """
325        assert isinstance(packageID, int)
326        if state is not None:
327            assert isinstance(state, int)
328
329        def selector(build):
330            selected = build['package_id'] == packageID
331            if state is not None:
332                selected = selected and build['state'] == state
333            return selected
334
335        return filter(selector, builds)
336
337    def getRPM(self, rpminfo):
338        """Mock kojihub.getRPM
339
340        :param dict rpminfo: a mapping containing rpm information, at least,
341        it contains name, version, release, and arch.
342        """
343        assert isinstance(rpminfo, dict)
344
345        def selector(rpm):
346            return rpm['name'] == rpminfo['name'] and \
347                rpm['version'] == rpminfo['version'] and \
348                rpm['release'] == rpminfo['release'] and \
349                rpm['arch'] == rpminfo['arch']
350
351        return [rpm for rpm in rpms if selector(rpm)][0]
352
353    def listRPMs(self, buildID, arches=None):
354        """Mock kojihub.listRPMs
355
356        :param int buildID: ID of build from which to list rpms
357        :param arches: to list rpms built for specific arches. If arches is
358        omitted, rpms of all arches will be listed.
359        :type arches: list, tuple, str, or None
360        :return: list of rpms
361        :rtype: list
362        """
363        assert isinstance(buildID, int)
364        if arches is not None:
365            assert isinstance(arches, (tuple, list, six.string_types))
366
367        if arches is not None and isinstance(arches, six.string_types):
368            arches = [arches]
369
370        def selector(rpm):
371            selected = rpm['build_id'] == buildID
372            if arches is not None:
373                selected = selected and rpm['arch'] in arches
374            return selected
375
376        return [rpm for rpm in rpms if selector(rpm)]
377
378
379@patch('koji.ClientSession', new=MockClientSession)
380@patch('fedabipkgdiff.DEFAULT_KOJI_TOPURL', new=TEST_TOPDIR)
381@patch('fedabipkgdiff.DEFAULT_ABIPKGDIFF', new=ABIPKGDIFF)
382@patch('fedabipkgdiff.get_download_dir', new=get_download_dir)
383def run_fedabipkgdiff():
384    return fedabipkgdiff_mod.main()
385
386
387def do_main():
388    run_fedabipkgdiff()
389
390
391if __name__ == '__main__':
392    do_main()
393