From e1629acd3074903552044ec3089e1c131dc62c83 Mon Sep 17 00:00:00 2001
From: Chenxiong Qi <cqi@redhat.com>
Date: Tue, 7 Jun 2016 21:55:12 +0800
Subject: [PATCH] selecting latest build in a stable way

Generally, koji provides event for each creation of resources, for
example build, rpm, task. So, selecting latest build by attribute nvr
is not a stable way, instead, creation_event_id is the right choice.

	* configure.ac: add new dependency.
	* tests/runtestfedabipkgdiff.py.in: (builds): add new builds
	for running tests to test selecting latest build from a package.
	(packages): add new package gnutls. Update existent packages by
	adding new attribute creation_event_id.
	(GetPackageLatestBuildTest.{test_get_latest_one,
	test_cannot_find_a_latest_build_with_invalid_distro}): use new
	builds of package gnutls to run tests.
	* tools/fedabipkgdiff: (Brew.get_package_latest_build): order
	builds by creation_event_id.
	(cmp_nvr): new method used to compare nvrs by sorted.

Signed-off-by: Chenxiong Qi <cqi@redhat.com>
---
 configure.ac                     |  2 +-
 tests/runtestfedabipkgdiff.py.in | 44 +++++++++++++++++++++++++++++++---------
 tools/fedabipkgdiff              | 23 +++++++++++++++++++++
 3 files changed, 58 insertions(+), 11 deletions(-)

diff --git a/configure.ac b/configure.ac
index 26172da..07b5583 100644
--- a/configure.ac
+++ b/configure.ac
@@ -291,7 +291,7 @@ if test x$CHECK_DEPS_FOR_FEDABIPKGDIFF = xyes; then
 
   REQUIRED_PYTHON_MODULES_FOR_FEDABIPKGDIFF="\
    argparse logging os re subprocess sys itertools urlparse \
-   unittest xdg koji mock StringIO"
+   unittest xdg koji mock StringIO rpm"
 
   if test -x$ENABLE_FEDABIPKGDIFF != xno; then
     AX_CHECK_PYTHON_MODULES([$REQUIRED_PYTHON_MODULES_FOR_FEDABIPKGDIFF],
diff --git a/tests/runtestfedabipkgdiff.py.in b/tests/runtestfedabipkgdiff.py.in
index 4a1541c..22287ac 100755
--- a/tests/runtestfedabipkgdiff.py.in
+++ b/tests/runtestfedabipkgdiff.py.in
@@ -88,34 +88,58 @@ BUILT_ABIPKGDIFF = '@top_builddir@/tools/abipkgdiff'
 # database, and methods of MockClientSession knows well how to read them.
 
 packages = [
+    {'id': 286, 'name': 'gnutls'},
     {'id': 612, 'name': 'dbus-glib'},
     ]
 
 builds = [
     {'build_id': 428835, 'nvr': 'dbus-glib-0.100-4.fc20',
      'name': 'dbus-glib', 'release': '4.fc20', 'version': '0.100',
-     'package_id': 612, 'package_name': 'dbus-glib', 'state': 1,
+     'package_id': 612, 'package_name': 'dbus-glib',
+     'state': 1, 'creation_event_id': 6196915,
      },
     {'build_id': 430720, 'nvr': 'dbus-glib-0.100.2-1.fc20',
      'name': 'dbus-glib', 'release': '1.fc20', 'version': '0.100.2',
-     'package_id': 612, 'package_name': 'dbus-glib', 'state': 1,
+     'package_id': 612, 'package_name': 'dbus-glib',
+     'state': 1, 'creation_event_id': 6224782,
      },
     {'build_id': 442366, 'nvr': 'dbus-glib-0.100.2-2.fc20',
      'name': 'dbus-glib', 'release': '2.fc20', 'version': '0.100.2',
-     'package_id': 612, 'package_name': 'dbus-glib', 'state': 1,
+     'package_id': 612, 'package_name': 'dbus-glib',
+     'state': 1, 'creation_event_id': 6389090,
      },
     {'build_id': 715478, 'nvr': 'dbus-glib-0.106-1.fc23',
      'name': 'dbus-glib', 'release': '1.fc23', 'version': '0.106',
-     'package_id': 612, 'package_name': 'dbus-glib', 'state': 1,
+     'package_id': 612, 'package_name': 'dbus-glib',
+     'state': 1, 'creation_event_id': 14480790,
      },
     {'build_id': 648058, 'nvr': 'dbus-glib-0.104-3.fc23',
      'name': 'dbus-glib', 'release': '3.fc23', 'version': '0.104',
-     'package_id': 612, 'package_name': 'dbus-glib', 'state': 1,
+     'package_id': 612, 'package_name': 'dbus-glib',
+     'state': 1, 'creation_event_id': 11199039,
      },
     {'build_id': 613769, 'nvr': 'dbus-glib-0.104-2.fc23',
      'name': 'dbus-glib', 'release': '2.fc23', 'version': '0.104',
-     'package_id': 612, 'package_name': 'dbus-glib', 'state': 1,
-     }
+     'package_id': 612, 'package_name': 'dbus-glib',
+     'state': 1, 'creation_event_id': 9840041,
+     },
+
+    # builds of package gnutls
+    {'build_id': 767306, 'nvr': 'gnutls-3.4.12-1.fc23',
+     'name': 'gnutls', 'release': '1.fc23', 'version': '3.4.12',
+     'package_id': 286, 'package_name': 'gnutls',
+     'state': 1, 'creation_event_id': 16323597,
+     },
+    {'build_id': 770965, 'nvr': 'gnutls-3.4.13-1.fc23',
+     'name': 'gnutls', 'release': '1.fc23', 'version': '3.4.13',
+     'package_id': 286, 'package_name': 'gnutls',
+     'state': 1, 'creation_event_id': 16584212,
+     },
+    {'build_id': 649701, 'nvr': 'gnutls-3.4.2-1.fc23',
+     'name': 'gnutls', 'release': '1.fc23', 'version': '3.4.2',
+     'package_id': 286, 'package_name': 'gnutls',
+     'state': 1, 'creation_event_id': 11212832,
+     },
     ]
 
 rpms = [
@@ -738,8 +762,8 @@ class GetPackageLatestBuildTest(unittest.TestCase):
     def test_get_latest_one(self):
         """Ensure to get latest build of a package"""
         session = fedabipkgdiff_mod.get_session()
-        build = session.get_package_latest_build('dbus-glib', 'fc23')
-        self.assertEquals('dbus-glib-0.106-1.fc23', build['nvr'])
+        build = session.get_package_latest_build('gnutls', 'fc23')
+        self.assertEquals('gnutls-3.4.13-1.fc23', build['nvr'])
 
     @patch('fedabipkgdiff.global_config', new=MockGlobalConfig)
     @patch('koji.ClientSession', new=MockClientSession)
@@ -751,7 +775,7 @@ class GetPackageLatestBuildTest(unittest.TestCase):
         session = fedabipkgdiff_mod.get_session()
         self.assertRaises(fedabipkgdiff_mod.NoCompleteBuilds,
                           session.get_package_latest_build,
-                          'dbus-glib', 'xxxx')
+                          'gnutls', 'xxxx')
 
 
 class DownloadRPMTest(unittest.TestCase):
