]> git.cworth.org Git - apitrace-tests/blobdiff - test.py
Take snapshots with TRACE_SNAPSHOT.
[apitrace-tests] / test.py
diff --git a/test.py b/test.py
index ec25fabad1e1d4b9eef4c8ac79eede225b8c2b0b..b3d2ed9722a51a886dc6e4807e752e49acc77a26 100755 (executable)
--- a/test.py
+++ b/test.py
 '''Common test suite code.'''
 
 
-import os.path
+import math
 import optparse
-import sys
-import subprocess
-import time
+import os.path
 import re
 import signal
+import subprocess
+import sys
+import time
+
+import Image
+import ImageChops
+import ImageEnhance
+
+
+class Comparer:
+    '''Image comparer.'''
+
+    def __init__(self, ref_image, src_image, alpha = False):
+        self.ref_im = Image.open(ref_image)
+        self.src_im = Image.open(src_image)
+
+        # Crop to the minimum size
+        ref_w, ref_h = self.ref_im.size
+        src_w, src_h = self.src_im.size
+        w = min(ref_w, src_w)
+        h = min(ref_h, src_h)
+        self.ref_im = self.ref_im.crop((0, ref_h - h, w, ref_h))
+        self.src_im = self.src_im.crop((0, src_h - h, w, src_h))
+
+        # Ignore alpha
+        if not alpha:
+            self.ref_im = self.ref_im.convert('RGB')
+            self.src_im = self.src_im.convert('RGB')
+
+        self.diff = ImageChops.difference(self.src_im, self.ref_im)
+
+    def write_diff(self, diff_image, fuzz = 0.05):
+        # make a difference image similar to ImageMagick's compare utility
+        mask = ImageEnhance.Brightness(self.diff).enhance(1.0/fuzz)
+        mask = mask.convert('L')
+
+        lowlight = Image.new('RGB', self.src_im.size, (0xff, 0xff, 0xff))
+        highlight = Image.new('RGB', self.src_im.size, (0xf1, 0x00, 0x1e))
+        diff_im = Image.composite(highlight, lowlight, mask)
+
+        diff_im = Image.blend(self.src_im, diff_im, 0xcc/255.0)
+        diff_im.save(diff_image)
+
+    def precision(self):
+        # See also http://effbot.org/zone/pil-comparing-images.htm
+        h = self.diff.histogram()
+        square_error = 0
+        for i in range(1, 256):
+            square_error += sum(h[i : 3*256: 256])*i*i
+        rel_error = float(square_error*2 + 1) / float(self.diff.size[0]*self.diff.size[1]*3*255*255*2)
+        bits = -math.log(rel_error)/math.log(2.0)
+        return bits
+
+    def ae(self, chantol = 4, pixeltol = 0.03):
+        # Compute absolute error
+        # chantol = color channel tolerance
+        # pixeltol = ratio of pixels we allow to go completely off
+        
+        # TODO: this is approximate due to the grayscale conversion
+        h = self.diff.convert('L').histogram()
+        
+        ae = sum(h[int(chantol) + 1 : 256])
+        
+        return ae <= pixeltol*self.diff.size[0]*self.diff.size[1]
 
 
 ansi_re = re.compile('\x1b\[[0-9]{1,2}(;[0-9]{1,2}){0,2}m')
@@ -111,6 +173,10 @@ class Report:
 
 class TestCase:
 
+    standalone = False
+
+    max_frames = 1
+
     def __init__(self, name, args, cwd=None, build=None, results = '.'):
         self.name = name
         self.args = args
@@ -133,48 +199,51 @@ class TestCase:
     def run(self, report):
 
         trace = os.path.abspath(os.path.join(self.results, self.name + '.trace'))
+        ref_prefix = os.path.abspath(os.path.join(self.results, self.name + '.ref.'))
+        src_prefix = os.path.join(self.results, self.name + '.src.')
+        diff_prefix = os.path.join(self.results, self.name + '.diff.')
 
         ld_preload = self._get_build_path('glxtrace.so')
 
         env = os.environ.copy()
         env['LD_PRELOAD'] = ld_preload
         env['TRACE_FILE'] = trace
