From 15bc282bcc8739cb7a2cac2a7b2b717b6f0b5558 Mon Sep 17 00:00:00 2001
From: Christian Heimes <cheimes@redhat.com>
Date: Fri, 26 Feb 2016 17:18:57 +0100
Subject: [PATCH] Implement total ordering for PKISubsystem and PKIInstance

In Python 3 subclasses no longer implement automatic ordering. To
provide ordering for sort() and custom comparison, __eq__ and __lt__ are
required.

https://fedorahosted.org/pki/ticket/2216
---
 base/server/python/pki/server/__init__.py | 45 +++++++++++++++++++++++
 tests/python/server/test_server.py        | 61 +++++++++++++++++++++++++++++++
 2 files changed, 106 insertions(+)
 create mode 100644 tests/python/server/test_server.py

diff --git a/base/server/python/pki/server/__init__.py b/base/server/python/pki/server/__init__.py
index e308a5033aa8ecdf3f89e66233d5dd3b29a39308..2364131c616496869bebd581e7434c048a5bb6f9 100644
--- a/base/server/python/pki/server/__init__.py
+++ b/base/server/python/pki/server/__init__.py
@@ -20,6 +20,7 @@
 
 from __future__ import absolute_import
 from lxml import etree
+import functools
 import getpass
 import grp
 import io
@@ -59,6 +60,7 @@ class PKIServer(object):
         return instances
 
 
+@functools.total_ordering
 class PKISubsystem(object):
 
     def __init__(self, instance, subsystem_name):
@@ -92,6 +94,29 @@ class PKISubsystem(object):
         # custom subsystem location
         self.doc_base = os.path.join(self.base_dir, 'webapps', self.name)
 
+    def __eq__(self, other):
+        if not isinstance(other, PKISubsystem):
+            return NotImplemented
+        return (self.name == other.name and
+                self.instance == other.instance and
+                self.type == other.type)
+
+    def __ne__(self, other):
+        if not isinstance(other, PKISubsystem):
+            return NotImplemented
+        return not self.__eq__(other)
+
+    def __lt__(self, other):
+        if not isinstance(other, PKISubsystem):
+            return NotImplemented
+        self_type = self.type if self.type is not None else ''
+        other_type = other.type if other.type is not None else ''
+        return (self.name < other.name or
+                self.instance < other.instance or
+                self_type < other_type)
+
+    __hash__ = None
+
     def load(self):
         self.config.clear()
 
@@ -335,6 +360,7 @@ class PKISubsystem(object):
         return str(self.instance) + '/' + self.name
 
 
+@functools.total_ordering
 class PKIInstance(object):
 
     def __init__(self, name, instanceType=10):  # nopep8
@@ -371,6 +397,25 @@ class PKIInstance(object):
 
         self.subsystems = []
 
+    def __eq__(self, other):
+        if not isinstance(other, PKIInstance):
+            return NotImplemented
+        return (self.name == other.name and
+                self.type == other.type)
+
+    def __ne__(self, other):
+        if not isinstance(other, PKIInstance):
+            return NotImplemented
+        return not self.__eq__(other)
+
+    def __lt__(self, other):
+        if not isinstance(other, PKIInstance):
+            return NotImplemented
+        return (self.name < other.name or
+                self.type < other.type)
+
+    __hash__ = None
+
     def is_valid(self):
         return os.path.exists(self.conf_dir)
 
diff --git a/tests/python/server/test_server.py b/tests/python/server/test_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..646e6826f369a02849831e37a9f1084451bae8ac
--- /dev/null
+++ b/tests/python/server/test_server.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+# Authors:
+#     Christian Heimes <cheimes@redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Copyright (C) 2016 Red Hat, Inc.
+# All rights reserved.
+#
+
+import unittest
+
+from pki.server import PKISubsystem, PKIInstance
+
+
+class PKIServerTests(unittest.TestCase):
+    def test_instance_ordering(self):
+        ca = PKIInstance('ca')
+        ca9 = PKIInstance('ca', 9)
+        kra = PKIInstance('kra')
+        instances = [kra, ca, ca9]
+        self.assertEqual(sorted(instances), [ca9, ca, kra])
+        self.assertEqual(sorted(instances, reverse=True), [kra, ca, ca9])
+        self.assertTrue(ca == ca)
+        self.assertFalse(ca != ca)
+        self.assertFalse(ca == ca9)
+        self.assertTrue(ca != ca9)
+        with self.assertRaises(TypeError):
+            hash(ca)
+
+    def test_subsystem(self):
+        ca = PKIInstance('ca')
+        kra = PKIInstance('kra')
+        casub = PKISubsystem(ca, 'ca sub')
+        krasub = PKISubsystem(kra, 'kra sub')
+        subs = [casub, krasub]
+        self.assertEqual(sorted(subs), [casub, krasub])
+        self.assertEqual(sorted(subs, reverse=True), [krasub, casub])
+        self.assertTrue(casub == casub)
+        self.assertFalse(casub != casub)
+        self.assertFalse(casub == krasub)
+        self.assertTrue(casub != krasub)
+        self.assertFalse(ca == casub)
+        self.assertTrue(ca != casub)
+        with self.assertRaises(TypeError):
+            hash(casub)
+
+
+if __name__ == '__main__':
+    unittest.main()
-- 
2.5.0

