Bug 5560: Fix bits ordering issue in BGP flowspec tests
[integration/test.git] / csit / libraries / norm_json.py
index d46d6c78ffe909b4eacd6a5bb553952fba594d7e..8a84fc27d3df5ca99d12a635c700c6f64f12f988 100644 (file)
@@ -18,7 +18,7 @@ __license__ = "Eclipse Public License v1.0"
 __email__ = "vrpolak@cisco.com"
 
 
-# Internal details.
+# Internal details; look down below for Robot Keywords.
 
 
 class _Hsfl(list):
@@ -93,23 +93,12 @@ class _Decoder(_json.JSONDecoder):
         self.scan_once = _json.scanner.py_make_scanner(self)
 
 
-# Robot Keywords.
+# Robot Keywords; look above for internal details.
 
 
 def loads_sorted(text, strict=False):
-    """
-    Return Python object with sorted arrays and dictionary keys.
-
-    If strict is true, raise exception on parse error.
-    If strict is not true, return string with error message.
-    """
-    try:
-        object_decoded = _json.loads(text, cls=_Decoder, object_hook=_Hsfod)
-    except ValueError as err:
-        if strict:
-            raise err
-        else:
-            return str(err) + '\n' + text
+    """Return Python object with sorted arrays and dictionary keys."""
+    object_decoded = _json.loads(text, cls=_Decoder, object_hook=_Hsfod)
     return object_decoded
 
 
@@ -125,8 +114,60 @@ def dumps_indented(obj, indent=1):
     return pretty_json + '\n'  # to avoid diff "no newline" warning line
 
 
-def normalize_json_text(text, strict=False, indent=1):  # pylint likes lowercase
-    """Return sorted indented JSON string, or an error message string."""
-    object_decoded = loads_sorted(text, strict=strict)
+def sort_bits(obj, keys_with_bits=[]):
+    """
+    Rearrange string values of list bits names in alphabetical order.
+
+    This function looks at dict items with known keys.
+    If the value is string, space-separated names are sorted.
+    This function is recursive over dicts and lists.
+    Current implementation performs re-arranging in-place (to save memory),
+    so it is not required to store the return value.
+
+    The intended usage is for composite objects which contain
+    OrderedDict elements. The implementation makes sure that ordering
+    (dictated by keys) is preserved. Support for generic dicts is an added value.
+
+    Sadly, dict (at least in Python 2.7) does not have __updatevalue__(key) method
+    which would guarantee iteritems() is not affected when value is updated.
+    Current "obj[key] = value" implementation is safe for dict and OrderedDict,
+    but it may be not safe for other subclasses of dict.
+
+    TODO: Should this docstring include links to support dict and OrderedDict safety?
+    """
+    if isinstance(obj, dict):
+        for key, value in obj.iteritems():
+            # Unicode is not str and vice versa, isinstance has to check for both.
+            # Luckily, "in" recognizes equivalent strings in different encodings.
+            # Type "bytes" is added for Python 3 compatibility.
+            if key in keys_with_bits and isinstance(value, (unicode, str, bytes)):
+                obj[key] = " ".join(sorted(value.split(" ")))
+            else:
+                sort_bits(value, keys_with_bits)
+    # A string is not a list, so there is no risk of recursion over characters.
+    elif isinstance(obj, list):
+        for item in obj:
+            sort_bits(item, keys_with_bits)
+    return obj
+
+
+def normalize_json_text(text, strict=False, indent=1, keys_with_bits=[]):
+    """
+    Attempt to return sorted indented JSON string.
+
+    If parse error happens:
+    If strict is true, raise the exception.
+    If strict is not true, return original text with error message.
+    If keys_with_bits is non-empty, run sort_bits on intermediate Python object.
+    """
+    try:
+        object_decoded = loads_sorted(text)
+    except ValueError as err:
+        if strict:
+            raise err
+        else:
+            return str(err) + '\n' + text
+    if keys_with_bits:
+        sort_bits(object_decoded, keys_with_bits)
     pretty_json = dumps_indented(object_decoded, indent=indent)
     return pretty_json