+        env['TRACE_SNAPSHOT'] = ref_prefix
+        env['TRACE_FRAMES'] = str(self.max_frames)
 
-        window_name = self.args[0]
-
-        p = popen(self.args, cwd=self.cwd)
-        for i in range(3):
-            time.sleep(1)
-            if p.poll() is not None:
-                break
-            if subprocess.call(['xwininfo', '-name', window_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0:
-                break
-        if p.returncode is None:
-            p.terminate()
-        elif p.returncode:
-            sys.stdout.write('SKIP (app)\n')
-            return
+        if self.standalone:
+            p = popen(self.args, cwd=self.cwd)
+            for i in range(3):
+                time.sleep(1)
+                if p.poll() is not None:
+                    break
+            if p.returncode is None:
+                p.terminate()
+            elif p.returncode:
+                sys.stdout.write('SKIP (app)\n')
+                return
 
-        ref_image = os.path.join(self.results, self.name + '.ref.png')
         p = popen(self.args, env=env, cwd=self.cwd)
         try:
             for i in range(5):
                 time.sleep(1)
                 if p.poll() is not None:
                     break
-                if subprocess.call(['xwininfo', '-name', window_name], stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0:
-                    break
-
-            if p.returncode is None:
-                subprocess.call("xwd -name '%s' | xwdtopnm | pnmtopng > '%s'" % (window_name, ref_image), shell=True)
         finally:
             if p.returncode is None:
                 p.terminate()
                 p.wait()
             elif p.returncode:
-                sys.stdout.write('FAIL (trace)\n')
+                if self.standalone:
+                    sys.stdout.write('FAIL (trace)\n')
+                else:
+                    sys.stdout.write('SKIP (app)\n')
                 return
 
+        if not os.path.isfile(trace):
+            sys.stdout.write('SKIP (no trace)\n')
+            return
         p = popen([self._get_build_path('tracedump'), trace], stdout=subprocess.PIPE)
         call_re = re.compile('^([0-9]+) (\w+)\(')
         swapbuffers = 0
@@ -204,44 +273,39 @@ class TestCase:
         else:
             args += ['-sb']
             frames = flushes
-        if os.path.exists(ref_image) and frames < 10:
-            snapshot_prefix = os.path.join(self.results, self.name + '.')
-            args += ['-s', snapshot_prefix]
-        else:
-            snapshot_prefix = None
+        args += ['-s', src_prefix]
         args += [trace]
         p = popen(args, stdout=subprocess.PIPE)
         image_re = re.compile('^Wrote (.*\.png)$')
-        image = None
+        images = []
         for line in p.stdout:
             line = line.rstrip()
             mo = image_re.match(line)
             if mo:
                 image = mo.group(1)
+                if image.startswith(src_prefix):
+                    image = image[len(src_prefix):]
+                    images.append(image)
         p.wait()
         if p.returncode != 0:
             sys.stdout.write('FAIL (glretrace)\n')
             return
 
-        if image:
-            delta_image = os.path.join(self.results, self.name + '.diff.png')
-            p = popen([
-                'compare',
-                '-alpha', 'opaque',
-                '-metric', 'AE',
-                '-fuzz', '5%',
-                '-dissimilarity-threshold', '1',
-                ref_image, image, delta_image
-            ], stderr = subprocess.PIPE)
-            _, stderr = p.communicate()
-
-            try:
-                ae = int(stderr)
-            except ValueError:
-                ae = 9999
-
-            if ae:
-                report.add_snapshot(ref_image, image, delta_image)
+        for image in images:
+            ref_image = ref_prefix + image
+            src_image = src_prefix + image
+            diff_image = diff_prefix + image
+            
+            if not os.path.isfile(ref_image):
+                continue
+            assert os.path.isfile(src_image)
+
+            comparer = Comparer(ref_image, src_image)
+            match = comparer.ae()
+            sys.stdout.write('%s: %s bits\n' % (image, comparer.precision()))
+            if not match:
+                comparer.write_diff(diff_image)
+                report.add_snapshot(ref_image, src_image, diff_image)
                 sys.stdout.write('FAIL (snapshot)\n')
                 return