#!/usr/bin/env python """ Parse XML test log file. This module serves as utility for other scripts. """ from __future__ import print_function import collections import re import os.path import sys from xml.dom.minidom import parse if sys.version_info > (3,): long = int 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] = long(self.properties[name]) elif xmlnode.hasAttribute(name): self.metrix[name] = long(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 = long(1) scale = long(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()