]> git.cworth.org Git - apitrace/blobdiff - scripts/jsondiff.py
Use skiplist-based FastCallSet within trace::CallSet
[apitrace] / scripts / jsondiff.py
index 063cd6160cabd4e36788497ea0d5bd23069a70de..470aeda059b8f2723ddf6796394507f8e347ee2d 100755 (executable)
@@ -26,6 +26,8 @@
 
 
 import json
+import optparse
+import re
 import sys
 
 
@@ -42,19 +44,19 @@ class Visitor:
 
     def visit(self, node, *args, **kwargs):
         if isinstance(node, dict):
-            return self.visit_object(node, *args, **kwargs)
+            return self.visitObject(node, *args, **kwargs)
         elif isinstance(node, list):
-            return self.visit_array(node, *args, **kwargs)
+            return self.visitArray(node, *args, **kwargs)
         else:
-            return self.visit_value(node, *args, **kwargs)
+            return self.visitValue(node, *args, **kwargs)
 
-    def visit_object(self, node, *args, **kwargs):
+    def visitObject(self, node, *args, **kwargs):
         pass
 
-    def visit_array(self, node, *args, **kwargs):
+    def visitArray(self, node, *args, **kwargs):
         pass
 
-    def visit_value(self, node, *args, **kwargs):
+    def visitValue(self, node, *args, **kwargs):
         pass
 
 
@@ -73,7 +75,7 @@ class Dumper(Visitor):
     def _newline(self):
         self._write('\n')
 
-    def visit_object(self, node):
+    def visitObject(self, node):
         self.enter_object()
 
         members = node.keys()
@@ -107,7 +109,7 @@ class Dumper(Visitor):
         if self.level <= 0:
             self._newline()
 
-    def visit_array(self, node):
+    def visitArray(self, node):
         self.enter_array()
         for i in range(len(node)):
             value = node[i]
@@ -128,17 +130,18 @@ class Dumper(Visitor):
         self._indent()
         self._write(']')
 
-    def visit_value(self, node):
+    def visitValue(self, node):
         self._write(json.dumps(node))
 
 
 
 class Comparer(Visitor):
 
-    def __init__(self, ignore_added = False):
+    def __init__(self, ignore_added = False, tolerance = 2.0 ** -24):
         self.ignore_added = ignore_added
+        self.tolerance = tolerance
 
-    def visit_object(self, a, b):
+    def visitObject(self, a, b):
         if not isinstance(b, dict):
             return False
         if len(a) != len(b) and not self.ignore_added:
@@ -159,7 +162,7 @@ class Comparer(Visitor):
                 return False
         return True
 
-    def visit_array(self, a, b):
+    def visitArray(self, a, b):
         if not isinstance(b, list):
             return False
         if len(a) != len(b):
@@ -169,9 +172,14 @@ class Comparer(Visitor):
                 return False
         return True
 
-    def visit_value(self, a, b):
-        return a == b
-
+    def visitValue(self, a, b):
+        if isinstance(a, float) or isinstance(b, float):
+            if a == 0:
+                return abs(b) < self.tolerance
+            else:
+                return abs((b - a)/a) < self.tolerance
+        else:
+            return a == b
 
 
 class Differ(Visitor):
@@ -185,7 +193,7 @@ class Differ(Visitor):
             return
         Visitor.visit(self, a, b)
 
-    def visit_object(self, a, b):
+    def visitObject(self, a, b):
         if not isinstance(b, dict):
             self.replace(a, b)
         else:
@@ -207,7 +215,7 @@ class Differ(Visitor):
 
             self.dumper.leave_object()
 
-    def visit_array(self, a, b):
+    def visitArray(self, a, b):
         if not isinstance(b, list):
             self.replace(a, b)
         else:
@@ -233,7 +241,7 @@ class Differ(Visitor):
 
             self.dumper.leave_array()
 
-    def visit_value(self, a, b):
+    def visitValue(self, a, b):
         if a != b:
             self.replace(a, b)
 
@@ -243,17 +251,66 @@ class Differ(Visitor):
         self.dumper.visit(b)
 
 
-def load(stream, strip = True):
-    if strip:
+#
+# Unfortunately JSON standard does not include comments, but this is a quite
+# useful feature to have on regressions tests
+#
+
+_token_res = [
+    r'//[^\r\n]*', # comment
+    r'"[^"\\]*(\\.[^"\\]*)*"', # string
+]
+
+_tokens_re = re.compile(r'|'.join(['(' + token_re + ')' for token_re in _token_res]), re.DOTALL)
+
+
+def _strip_comment(mo):
+    if mo.group(1):
+        return ''
+    else:
+        return mo.group(0)
+
+
+def _strip_comments(data):
+    '''Strip (non-standard) JSON comments.'''
+    return _tokens_re.sub(_strip_comment, data)
+
+
+assert _strip_comments('''// a comment
+"// a comment in a string
+"''') == '''
+"// a comment in a string
+"'''
+
+
+def load(stream, strip_images = True, strip_comments = True):
+    if strip_images:
         object_hook = strip_object_hook
     else:
         object_hook = None
-    return json.load(stream, strict=False, object_hook = object_hook)
+    if strip_comments:
+        data = stream.read()
+        data = _strip_comments(data)
+        return json.loads(data, strict=False, object_hook = object_hook)
+    else:
+        return json.load(stream, strict=False, object_hook = object_hook)
 
 
 def main():
-    a = load(open(sys.argv[1], 'rt'))
-    b = load(open(sys.argv[2], 'rt'))
+    optparser = optparse.OptionParser(
+        usage="\n\t%prog [options] <ref_json> <src_json>")
+    optparser.add_option(
+        '--keep-images',
+        action="store_false", dest="strip_images", default=True,
+        help="compare images")
+
+    (options, args) = optparser.parse_args(sys.argv[1:])
+
+    if len(args) != 2:
+        optparser.error('incorrect number of arguments')
+
+    a = load(open(sys.argv[1], 'rt'), options.strip_images)
+    b = load(open(sys.argv[2], 'rt'), options.strip_images)
 
     if False:
         dumper = Dumper()