diff --git a/tools/fedabipkgdiff b/tools/fedabipkgdiff
index a6abdf1..83a1a6b 100755
--- a/tools/fedabipkgdiff
+++ b/tools/fedabipkgdiff
@@ -34,6 +34,7 @@ from itertools import groupby
 
 import xdg.BaseDirectory
 
+import rpm
 import koji
 
 # @file
@@ -152,6 +153,26 @@ def is_distro_valid(distro):
     return re.match(r'^(fc|el)\d{1,2}$', distro) is not None
 
 
+def cmp_nvr(left, right):
+    """Compare function for sorting a sequence of NVRs
+
+    This is the compare function used in sorted function to sort builds so that
+    fedabipkgdiff is able to select the latest build. Return value follows the
+    rules described in the part of paramter cmp of sorted documentation.
+
+    :param str left: left nvr to compare.
+    :param str right: right nvr to compare.
+    :return: -1, 0, or 1 that represents left is considered smaller than,
+    equal to, or larger than the right individually.
+    :rtype: int
+    """
+    left_nvr = koji.parse_NVR(left)
+    right_nvr = koji.parse_NVR(right)
+    return rpm.labelCompare(
+        (left_nvr['epoch'], left_nvr['version'], left_nvr['release']),
+        (right_nvr['epoch'], right_nvr['version'], right_nvr['release']))
+
+
 def log_call(func):
     """A decorator that logs a method invocation
 
@@ -433,8 +454,10 @@ class Brew(object):
         if order_by is not None:
             # FIXME: is it possible to sort builds by using opts parameter of
             # listBuilds
+            cmp_func = cmp_nvr if order_by == 'nvr' else None
             builds = sorted(builds,
                             key=lambda item: item[order_by],
+                            cmp=cmp_func,
                             reverse=reverse)
         if topone:
             builds = builds[0:1]
-- 
2.5.5

