summaryrefslogtreecommitdiffstats
path: root/src/VBox/ValidationKit/testanalysis
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 03:01:46 +0000
commitf8fe689a81f906d1b91bb3220acde2a4ecb14c5b (patch)
tree26484e9d7e2c67806c2d1760196ff01aaa858e8c /src/VBox/ValidationKit/testanalysis
parentInitial commit. (diff)
downloadvirtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.tar.xz
virtualbox-f8fe689a81f906d1b91bb3220acde2a4ecb14c5b.zip
Adding upstream version 6.0.4-dfsg.upstream/6.0.4-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/ValidationKit/testanalysis')
-rw-r--r--src/VBox/ValidationKit/testanalysis/Makefile.kmk35
-rw-r--r--src/VBox/ValidationKit/testanalysis/__init__.py31
-rwxr-xr-xsrc/VBox/ValidationKit/testanalysis/diff.py95
-rwxr-xr-xsrc/VBox/ValidationKit/testanalysis/reader.py291
-rwxr-xr-xsrc/VBox/ValidationKit/testanalysis/reporting.py302
-rwxr-xr-xsrc/VBox/ValidationKit/testanalysis/tst-a1.py100
6 files changed, 854 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/testanalysis/Makefile.kmk b/src/VBox/ValidationKit/testanalysis/Makefile.kmk
new file mode 100644
index 00000000..3cdc1c86
--- /dev/null
+++ b/src/VBox/ValidationKit/testanalysis/Makefile.kmk
@@ -0,0 +1,35 @@
+# $Id: Makefile.kmk $
+## @file
+# VirtualBox Validation Kit - Python Test Driver.
+#
+
+#
+# Copyright (C) 2010-2019 Oracle Corporation
+#
+# This file is part of VirtualBox Open Source Edition (OSE), as
+# available from http://www.virtualbox.org. This file is free software;
+# you can redistribute it and/or modify it under the terms of the GNU
+# General Public License (GPL) as published by the Free Software
+# Foundation, in version 2 as it comes in the "COPYING" file of the
+# VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+# hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+#
+# The contents of this file may alternatively be used under the terms
+# of the Common Development and Distribution License Version 1.0
+# (CDDL) only, as it comes in the "COPYING.CDDL" file of the
+# VirtualBox OSE distribution, in which case the provisions of the
+# CDDL are applicable instead of those of the GPL.
+#
+# You may elect to license modified versions of this file under the
+# terms and conditions of either the GPL or the CDDL or both.
+#
+
+SUB_DEPTH = ../../../..
+include $(KBUILD_PATH)/subheader.kmk
+
+
+VBOX_VALIDATIONKIT_PYTHON_SOURCES += $(wildcard $(PATH_SUB_CURRENT)/*.py)
+
+$(evalcall def_vbox_validationkit_process_python_sources)
+include $(FILE_KBUILD_SUB_FOOTER)
+
diff --git a/src/VBox/ValidationKit/testanalysis/__init__.py b/src/VBox/ValidationKit/testanalysis/__init__.py
new file mode 100644
index 00000000..650cba1d
--- /dev/null
+++ b/src/VBox/ValidationKit/testanalysis/__init__.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# $Id: __init__.py $
+
+"""
+Test analysis package
+"""
+
+__copyright__ = \
+"""
+Copyright (C) 2010-2019 Oracle Corporation
+
+This file is part of VirtualBox Open Source Edition (OSE), as
+available from http://www.virtualbox.org. This file is free software;
+you can redistribute it and/or modify it under the terms of the GNU
+General Public License (GPL) as published by the Free Software
+Foundation, in version 2 as it comes in the "COPYING" file of the
+VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL) only, as it comes in the "COPYING.CDDL" file of the
+VirtualBox OSE distribution, in which case the provisions of the
+CDDL are applicable instead of those of the GPL.
+
+You may elect to license modified versions of this file under the
+terms and conditions of either the GPL or the CDDL or both.
+"""
+__version__ = "$Revision: 127855 $"
+__all__ = ["reader", "diff", "reporting"]
+
diff --git a/src/VBox/ValidationKit/testanalysis/diff.py b/src/VBox/ValidationKit/testanalysis/diff.py
new file mode 100755
index 00000000..f75897d0
--- /dev/null
+++ b/src/VBox/ValidationKit/testanalysis/diff.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+# $Id: diff.py $
+
+"""
+Diff two test sets.
+"""
+
+__copyright__ = \
+"""
+Copyright (C) 2010-2019 Oracle Corporation
+
+This file is part of VirtualBox Open Source Edition (OSE), as
+available from http://www.virtualbox.org. This file is free software;
+you can redistribute it and/or modify it under the terms of the GNU
+General Public License (GPL) as published by the Free Software
+Foundation, in version 2 as it comes in the "COPYING" file of the
+VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL) only, as it comes in the "COPYING.CDDL" file of the
+VirtualBox OSE distribution, in which case the provisions of the
+CDDL are applicable instead of those of the GPL.
+
+You may elect to license modified versions of this file under the
+terms and conditions of either the GPL or the CDDL or both.
+"""
+__version__ = "$Revision: 127855 $"
+__all__ = ['BaselineDiff', ];
+
+
+def _findBaselineTest(oBaseline, oTest):
+ """ Recursively finds the test in oBaseline corresponding to oTest. """
+ if oTest.oParent is None:
+ return oBaseline;
+ oBaseline = _findBaselineTest(oBaseline, oTest.oParent);
+ if oBaseline is not None:
+ for oBaseTest in oBaseline.aoChildren:
+ if oBaseTest.sName == oTest.sName:
+ return oBaseTest;
+ return None;
+
+def _findBaselineTestValue(oBaseline, oValue):
+ """ Finds the baseline value corresponding to oValue. """
+ oBaseTest = _findBaselineTest(oBaseline, oValue.oTest);
+ if oBaseTest is not None:
+ for oBaseValue in oBaseTest.aoValues:
+ if oBaseValue.sName == oValue.sName:
+ return oBaseValue;
+ return None;
+
+def baselineDiff(oTestTree, oBaseline):
+ """
+ Diffs oTestTree against oBaseline, adding diff info to oTestTree.
+ Returns oTestTree on success, None on failure (complained already).
+ """
+
+ aoStack = [];
+ aoStack.append((oTestTree, 0));
+ while len(aoStack) > 0:
+ oCurTest, iChild = aoStack.pop();
+
+ # depth first
+ if iChild < len(oCurTest.aoChildren):
+ aoStack.append((oCurTest, iChild + 1));
+ aoStack.append((oCurTest.aoChildren[iChild], 0));
+ continue;
+
+ # do value diff.
+ for i in range(len(oCurTest.aoValues)):
+ oBaseVal = _findBaselineTestValue(oBaseline, oCurTest.aoValues[i]);
+ if oBaseVal is not None:
+ try:
+ lBase = long(oBaseVal.sValue);
+ lTest = long(oCurTest.aoValues[i].sValue);
+ except:
+ try:
+ if oBaseVal.sValue == oCurTest.aoValues[i].sValue:
+ oCurTest.aoValues[i].sValue += '|';
+ else:
+ oCurTest.aoValues[i].sValue += '|%s' % (oBaseVal.sValue);
+ except:
+ oCurTest.aoValues[i].sValue += '|%s' % (oBaseVal.sValue);
+ else:
+ if lTest > lBase:
+ oCurTest.aoValues[i].sValue += '|+%u' % (lTest - lBase);
+ elif lTest < lBase:
+ oCurTest.aoValues[i].sValue += '|-%u' % (lBase - lTest);
+ else:
+ oCurTest.aoValues[i].sValue += '|0';
+ else:
+ oCurTest.aoValues[i].sValue += '|N/A';
+
+ return oTestTree;
diff --git a/src/VBox/ValidationKit/testanalysis/reader.py b/src/VBox/ValidationKit/testanalysis/reader.py
new file mode 100755
index 00000000..f46b3924
--- /dev/null
+++ b/src/VBox/ValidationKit/testanalysis/reader.py
@@ -0,0 +1,291 @@
+# -*- coding: utf-8 -*-
+# $Id: reader.py $
+
+"""
+XML reader module.
+
+This produces a test result tree that can be processed and passed to
+reporting.
+"""
+
+__copyright__ = \
+"""
+Copyright (C) 2010-2019 Oracle Corporation
+
+This file is part of VirtualBox Open Source Edition (OSE), as
+available from http://www.virtualbox.org. This file is free software;
+you can redistribute it and/or modify it under the terms of the GNU
+General Public License (GPL) as published by the Free Software
+Foundation, in version 2 as it comes in the "COPYING" file of the
+VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL) only, as it comes in the "COPYING.CDDL" file of the
+VirtualBox OSE distribution, in which case the provisions of the
+CDDL are applicable instead of those of the GPL.
+
+You may elect to license modified versions of this file under the
+terms and conditions of either the GPL or the CDDL or both.
+"""
+__version__ = "$Revision: 127855 $"
+__all__ = ['ParseTestResult', ]
+
+# Standard python imports.
+import os
+import traceback
+
+# pylint: disable=C0111
+
+class Value(object):
+ """
+ Represents a value. Usually this is benchmark result or parameter.
+ """
+ def __init__(self, oTest, hsAttrs):
+ self.oTest = oTest;
+ self.sName = hsAttrs['name'];
+ self.sUnit = hsAttrs['unit'];
+ self.sTimestamp = hsAttrs['timestamp'];
+ self.sValue = '';
+ self.sDiff = None;
+
+ # parsing
+
+ def addData(self, sData):
+ self.sValue += sData;
+
+ # debug
+
+ def printValue(self, cIndent):
+ print '%sValue: name=%s timestamp=%s unit=%s value="%s"' \
+ % (''.ljust(cIndent*2), self.sName, self.sTimestamp, self.sUnit, self.sValue);
+
+
+class Test(object):
+ """
+ Nested test result.
+ """
+ def __init__(self, oParent, hsAttrs):
+ self.aoChildren = [];
+ self.aoValues = [];
+ self.oParent = oParent;
+ self.sName = hsAttrs['name'];
+ self.sStartTS = hsAttrs['timestamp'];
+ self.sEndTS = None;
+ self.sStatus = None;
+ self.cErrors = -1;
+ self.sStatusDiff = None;
+ self.cErrorsDiff = None;
+
+ # parsing
+
+ def addChild(self, oChild):
+ self.aoChildren.append(oChild);
+ return oChild;
+
+ def addValue(self, hsAttrs):
+ oValue = hsAttrs['value'];
+ self.aoValues.append(oValue);
+ return oValue;
+
+ def markPassed(self, hsAttrs):
+ try: self.sEndTS = hsAttrs['timestamp'];
+ except: pass;
+ self.sStatus = 'passed';
+ self.cErrors = 0;
+
+ def markSkipped(self, hsAttrs):
+ try: self.sEndTS = hsAttrs['timestamp'];
+ except: pass;
+ self.sStatus = 'skipped';
+ self.cErrors = 0;
+
+ def markFailed(self, hsAttrs):
+ try: self.sEndTS = hsAttrs['timestamp'];
+ except: pass;
+ self.sStatus = 'failed';
+ self.cErrors = int(hsAttrs['errors']);
+
+ def markEnd(self, hsAttrs):
+ try: self.sEndTS = hsAttrs['timestamp'];
+ except: pass;
+ if self.sStatus is None:
+ self.sStatus = 'end';
+ self.cErrors = 0;
+
+ def mergeInIncludedTest(self, oTest):
+ """ oTest will be robbed. """
+ if oTest is not None:
+ for oChild in oTest.aoChildren:
+ oChild.oParent = self;
+ self.aoChildren.append(oChild);
+ for oValue in oTest.aoValues:
+ oValue.oTest = self;
+ self.aoValues.append(oValue);
+ oTest.aoChildren = [];
+ oTest.aoValues = [];
+
+ # debug
+
+ def printTree(self, iLevel = 0):
+ print '%sTest: name=%s start=%s end=%s' % (''.ljust(iLevel*2), self.sName, self.sStartTS, self.sEndTS);
+ for oChild in self.aoChildren:
+ oChild.printTree(iLevel + 1);
+ for oValue in self.aoValues:
+ oValue.printValue(iLevel + 1);
+
+ # getters / queries
+
+ def getFullNameWorker(self, cSkipUpper):
+ if self.oParent is None:
+ return (self.sName, 0);
+ sName, iLevel = self.oParent.getFullNameWorker(cSkipUpper);
+ if iLevel < cSkipUpper:
+ sName = self.sName;
+ else:
+ sName += ', ' + self.sName;
+ return (sName, iLevel + 1);
+
+ def getFullName(self, cSkipUpper = 2):
+ return self.getFullNameWorker(cSkipUpper)[0];
+
+ def matchFilters(self, asFilters):
+ """
+ Checks if the all of the specified filter strings are substrings
+ of the full test name. Returns True / False.
+ """
+ sName = self.getFullName();
+ for sFilter in asFilters:
+ if sName.find(sFilter) < 0:
+ return False;
+ return True;
+
+ # manipulation
+
+ def filterTestsWorker(self, asFilters):
+ # depth first
+ i = 0;
+ while i < len(self.aoChildren):
+ if self.aoChildren[i].filterTestsWorker(asFilters):
+ i += 1;
+ else:
+ self.aoChildren[i].oParent = None;
+ del self.aoChildren[i];
+
+ # If we have children, they must've matched up.
+ if len(self.aoChildren) != 0:
+ return True;
+ return self.matchFilters(asFilters);
+
+ def filterTests(self, asFilters):
+ if len(asFilters) > 0:
+ self.filterTestsWorker(asFilters)
+ return self;
+
+
+class XmlLogReader(object):
+ """
+ XML log reader class.
+ """
+
+ def __init__(self, sXmlFile):
+ self.sXmlFile = sXmlFile;
+ self.oRoot = Test(None, {'name': 'root', 'timestamp': ''});
+ self.oTest = self.oRoot;
+ self.iLevel = 0;
+ self.oValue = None;
+
+ def parse(self):
+ try:
+ oFile = open(self.sXmlFile, 'r');
+ except:
+ traceback.print_exc();
+ return False;
+
+ from xml.parsers.expat import ParserCreate
+ oParser = ParserCreate();
+ oParser.StartElementHandler = self.handleElementStart;
+ oParser.CharacterDataHandler = self.handleElementData;
+ oParser.EndElementHandler = self.handleElementEnd;
+ try:
+ oParser.ParseFile(oFile);
+ except:
+ traceback.print_exc();
+ oFile.close();
+ return False;
+ oFile.close();
+ return True;
+
+ def handleElementStart(self, sName, hsAttrs):
+ #print '%s%s: %s' % (''.ljust(self.iLevel * 2), sName, str(hsAttrs));
+ if sName == 'Test' or sName == 'SubTest':
+ self.iLevel += 1;
+ self.oTest = self.oTest.addChild(Test(self.oTest, hsAttrs));
+ elif sName == 'Value':
+ self.oValue = self.oTest.addValue(hsAttrs);
+ elif sName == 'End':
+ self.oTest.markEnd(hsAttrs);
+ elif sName == 'Passed':
+ self.oTest.markPassed(hsAttrs);
+ elif sName == 'Skipped':
+ self.oTest.markSkipped(hsAttrs);
+ elif sName == 'Failed':
+ self.oTest.markFailed(hsAttrs);
+ elif sName == 'Include':
+ self.handleInclude(hsAttrs);
+ else:
+ print 'Unknonwn element "%s"' % (sName);
+
+ def handleElementData(self, sData):
+ if self.oValue is not None:
+ self.oValue.addData(sData);
+ elif sData.strip() != '':
+ print 'Unexpected data "%s"' % (sData);
+ return True;
+
+ def handleElementEnd(self, sName):
+ if sName == 'Test' or sName == 'Subtest':
+ self.iLevel -= 1;
+ self.oTest = self.oTest.oParent;
+ elif sName == 'Value':
+ self.oValue = None;
+ return True;
+
+ def handleInclude(self, hsAttrs):
+ # relative or absolute path.
+ sXmlFile = hsAttrs['filename'];
+ if not os.path.isabs(sXmlFile):
+ sXmlFile = os.path.join(os.path.dirname(self.sXmlFile), sXmlFile);
+
+ # Try parse it.
+ oSub = parseTestResult(sXmlFile);
+ if oSub is None:
+ print 'error: failed to parse include "%s"' % (sXmlFile);
+ else:
+ # Skip the root and the next level before merging it the subtest and
+ # values in to the current test. The reason for this is that the
+ # include is the output of some sub-program we've run and we don't need
+ # the extra test level it automatically adds.
+ #
+ # More benchmark heuristics: Walk down until we find more than one
+ # test or values.
+ oSub2 = oSub;
+ while len(oSub2.aoChildren) == 1 and len(oSub2.aoValues) == 0:
+ oSub2 = oSub2.aoChildren[0];
+ if len(oSub2.aoValues) == 0:
+ oSub2 = oSub;
+ self.oTest.mergeInIncludedTest(oSub2);
+ return True;
+
+def parseTestResult(sXmlFile):
+ """
+ Parses the test results in the XML.
+ Returns result tree.
+ Returns None on failure.
+ """
+ oXlr = XmlLogReader(sXmlFile);
+ if oXlr.parse():
+ return oXlr.oRoot;
+ return None;
+
diff --git a/src/VBox/ValidationKit/testanalysis/reporting.py b/src/VBox/ValidationKit/testanalysis/reporting.py
new file mode 100755
index 00000000..3ff334ce
--- /dev/null
+++ b/src/VBox/ValidationKit/testanalysis/reporting.py
@@ -0,0 +1,302 @@
+# -*- coding: utf-8 -*-
+# $Id: reporting.py $
+
+"""
+Test Result Report Writer.
+
+This takes a processed test result tree and creates a HTML, re-structured text,
+or normal text report from it.
+"""
+
+__copyright__ = \
+"""
+Copyright (C) 2010-2019 Oracle Corporation
+
+This file is part of VirtualBox Open Source Edition (OSE), as
+available from http://www.virtualbox.org. This file is free software;
+you can redistribute it and/or modify it under the terms of the GNU
+General Public License (GPL) as published by the Free Software
+Foundation, in version 2 as it comes in the "COPYING" file of the
+VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL) only, as it comes in the "COPYING.CDDL" file of the
+VirtualBox OSE distribution, in which case the provisions of the
+CDDL are applicable instead of those of the GPL.
+
+You may elect to license modified versions of this file under the
+terms and conditions of either the GPL or the CDDL or both.
+"""
+__version__ = "$Revision: 127855 $"
+__all__ = ['HtmlReport', 'RstReport', 'TextReport'];
+
+
+def tryAddThousandSeparators(sPotentialInterger):
+ """ Apparently, python 3.0(/3.1) has(/will have) support for this..."""
+ # Try convert the string/value to a long.
+ try:
+ lVal = long(sPotentialInterger);
+ lVal = long(sPotentialInterger);
+ except:
+ return sPotentialInterger;
+
+ # Convert it back to a string (paranoia) and build up the new string.
+ sOld = str(lVal);
+ chSign = '';
+ if sOld[0] == '-':
+ chSign = '-';
+ sOld = sOld[1:];
+ elif sPotentialInterger[0] == '+':
+ chSign = '+';
+ cchDigits = len(sOld);
+ iDigit = 0
+ sNewVal = '';
+ while iDigit < cchDigits:
+ if (iDigit % 3) == 0 and iDigit > 0:
+ sNewVal = ' ' + sNewVal;
+ sNewVal = sOld[cchDigits - iDigit - 1] + sNewVal;
+ iDigit += 1;
+ return chSign + sNewVal;
+
+
+class Table(object):
+ """
+ A table as a header as well as data rows, thus this class.
+ """
+ def __init__(self, oTest, fSplitDiff):
+ self.aasRows = [];
+ self.asHeader = ['Test',];
+ self.asUnits = ['',];
+ for oValue in oTest.aoValues:
+ self.asHeader.append(oValue.sName);
+ self.asUnits.append(oValue.sUnit);
+ self.addRow(oTest, fSplitDiff);
+
+ def addRow(self, oTest, fSplitDiff):
+ """Adds a row."""
+ asRow = [oTest.getFullName(),];
+ for oValue in oTest.aoValues:
+ asRow.append(oValue.sValue);
+ if not fSplitDiff:
+ self.aasRows.append(asRow);
+ else:
+ # Split cells into multiple rows on '|'. Omit the first column.
+ iRow = 0;
+ asThisRow = [asRow[0], ];
+ fMoreTodo = True;
+ while fMoreTodo:
+ for i in range(1, len(asRow)):
+ asSplit = asRow[i].split('|');
+ asThisRow.append(asSplit[0]);
+ asRow[i] = '|'.join(asSplit[1:])
+ self.aasRows.append(asThisRow);
+
+ # Done?
+ fMoreTodo = False;
+ for i in range(1, len(asRow)):
+ if len(asRow[i]):
+ fMoreTodo = True;
+ asThisRow = ['', ];
+ iRow += 1;
+
+ # Readability hack: Add an extra row if there are diffs.
+ if iRow > 1:
+ asRow[0] = '';
+ self.aasRows.append(asRow);
+
+ return True;
+
+ def hasTheSameHeadingAsTest(self, oTest):
+ """ Checks if the test values has the same heading."""
+ i = 1;
+ for oValue in oTest.aoValues:
+ if self.asHeader[i] != oValue.sName:
+ return False;
+ if self.asUnits[i] != oValue.sUnit:
+ return False;
+ i += 1;
+ return True;
+
+ def hasTheSameHeadingAsTable(self, oTable):
+ """ Checks if the other table has the same heading."""
+ if len(oTable.asHeader) != len(self.asHeader):
+ return False;
+ for i in range(len(self.asHeader)):
+ if self.asHeader[i] != oTable.asHeader[i]:
+ return False;
+ if self.asUnits[i] != oTable.asUnits[i]:
+ return False;
+ return True;
+
+ def appendTable(self, oTable):
+ """ Append the rows in oTable. oTable has the same heading as us. """
+ self.aasRows.extend(oTable.aasRows);
+ return True;
+
+ # manipulation and stuff
+
+ def optimizeUnit(self):
+ """ Turns bytes into KB, MB or GB. """
+ ## @todo
+ pass;
+
+ def addThousandSeparators(self):
+ """ Adds thousand separators to make numbers more readable. """
+ for iRow in range(len(self.aasRows)):
+ for iColumn in range(1, len(self.aasRows[iRow])):
+ asValues = self.aasRows[iRow][iColumn].split('|');
+ for i in range(len(asValues)):
+ asValues[i] = tryAddThousandSeparators(asValues[i]);
+ self.aasRows[iRow][iColumn] = '|'.join(asValues);
+ return True;
+
+ def getRowWidths(self):
+ """Figure out the column withs."""
+ # Header is first.
+ acchColumns = [];
+ for i in range(len(self.asHeader)):
+ cch = 1;
+ asWords = self.asHeader[i].split();
+ for s in asWords:
+ if len(s) > cch:
+ cch = len(s);
+ if i > 0 and len(self.asUnits[i]) > cch:
+ cch = len(self.asUnits[i]);
+ acchColumns.append(cch);
+
+ # Check out all cells.
+ for asColumns in self.aasRows:
+ for i in range(len(asColumns)):
+ if len(asColumns[i]) > acchColumns[i]:
+ acchColumns[i] = len(asColumns[i]);
+ return acchColumns;
+
+
+def tabelizeTestResults(oTest, fSplitDiff):
+ """
+ Break the test results down into a list of tables containing the values.
+
+ TODO: Handle passed / failed stuff too. Not important for benchmarks.
+ """
+ # Pass 1
+ aoTables = [];
+ aoStack = [];
+ aoStack.append((oTest, 0));
+ while len(aoStack) > 0:
+ oCurTest, iChild = aoStack.pop();
+
+ # depth first
+ if iChild < len(oCurTest.aoChildren):
+ aoStack.append((oCurTest, iChild + 1));
+ aoStack.append((oCurTest.aoChildren[iChild], 0));
+ continue;
+
+ # values -> row
+ if len(oCurTest.aoValues) > 0:
+ if len(aoTables) > 0 and aoTables[len(aoTables) - 1].hasTheSameHeadingAsTest(oCurTest):
+ aoTables[len(aoTables) - 1].addRow(oCurTest, fSplitDiff);
+ else:
+ aoTables.append(Table(oCurTest, fSplitDiff));
+
+ # Pass 2 - Combine tables with the same heading.
+ aoTables2 = [];
+ for oTable in aoTables:
+ for i in range(len(aoTables2)):
+ if aoTables2[i].hasTheSameHeadingAsTable(oTable):
+ aoTables2[i].appendTable(oTable);
+ oTable = None;
+ break;
+ if oTable is not None:
+ aoTables2.append(oTable);
+
+ return aoTables2;
+
+def produceHtmlReport(oTest):
+ """
+ Produce an HTML report on stdout (via print).
+ """
+ print 'not implemented: %s' % (oTest);
+ return False;
+
+def produceReStructuredTextReport(oTest):
+ """
+ Produce a ReStructured text report on stdout (via print).
+ """
+ print 'not implemented: %s' % (oTest);
+ return False;
+
+def produceTextReport(oTest):
+ """
+ Produce a text report on stdout (via print).
+ """
+
+ #
+ # Report header.
+ #
+ ## @todo later
+
+ #
+ # Tabelize the results and display the tables.
+ #
+ aoTables = tabelizeTestResults(oTest, True)
+ for oTable in aoTables:
+ ## @todo do max/min on the columns where we can do [GMK]B(/s).
+ oTable.addThousandSeparators();
+ acchColumns = oTable.getRowWidths();
+
+ # The header.
+ # This is a bit tedious and the solution isn't entirely elegant due
+ # to the pick-it-up-as-you-go-along python skills.
+ aasHeader = [];
+ aasHeader.append([]);
+ for i in range(len(oTable.asHeader)):
+ aasHeader[0].append('');
+
+ for iColumn in range(len(oTable.asHeader)):
+ asWords = oTable.asHeader[iColumn].split();
+ iLine = 0;
+ for s in asWords:
+ if len(aasHeader[iLine][iColumn]) <= 0:
+ aasHeader[iLine][iColumn] = s;
+ elif len(s) + 1 + len(aasHeader[iLine][iColumn]) <= acchColumns[iColumn]:
+ aasHeader[iLine][iColumn] += ' ' + s;
+ else:
+ iLine += 1;
+ if iLine >= len(aasHeader): # There must be a better way to do this...
+ aasHeader.append([]);
+ for i in range(len(oTable.asHeader)):
+ aasHeader[iLine].append('');
+ aasHeader[iLine][iColumn] = s;
+
+ for asLine in aasHeader:
+ sLine = '';
+ for i in range(len(asLine)):
+ if i > 0: sLine += ' ';
+ sLine += asLine[i].center(acchColumns[i]);
+ print sLine;
+
+ # Units.
+ sLine = '';
+ for i in range(len(oTable.asUnits)):
+ if i > 0: sLine += ' ';
+ sLine += oTable.asUnits[i].center(acchColumns[i]);
+ print sLine;
+
+ # Separator line.
+ sLine = '';
+ for i in range(len(oTable.asHeader)):
+ if i > 0: sLine += ' '
+ sLine += '=' * acchColumns[i];
+ print sLine;
+
+ # The rows.
+ for asColumns in oTable.aasRows:
+ sText = asColumns[0].ljust(acchColumns[0]);
+ for i in range(1, len(asColumns)):
+ sText += ' ' + asColumns[i].rjust(acchColumns[i]);
+ print sText;
+
+ return None;
+
diff --git a/src/VBox/ValidationKit/testanalysis/tst-a1.py b/src/VBox/ValidationKit/testanalysis/tst-a1.py
new file mode 100755
index 00000000..47b2a565
--- /dev/null
+++ b/src/VBox/ValidationKit/testanalysis/tst-a1.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# $Id: tst-a1.py $
+
+"""
+Analyzer Experiment 1.
+"""
+
+__copyright__ = \
+"""
+Copyright (C) 2010-2019 Oracle Corporation
+
+This file is part of VirtualBox Open Source Edition (OSE), as
+available from http://www.virtualbox.org. This file is free software;
+you can redistribute it and/or modify it under the terms of the GNU
+General Public License (GPL) as published by the Free Software
+Foundation, in version 2 as it comes in the "COPYING" file of the
+VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+
+The contents of this file may alternatively be used under the terms
+of the Common Development and Distribution License Version 1.0
+(CDDL) only, as it comes in the "COPYING.CDDL" file of the
+VirtualBox OSE distribution, in which case the provisions of the
+CDDL are applicable instead of those of the GPL.
+
+You may elect to license modified versions of this file under the
+terms and conditions of either the GPL or the CDDL or both.
+"""
+__version__ = "$Revision: 127855 $"
+
+
+import os.path
+import sys
+
+# Only the main script needs to modify the path.
+try: __file__
+except: __file__ = sys.argv[0];
+g_ksValidationKitDir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)));
+sys.path.append(g_ksValidationKitDir);
+
+# Validation Kit imports.
+from testanalysis import reader ## @todo fix testanalysis/__init__.py.
+from testanalysis import reporting
+from testanalysis import diff
+
+
+def usage():
+ """ Display usage """
+ print 'usage: %s [options] <testresults.xml> [baseline.xml]' % (sys.argv[0]);
+ print ''
+ print 'options:'
+ print ' --filter <test-sub-string>'
+ return 1;
+
+def main(asArgs):
+ """ C styl main(). """
+ # Parse arguments
+ sTestFile = None;
+ sBaseFile = None;
+ asFilters = [];
+ iArg = 1;
+ while iArg < len(asArgs):
+ if asArgs[iArg] == '--filter':
+ iArg += 1;
+ asFilters.append(asArgs[iArg]);
+ elif asArgs[iArg].startswith('--help'):
+ return usage();
+ elif asArgs[iArg].startswith('--'):
+ print 'syntax error: unknown option "%s"' % (asArgs[iArg]);
+ return usage();
+ elif sTestFile is None:
+ sTestFile = asArgs[iArg];
+ elif sBaseFile is None:
+ sBaseFile = asArgs[iArg];
+ else:
+ print 'syntax error: too many file names: %s' % (asArgs[iArg])
+ return usage();
+ iArg += 1;
+
+ # Down to business
+ oTestTree = reader.parseTestResult(sTestFile);
+ if oTestTree is None:
+ return 1;
+ oTestTree = oTestTree.filterTests(asFilters)
+
+ if sBaseFile is not None:
+ oBaseline = reader.parseTestResult(sBaseFile);
+ if oBaseline is None:
+ return 1;
+ oTestTree = diff.baselineDiff(oTestTree, oBaseline);
+ if oTestTree is None:
+ return 1;
+
+ reporting.produceTextReport(oTestTree);
+ return 0;
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv));
+