Index: /trunk/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py	(revision 61444)
+++ /trunk/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py	(revision 61445)
@@ -219,4 +219,5 @@
         self.oLogFile                = None;
         self.asBsodReasons           = [];
+        self.asUnitTestReasons       = [];
 
         oParser = OptionParser();
@@ -391,7 +392,12 @@
 
     ## BSOD category.
-    ksBsodCategory    = 'BSOD';
+    ksBsodCategory      = 'BSOD';
     ## Special reason indicating that the flesh and blood sheriff has work to do.
-    ksBsodAddNew      = 'Add new BSOD';
+    ksBsodAddNew        = 'Add new BSOD';
+
+    ## Unit test category.
+    ksUnitTestCategory  = 'Unit';
+    ## Special reason indicating that the flesh and blood sheriff has work to do.
+    ksUnitTestAddNew    = 'Add new';
 
     ## Used for indica that we shouldn't report anything for this test result ID and
@@ -553,18 +559,47 @@
         Checks out a VBox unittest problem.
         """
-        _ = oCaseFile;
-
-        #
-        # As a first step we'll just fish out what failed here and report
-        # the unit test case name as the "reason".  This can mostly be done
-        # using the TestResultDataEx bits, however in case it timed out and
-        # got killed we have to fish the test timing out from the logs.
-        #
-
-        #
-        # Report lone failures on the testcase, multiple failures must be
-        # reported directly on the failing test (will fix the test result
-        # listing to collect all of them).
-        #
+
+        #
+        # Process simple test case failures first, using their name as reason.
+        # We do the reason management just like for BSODs.
+        #
+        cRelevantOnes = 0;
+        aoFailedResults = oCaseFile.oTree.getListOfFailures();
+        for oFailedResult in aoFailedResults:
+            if oFailedResult is oCaseFile.oTree:
+                cRelevantOnes += 1
+            if oFailedResult.sName == 'Installing VirtualBox':
+                self.vprint('TODO: Installation failure');
+                cRelevantOnes += 1
+            elif oFailedResult.sName == 'Uninstalling VirtualBox':
+                self.vprint('TODO: Uninstallation failure');
+                cRelevantOnes += 1
+            elif oFailedResult.oParent is not None:
+                # Get the 2nd level node because that's where we'll find the unit test name.
+                o2ndLevel = oFailedResult;
+                while o2ndLevel.oParent.oParent is not None:
+                    o2ndLevel = o2ndLevel.oParent;
+
+                # Only report a failure once.
+                if o2ndLevel.idTestResult not in oCaseFile.dReasonForResultId:
+                    sKey = o2ndLevel.sName;
+                    if sKey.startswith('testcase/'):
+                        sKey = sKey[9:];
+                    if sKey in self.asUnitTestReasons:
+                        tReason = ( self.ksUnitTestCategory, sKey );
+                        oCaseFile.noteReasonForId(tReason, oFailedResult.idTestResult);
+                    else:
+                        self.dprint('Unit test failure "%s" not found in %s;' % (sKey, self.asUnitTestReasons));
+                        tReason = ( self.ksUnitTestCategory, self.ksUnitTestAddNew );
+                        oCaseFile.noteReasonForId(tReason, oFailedResult.idTestResult, sComment = sKey);
+                    cRelevantOnes += 1
+            else:
+                self.vprint('Internal error: expected oParent to NOT be None for %s' % (oFailedResult,));
+
+        #
+        # If we've caught all the relevant ones by now, report the result.
+        #
+        if len(oCaseFile.dReasonForResultId) >= cRelevantOnes:
+            return self.caseClosed(oCaseFile);
         return False;
 
@@ -613,6 +648,8 @@
                 try:    sKey = '0x%08X' % (int(sKey, 16),);
                 except: pass;
-                if sKey in self.asBsodReasons or sKey.lower() in self.asBsodReasons:
+                if sKey in self.asBsodReasons:
                     tReason = ( self.ksBsodCategory, sKey );
+                elif sKey.lower() in self.asBsodReasons: # just in case.
+                    tReason = ( self.ksBsodCategory, sKey.lower() );
                 else:
                     self.dprint('BSOD "%s" not found in %s;' % (sKey, self.asBsodReasons));
@@ -819,4 +856,5 @@
         self.oTestResultFailureLogic = TestResultFailureLogic(self.oDb);
         self.asBsodReasons           = self.oFailureReasonLogic.fetchForSheriffByNamedCategory(self.ksBsodCategory);
+        self.asUnitTestReasons       = self.oFailureReasonLogic.fetchForSheriffByNamedCategory(self.ksUnitTestCategory);
 
         # Get a fix on our 'now' before we do anything..
