Index: /trunk/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py
===================================================================
--- /trunk/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py	(revision 61552)
+++ /trunk/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py	(revision 61553)
@@ -40,5 +40,8 @@
 import sys;
 import os;
+import hashlib;
+import StringIO;
 from optparse import OptionParser;
+from PIL import Image;
 
 # Add Test Manager's modules path
@@ -182,4 +185,31 @@
             self.oSheriff.vprint('Error opening the "%s" log file: %s' % (oFile.sFile, oSizeOrError,));
         return sContent;
+
+    def getScreenshotSha256(self, oFile):
+        """
+        Tries to read the given screenshot file, uncompress it, and do SHA-2
+        on the raw pixels.
+        Returns SHA-2 digest string on success, None on failure.
+        """
+        (oFile, _, _) = self.oTestSet.openFile(oFile.sFile, 'rb');
+        try:
+            abImageFile = oFile.read();
+        except Exception as oXcpt:
+            self.oSheriff.vprint('Error reading the "%s" image file: %s' % (oFile.sFile, oXcpt,))
+        else:
+            try:
+                oImage = Image.open(StringIO.StringIO(abImageFile));
+            except Exception as oXcpt:
+                self.oSheriff.vprint('Error opening the "%s" image bytes using PIL.Image.open: %s' % (oFile.sFile, oXcpt,))
+            else:
+                try:
+                    oHash = hashlib.sha256();
+                    oHash.update(oImage.tostring());
+                except Exception as oXcpt:
+                    self.oSheriff.vprint('Error hashing the uncompressed image bytes for "%s": %s' % (oFile.sFile, oXcpt,))
+                else:
+                    return oHash.hexdigest();
+        return None;
+
 
 
@@ -377,4 +407,5 @@
     ## @name Failure reasons we know.
     ## @{
+    ktReason_BSOD_Recovery                             = ( 'BSOD',              'Recovery' );
     ktReason_Guru_Generic                              = ( 'Guru Meditations',  'Generic Guru Meditation' );
     ktReason_Guru_VERR_IEM_INSTR_NOT_IMPLEMENTED       = ( 'Guru Meditations',  'VERR_IEM_INSTR_NOT_IMPLEMENTED' );
@@ -630,4 +661,10 @@
     ];
 
+    ## Mapping screenshot/failure SHA-256 hashes to failure reasons.
+    katSimpleScreenshotHashReasons = [
+        # ( Whether to stop on hit, reason tuple, lowercased sha-256 of PIL.Image.tostring output )
+        ( True,  ktReason_BSOD_Recovery,                    '576f8e38d62b311cac7e3dc3436a0d0b9bd8cfd7fa9c43aafa95631520a45eac' ),
+    ];
+
     def investigateVMResult(self, oCaseFile, oFailedResult, sResultLog):
         """
@@ -699,4 +736,13 @@
             _ = sInfoText;
 
+            # Continue with screen hashes.
+            if sScreenHash is not None:
+                for fStopOnHit, tReason, sHash in self.katSimpleScreenshotHashReasons:
+                    if sScreenHash == sHash:
+                        oCaseFile.noteReasonForId(tReason, oFailedResult.idTestResult);
+                        if fStopOnHit:
+                            return True;
+                        fFoundSomething = True;
+
             #
             # Check for repeated reboots...
@@ -714,8 +760,9 @@
         # appear in the order that terminateVmBySession uploads them).
         #
-        sVMLog    = None;
-        sKrnlLog  = None;
-        sVgaText  = None;
-        sInfoText = None;
+        sVMLog      = None;
+        sScreenHash = None;
+        sKrnlLog    = None;
+        sVgaText    = None;
+        sInfoText   = None;
         for oFile in oFailedResult.aoFiles:
             if oFile.sKind == TestResultFileData.ksKind_LogReleaseVm:
@@ -723,8 +770,9 @@
                     if investigateLogSet() is True:
                         return True;
-                sKrnlLog  = None;
-                sVgaText  = None;
-                sInfoText = None;
-                sVMLog    = oCaseFile.getLogFile(oFile);
+                sKrnlLog    = None;
+                sScreenHash = None;
+                sVgaText    = None;
+                sInfoText   = None;
+                sVMLog      = oCaseFile.getLogFile(oFile);
             elif oFile.sKind == TestResultFileData.ksKind_LogGuestKernel:
                 sKrnlLog  = oCaseFile.getLogFile(oFile);
@@ -733,4 +781,9 @@
             elif oFile.sKind == TestResultFileData.ksKind_InfoCollection:
                 sInfoText = oCaseFile.getLogFile(oFile);
+            elif oFile.sKind == TestResultFileData.ksKind_ScreenshotFailure:
+                sScreenHash = oCaseFile.getScreenshotSha256(oFile);
+                if sScreenHash is not None:
+                    sScreenHash = sScreenHash.tolower();
+                    self.vprint('%s  %s' % ( sScreenHash, oFile.sFile,));
         if sVMLog is not None and investigateLogSet() is True:
             return True;
