pykolab/xml tests/unit

Thomas Brüderli bruederli at kolabsys.com
Wed Mar 25 19:03:36 CET 2015


 pykolab/xml/utils.py        |   48 +++++++++-
 tests/unit/test-017-diff.py |  199 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 243 insertions(+), 4 deletions(-)

New commits:
commit aa3b0f23dccfb8c8e52fc6f27fb7b99bad42a0b5
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Tue Mar 24 21:08:12 2015 +0100

    Improve object diff computation: ignore order of attribute lists (e.g. attachments, attendees)

diff --git a/pykolab/xml/utils.py b/pykolab/xml/utils.py
index c92c52f..261e33c 100644
--- a/pykolab/xml/utils.py
+++ b/pykolab/xml/utils.py
@@ -253,16 +253,15 @@ def compute_diff(a, b, reduced=False):
                 aa = [aa]
             if not isinstance(bb, list):
                 bb = [bb]
+
+            (aa, bb) = order_proplists(aa, bb)
             index = 0
             length = max(len(aa), len(bb))
             while index < length:
                 aai = aa[index] if index < len(aa) else None
                 bbi = bb[index] if index < len(bb) else None
                 if not compare_values(aai, bbi):
-                    if reduced:
-                        (old, new) = reduce_properties(aai, bbi)
-                    else:
-                        (old, new) = (aai, bbi)
+                    (old, new) = reduce_properties(aai, bbi) if reduced else (aai, bbi)
                     diff.append(OrderedDict([('property', prop), ('index', index), ('old', old), ('new', new)]))
                 index += 1
 
@@ -277,6 +276,47 @@ def compute_diff(a, b, reduced=False):
     return diff
 
 
+def order_proplists(a, b):
+    """
+        Orders two lists so that equal entries have the same position
+    """
+    # nothing to be done here
+    if len(a) == 0 and len(b) == 0:
+        return (a, b)
+
+    base = a
+    comp = b
+    flip = False
+
+    if len(a) > len(b):
+        flip = True
+        base = b
+        comp = a
+
+    indices = []
+    top = len(comp) + 1
+    for bb in comp:
+        index = None
+
+        # find a matching entry in base
+        for j, aa in enumerate(base):
+            if compare_values(aa, bb):
+                index = j
+                break
+
+        # move non-matching items to the end of the list
+        if index is None:
+            index = top
+            top += 1
+
+        indices.append(index)
+
+    # do sort by indices
+    indices, comp = zip(*sorted(zip(indices, comp), key=lambda x: x[0]))
+
+    return (comp, base) if flip else (base, comp)
+
+
 def compare_values(aa, bb):
     ignore_keys = ['rsvp']
     if not aa.__class__ == bb.__class__:
