#!/usr/bin/env python """ Parse XML test log file. This module serves as utility for other scripts. """ import collections import re import os.path import sys from xml.dom.minidom import parse def cmp(a, b): return (a>b)-(a 0: self.status = custom_status elif len(failures) > 0: self.status = "failed" else: self.status = xmlnode.getAttribute("status") if self.name.startswith("DISABLED_"): if self.status == 'notrun': self.status = "disabled" self.fixture = self.fixture.replace("DISABLED_", "") self.name = self.name.replace("DISABLED_", "") self.properties = { prop.getAttribute("name") : prop.getAttribute("value") for prop in xmlnode.getElementsByTagName("property") if prop.hasAttribute("name") and prop.hasAttribute("value") } self.metrix = {} self.parseLongMetric(xmlnode, "bytesIn"); self.parseLongMetric(xmlnode, "bytesOut"); self.parseIntMetric(xmlnode, "samples"); self.parseIntMetric(xmlnode, "outliers"); self.parseFloatMetric(xmlnode, "frequency", 1); self.parseLongMetric(xmlnode, "min"); self.parseLongMetric(xmlnode, "median"); self.parseLongMetric(xmlnode, "gmean"); self.parseLongMetric(xmlnode, "mean"); self.parseLongMetric(xmlnode, "stddev"); self.parseFloatMetric(xmlnode, "gstddev"); self.parseFloatMetric(xmlnode, "time"); self.parseLongMetric(xmlnode, "total_memory_usage"); def parseLongMetric(self, xmlnode, name, default = 0): if name in self.properties: self.metrix[name] = int(self.properties[name]) elif xmlnode.hasAttribute(name): self.metrix[name] = int(xmlnode.getAttribute(name)) else: self.metrix[name] = default def parseIntMetric(self, xmlnode, name, default = 0): if name in self.properties: self.metrix[name] = int(self.properties[name]) elif xmlnode.hasAttribute(name): self.metrix[name] = int(xmlnode.getAttribute(name)) else: self.metrix[name] = default def parseFloatMetric(self, xmlnode, name, default = 0): if name in self.properties: self.metrix[name] = float(self.properties[name]) elif xmlnode.hasAttribute(name): self.metrix[name] = float(xmlnode.getAttribute(name)) else: self.metrix[name] = default def parseStringMetric(self, xmlnode, name, default = None): if name in self.properties: self.metrix[name] = self.properties[name].strip() elif xmlnode.hasAttribute(name): self.metrix[name] = xmlnode.getAttribute(name).strip() else: self.metrix[name] = default def get(self, name, units="ms"): if name == "classname": return self.fixture if name == "name": return self.name if name == "fullname": return self.__str__() if name == "value_param": return self.value_param if name == "type_param": return self.type_param if name == "status": return self.status val = self.metrix.get(name, None) if not val: return val if name == "time": return self.metrix.get("time") if name in ["gmean", "min", "mean", "median", "stddev"]: scale = 1.0 frequency = self.metrix.get("frequency", 1.0) or 1.0 if units == "ms": scale = 1000.0 if units == "us" or units == "mks": # mks is typo error for microsecond (<= OpenCV 3.4) scale = 1000000.0 if units == "ns": scale = 1000000000.0 if units == "ticks": frequency = int(1) scale = int(1) return val * scale / frequency return val def dump(self, units="ms"): print("%s ->\t\033[1;31m%s\033[0m = \t%.2f%s" % (str(self), self.status, self.get("gmean", units), units)) def getName(self): pos = self.name.find("/") if pos > 0: return self.name[:pos] return self.name def getFixture(self): if self.fixture.endswith(self.getName()): fixture = self.fixture[:-len(self.getName())] else: fixture = self.fixture if fixture.endswith("_"): fixture = fixture[:-1] return fixture def param(self): return '::'.join(filter(None, [self.type_param, self.value_param])) def shortName(self): name = self.getName() fixture = self.getFixture() return '::'.join(filter(None, [name, fixture])) def __str__(self): name = self.getName() fixture = self.getFixture() return '::'.join(filter(None, [name, fixture, self.type_param, self.value_param])) def __cmp__(self, other): r = cmp(self.fixture, other.fixture); if r != 0: return r if self.type_param: if other.type_param: r = cmp(self.type_param, other.type_param); if r != 0: return r else: return -1 else: if other.type_param: return 1 if self.value_param: if other.value_param: r = cmp(self.value_param, other.value_param); if r != 0: return r else: return -1 else: if other.value_param: return 1 return 0 def __lt__(self, other): return self.__cmp__(other) == -1 # This is a Sequence for compatibility with old scripts, # which treat parseLogFile's return value as a list. class TestRunInfo(object): def __init__(self, properties, tests): self.properties = properties self.tests = tests def __len__(self): return len(self.tests) def __getitem__(self, key): return self.tests[key] def parseLogFile(filename): log = parse(filename) properties = { attr_name[3:]: attr_value for (attr_name, attr_value) in log.documentElement.attributes.items() if attr_name.startswith('cv_') } tests = list(map(TestInfo, log.getElementsByTagName("testcase"))) return TestRunInfo(properties, tests) if __name__ == "__main__": if len(sys.argv) < 2: print("Usage:\n", os.path.basename(sys.argv[0]), ".xml") exit(0) for arg in sys.argv[1:]: print("Processing {}...".format(arg)) run = parseLogFile(arg) print("Properties:") for (prop_name, prop_value) in run.properties.items(): print("\t{} = {}".format(prop_name, prop_value)) print("Tests:") for t in sorted(run.tests): t.dump() print()