Path: blob/21.2-virgl/src/gallium/tools/trace/diff_state.py
4561 views
#!/usr/bin/env python31##########################################################################2#3# Copyright 2011 Jose Fonseca4# All Rights Reserved.5#6# Permission is hereby granted, free of charge, to any person obtaining a copy7# of this software and associated documentation files (the "Software"), to deal8# in the Software without restriction, including without limitation the rights9# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell10# copies of the Software, and to permit persons to whom the Software is11# furnished to do so, subject to the following conditions:12#13# The above copyright notice and this permission notice shall be included in14# all copies or substantial portions of the Software.15#16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR17# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,18# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE19# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER20# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,21# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN22# THE SOFTWARE.23#24##########################################################################/252627import json28import argparse29import re30import difflib31import sys323334def strip_object_hook(obj):35if '__class__' in obj:36return None37for name in obj.keys():38if name.startswith('__') and name.endswith('__'):39del obj[name]40return obj414243class Visitor:4445def visit(self, node, *args, **kwargs):46if isinstance(node, dict):47return self.visitObject(node, *args, **kwargs)48elif isinstance(node, list):49return self.visitArray(node, *args, **kwargs)50else:51return self.visitValue(node, *args, **kwargs)5253def visitObject(self, node, *args, **kwargs):54pass5556def visitArray(self, node, *args, **kwargs):57pass5859def visitValue(self, node, *args, **kwargs):60pass616263class Dumper(Visitor):6465def __init__(self, stream = sys.stdout):66self.stream = stream67self.level = 06869def _write(self, s):70self.stream.write(s)7172def _indent(self):73self._write(' '*self.level)7475def _newline(self):76self._write('\n')7778def visitObject(self, node):79self.enter_object()8081members = sorted(node)82for i in range(len(members)):83name = members[i]84value = node[name]85self.enter_member(name)86self.visit(value)87self.leave_member(i == len(members) - 1)88self.leave_object()8990def enter_object(self):91self._write('{')92self._newline()93self.level += 19495def enter_member(self, name):96self._indent()97self._write('%s: ' % name)9899def leave_member(self, last):100if not last:101self._write(',')102self._newline()103104def leave_object(self):105self.level -= 1106self._indent()107self._write('}')108if self.level <= 0:109self._newline()110111def visitArray(self, node):112self.enter_array()113for i in range(len(node)):114value = node[i]115self._indent()116self.visit(value)117if i != len(node) - 1:118self._write(',')119self._newline()120self.leave_array()121122def enter_array(self):123self._write('[')124self._newline()125self.level += 1126127def leave_array(self):128self.level -= 1129self._indent()130self._write(']')131132def visitValue(self, node):133self._write(json.dumps(node, allow_nan=True))134135136137class Comparer(Visitor):138139def __init__(self, ignore_added = False, tolerance = 2.0 ** -24):140self.ignore_added = ignore_added141self.tolerance = tolerance142143def visitObject(self, a, b):144if not isinstance(b, dict):145return False146if len(a) != len(b) and not self.ignore_added:147return False148ak = sorted(a)149bk = sorted(b)150if ak != bk and not self.ignore_added:151return False152for k in ak:153ae = a[k]154try:155be = b[k]156except KeyError:157return False158if not self.visit(ae, be):159return False160return True161162def visitArray(self, a, b):163if not isinstance(b, list):164return False165if len(a) != len(b):166return False167for ae, be in zip(a, b):168if not self.visit(ae, be):169return False170return True171172def visitValue(self, a, b):173if isinstance(a, float) and isinstance(b, float):174if a == 0:175return abs(b) < self.tolerance176else:177return abs((b - a) / a) < self.tolerance178else:179return a == b180181182class Differ(Visitor):183184def __init__(self, stream = sys.stdout, ignore_added = False):185self.dumper = Dumper(stream)186self.comparer = Comparer(ignore_added = ignore_added)187188def visit(self, a, b):189if self.comparer.visit(a, b):190return191Visitor.visit(self, a, b)192193def visitObject(self, a, b):194if not isinstance(b, dict):195self.replace(a, b)196else:197self.dumper.enter_object()198names = set(a.keys())199if not self.comparer.ignore_added:200names.update(b.keys())201names = list(names)202names.sort()203204for i in range(len(names)):205name = names[i]206ae = a.get(name, None)207be = b.get(name, None)208if not self.comparer.visit(ae, be):209self.dumper.enter_member(name)210self.visit(ae, be)211self.dumper.leave_member(i == len(names) - 1)212213self.dumper.leave_object()214215def visitArray(self, a, b):216if not isinstance(b, list):217self.replace(a, b)218else:219self.dumper.enter_array()220max_len = max(len(a), len(b))221for i in range(max_len):222try:223ae = a[i]224except IndexError:225ae = None226try:227be = b[i]228except IndexError:229be = None230self.dumper._indent()231if self.comparer.visit(ae, be):232self.dumper.visit(ae)233else:234self.visit(ae, be)235if i != max_len - 1:236self.dumper._write(',')237self.dumper._newline()238239self.dumper.leave_array()240241def visitValue(self, a, b):242if a != b:243self.replace(a, b)244245def replace(self, a, b):246if isinstance(a, str) and isinstance(b, str):247if '\n' in a or '\n' in b:248a = a.splitlines()249b = b.splitlines()250differ = difflib.Differ()251result = differ.compare(a, b)252self.dumper.level += 1253for entry in result:254self.dumper._newline()255self.dumper._indent()256tag = entry[:2]257text = entry[2:]258if tag == '? ':259tag = ' '260prefix = ' '261text = text.rstrip()262suffix = ''263else:264prefix = '"'265suffix = '\\n"'266line = tag + prefix + text + suffix267self.dumper._write(line)268self.dumper.level -= 1269return270self.dumper.visit(a)271self.dumper._write(' -> ')272self.dumper.visit(b)273274def isMultilineString(self, value):275return isinstance(value, str) and '\n' in value276277def replaceMultilineString(self, a, b):278self.dumper.visit(a)279self.dumper._write(' -> ')280self.dumper.visit(b)281282283#284# Unfortunately JSON standard does not include comments, but this is a quite285# useful feature to have on regressions tests286#287288_token_res = [289r'//[^\r\n]*', # comment290r'"[^"\\]*(\\.[^"\\]*)*"', # string291]292293_tokens_re = re.compile(r'|'.join(['(' + token_re + ')' for token_re in _token_res]), re.DOTALL)294295296def _strip_comment(mo):297if mo.group(1):298return ''299else:300return mo.group(0)301302303def _strip_comments(data):304'''Strip (non-standard) JSON comments.'''305return _tokens_re.sub(_strip_comment, data)306307308assert _strip_comments('''// a comment309"// a comment in a string310"''') == '''311"// a comment in a string312"'''313314315def load(stream, strip_images = True, strip_comments = True):316if strip_images:317object_hook = strip_object_hook318else:319object_hook = None320if strip_comments:321data = stream.read()322data = _strip_comments(data)323return json.loads(data, strict=False, object_hook = object_hook)324else:325return json.load(stream, strict=False, object_hook = object_hook)326327328def main():329optparser = argparse.ArgumentParser(330description="Diff JSON format state dump files")331optparser.add_argument("-k", "--keep-images",332action="store_false", dest="strip_images", default=True,333help="compare images")334335optparser.add_argument("ref_json", action="store",336type=str, help="reference state file")337optparser.add_argument("src_json", action="store",338type=str, help="source state file")339340args = optparser.parse_args()341342a = load(open(args.ref_json, 'rt'), args.strip_images)343b = load(open(args.src_json, 'rt'), args.strip_images)344345if False:346dumper = Dumper()347dumper.visit(a)348349differ = Differ()350differ.visit(a, b)351352353if __name__ == '__main__':354main()355356357