diff --git a/tests/unit/test-017-diff.py b/tests/unit/test-017-diff.py
new file mode 100644
index 0000000..6c0a585
--- /dev/null
+++ b/tests/unit/test-017-diff.py
@@ -0,0 +1,199 @@
+import unittest
+
+from pykolab.xml import Todo
+from pykolab.xml.utils import compute_diff
+from pykolab.xml.utils import order_proplists
+
+xml_todo_01 = """<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
+<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">
+  <vcalendar>
+    <properties>
+      <prodid>
+        <text>Roundcube-libkolab-1.1 Libkolabxml-1.1</text>
+      </prodid>
+      <version>
+        <text>2.0</text>
+      </version>
+      <x-kolab-version>
+        <text>3.1.0</text>
+      </x-kolab-version>
+    </properties>
+    <components>
+      <vtodo>
+        <properties>
+          <uid>
+            <text>E49000485B9EE3299A8870AD6A3E75E5-A4BF5BBB9FEAA271</text>
+          </uid>
+          <created>
+            <date-time>2015-03-25T10:32:18Z</date-time>
+          </created>
+          <dtstamp>
+            <date-time>2015-03-25T16:09:11Z</date-time>
+          </dtstamp>
+          <sequence>
+            <integer>0</integer>
+          </sequence>
+          <summary>
+            <text>Old attachments</text>
+          </summary>
+          <attach>
+            <parameters>
+              <fmttype>
+                <text>image/png</text>
+              </fmttype>
+              <x-label>
+                <text>silhouette.png</text>
+              </x-label>
+            </parameters>
+            <uri>cid:silhouette.1427297477.7514.png</uri>
+          </attach>
+          <attach>
+            <parameters>
+              <fmttype>
+                <text>text/plain</text>
+              </fmttype>
+              <x-label>
+                <text>notes.txt</text>
+              </x-label>
+            </parameters>
+            <uri>cid:notes.1427298885.9012.txt</uri>
+          </attach>
+          <attach>
+            <parameters>
+              <fmttype>
+                <text>image/gif</text>
+              </fmttype>
+              <x-label>
+                <text>landspeeder-lego-icon.gif</text>
+              </x-label>
+            </parameters>
+            <uri>cid:landspeeder-lego-icon.1427299751.8542.gif</uri>
+          </attach>
+        </properties>
+      </vtodo>
+    </components>
+  </vcalendar>
+</icalendar>
+"""
+
+xml_todo_02 = """<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
+<icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0">
+  <vcalendar>
+    <properties>
+      <prodid>
+        <text>Roundcube-libkolab-1.1 Libkolabxml-1.1</text>
+      </prodid>
+      <version>
+        <text>2.0</text>
+      </version>
+      <x-kolab-version>
+        <text>3.1.0</text>
+      </x-kolab-version>
+    </properties>
+    <components>
+      <vtodo>
+        <properties>
+          <uid>
+            <text>E49000485B9EE3299A8870AD6A3E75E5-A4BF5BBB9FEAA271</text>
+          </uid>
+          <created>
+            <date-time>2015-03-25T10:32:18Z</date-time>
+          </created>
+          <dtstamp>
+            <date-time>2015-03-25T16:09:53Z</date-time>
+          </dtstamp>
+          <sequence>
+            <integer>1</integer>
+          </sequence>
+          <summary>
+            <text>New attachments</text>
+          </summary>
+          <description>
+            <text>Removed attachment</text>
+          </description>
+          <attach>
+            <parameters>
+              <fmttype>
+                <text>text/plain</text>
+              </fmttype>
+              <x-label>
+                <text>notes.txt</text>
+              </x-label>
+            </parameters>
+            <uri>cid:notes.1427298885.9012.txt</uri>
+          </attach>
+          <attach>
+            <parameters>
+              <fmttype>
+                <text>image/gif</text>
+              </fmttype>
+              <x-label>
+                <text>landspeeder-lego-icon.gif</text>
+              </x-label>
+            </parameters>
+            <uri>cid:landspeeder-lego-icon.1427299751.8542.gif</uri>
+          </attach>
+        </properties>
+      </vtodo>
+    </components>
+  </vcalendar>
+</icalendar>
+"""
+
+class TestComputeDiff(unittest.TestCase):
+    
+    def test_000_order_proplists(self):
+        one = {
+            "uri": "cid:one",
+            "label": "one.txt"
+        }
+        two = {
+            "uri": "cid:two",
+            "label": "two.png"
+        }
+        three = {
+            "foo": "three"
+        }
+        four = {
+            "foo": "four"
+        }
+
+        (aa, bb) = order_proplists([one, two, four], [three, two, one, four])
+        self.assertEqual(len(aa), 3)
+        self.assertEqual(len(bb), 4)
+        self.assertEqual(aa[0], bb[0])
+        self.assertEqual(aa[1], bb[1])
+
+        (aa, bb) = order_proplists([four, three, one, two], [two, one])
+        self.assertEqual(len(aa), 4)
+        self.assertEqual(len(bb), 2)
+        self.assertEqual(aa[0], bb[0])
+        self.assertEqual(aa[1], bb[1])
+
+
+    def test_001_attachments(self):
+        old = Todo(from_string=xml_todo_01)
+        new = Todo(from_string=xml_todo_02)
+        diff = compute_diff(old.to_dict(), new.to_dict())
+
+        self.assertEqual(len(diff), 5)
+        self.assertEqual(diff[0]['property'], 'sequence')
+        self.assertEqual(diff[0]['old'], 0)
+        self.assertEqual(diff[0]['new'], 1)
+
+        self.assertEqual(diff[1]['property'], 'description')
+        self.assertEqual(diff[1]['old'], '')
+
+        self.assertEqual(diff[2]['property'], 'summary')
+        self.assertEqual(diff[2]['old'], 'Old attachments')
+        self.assertEqual(diff[2]['new'], 'New attachments')
+
+        self.assertEqual(diff[3]['property'], 'attach')
+        self.assertEqual(diff[3]['new'], None)
+        self.assertEqual(diff[3]['old']['uri'], "cid:silhouette.1427297477.7514.png")
+
+        self.assertEqual(diff[4]['property'], 'lastmodified-date')
+
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file




More information about the commits mailing list