1*c8dee2aaSAndroid Build Coastguard Worker#!/usr/bin/env python 2*c8dee2aaSAndroid Build Coastguard Worker# Copyright 2019 Google LLC. 3*c8dee2aaSAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 4*c8dee2aaSAndroid Build Coastguard Worker# found in the LICENSE file. 5*c8dee2aaSAndroid Build Coastguard Worker 6*c8dee2aaSAndroid Build Coastguard Worker''' 7*c8dee2aaSAndroid Build Coastguard WorkerThis tool compares the PDF output of Skia's DM tool of two commits. 8*c8dee2aaSAndroid Build Coastguard Worker 9*c8dee2aaSAndroid Build Coastguard WorkerIt relies on pdfium_test being in the PATH. To build: 10*c8dee2aaSAndroid Build Coastguard Worker 11*c8dee2aaSAndroid Build Coastguard Workermkdir -p ~/src/pdfium 12*c8dee2aaSAndroid Build Coastguard Workercd ~/src/pdfium 13*c8dee2aaSAndroid Build Coastguard Workergclient config --unmanaged https://pdfium.googlesource.com/pdfium.git 14*c8dee2aaSAndroid Build Coastguard Workergclient sync 15*c8dee2aaSAndroid Build Coastguard Workercd pdfium 16*c8dee2aaSAndroid Build Coastguard Workergn gen out/default --args='pdf_enable_xfa=false pdf_enable_v8=false pdf_is_standalone=true' 17*c8dee2aaSAndroid Build Coastguard Workerninja -C out/default pdfium_test 18*c8dee2aaSAndroid Build Coastguard Workercp out/default/pdfium_test ~/bin/ 19*c8dee2aaSAndroid Build Coastguard Worker''' 20*c8dee2aaSAndroid Build Coastguard Worker 21*c8dee2aaSAndroid Build Coastguard Workerimport os 22*c8dee2aaSAndroid Build Coastguard Workerimport re 23*c8dee2aaSAndroid Build Coastguard Workerimport shutil 24*c8dee2aaSAndroid Build Coastguard Workerimport subprocess 25*c8dee2aaSAndroid Build Coastguard Workerimport sys 26*c8dee2aaSAndroid Build Coastguard Workerimport tempfile 27*c8dee2aaSAndroid Build Coastguard Workerimport threading 28*c8dee2aaSAndroid Build Coastguard Worker 29*c8dee2aaSAndroid Build Coastguard WorkerEXTRA_GN_ARGS = os.environ.get('PDF_COMPARISON_GN_ARGS', '') 30*c8dee2aaSAndroid Build Coastguard Worker 31*c8dee2aaSAndroid Build Coastguard WorkerREFERENCE_BACKEND = 'gl' if 'PDF_COMPARISON_NOGPU' not in os.environ else '8888' 32*c8dee2aaSAndroid Build Coastguard Worker 33*c8dee2aaSAndroid Build Coastguard WorkerDPI = float(os.environ.get('PDF_COMPARISON_DPI', 72)) 34*c8dee2aaSAndroid Build Coastguard Worker 35*c8dee2aaSAndroid Build Coastguard WorkerPDF_CONFIG = 'pdf' if 'PDF_COMPARISON_300DPI' not in os.environ else 'pdf300' 36*c8dee2aaSAndroid Build Coastguard Worker 37*c8dee2aaSAndroid Build Coastguard WorkerBAD_TESTS = [ 38*c8dee2aaSAndroid Build Coastguard Worker 'image-cacherator-from-picture', 39*c8dee2aaSAndroid Build Coastguard Worker 'image-cacherator-from-raster', 40*c8dee2aaSAndroid Build Coastguard Worker 'mixershader', 41*c8dee2aaSAndroid Build Coastguard Worker 'shadermaskfilter_image', 42*c8dee2aaSAndroid Build Coastguard Worker 'tilemode_decal', 43*c8dee2aaSAndroid Build Coastguard Worker] 44*c8dee2aaSAndroid Build Coastguard Worker 45*c8dee2aaSAndroid Build Coastguard WorkerNINJA = 'ninja' 46*c8dee2aaSAndroid Build Coastguard Worker 47*c8dee2aaSAndroid Build Coastguard WorkerPDFIUM_TEST = 'pdfium_test' 48*c8dee2aaSAndroid Build Coastguard Worker 49*c8dee2aaSAndroid Build Coastguard WorkerNUM_THREADS = int(os.environ.get('PDF_COMPARISON_THREADS', 40)) 50*c8dee2aaSAndroid Build Coastguard Worker 51*c8dee2aaSAndroid Build Coastguard WorkerSOURCES = ['gm'] 52*c8dee2aaSAndroid Build Coastguard Worker 53*c8dee2aaSAndroid Build Coastguard Workerdef test_exe(cmd): 54*c8dee2aaSAndroid Build Coastguard Worker with open(os.devnull, 'w') as o: 55*c8dee2aaSAndroid Build Coastguard Worker try: 56*c8dee2aaSAndroid Build Coastguard Worker subprocess.call([cmd], stdout=o, stderr=o) 57*c8dee2aaSAndroid Build Coastguard Worker except OSError: 58*c8dee2aaSAndroid Build Coastguard Worker return False 59*c8dee2aaSAndroid Build Coastguard Worker return True 60*c8dee2aaSAndroid Build Coastguard Worker 61*c8dee2aaSAndroid Build Coastguard Workerdef print_cmd(cmd, o): 62*c8dee2aaSAndroid Build Coastguard Worker m = re.compile('[^A-Za-z0-9_./-]') 63*c8dee2aaSAndroid Build Coastguard Worker o.write('+ ') 64*c8dee2aaSAndroid Build Coastguard Worker for c in cmd: 65*c8dee2aaSAndroid Build Coastguard Worker if m.search(c) is not None: 66*c8dee2aaSAndroid Build Coastguard Worker o.write(repr(c) + ' ') 67*c8dee2aaSAndroid Build Coastguard Worker else: 68*c8dee2aaSAndroid Build Coastguard Worker o.write(c + ' ') 69*c8dee2aaSAndroid Build Coastguard Worker o.write('\n') 70*c8dee2aaSAndroid Build Coastguard Worker o.flush() 71*c8dee2aaSAndroid Build Coastguard Worker 72*c8dee2aaSAndroid Build Coastguard Workerdef check_call(cmd, **kwargs): 73*c8dee2aaSAndroid Build Coastguard Worker print_cmd(cmd, sys.stdout) 74*c8dee2aaSAndroid Build Coastguard Worker return subprocess.check_call(cmd, **kwargs) 75*c8dee2aaSAndroid Build Coastguard Worker 76*c8dee2aaSAndroid Build Coastguard Workerdef check_output(cmd, **kwargs): 77*c8dee2aaSAndroid Build Coastguard Worker print_cmd(cmd, sys.stdout) 78*c8dee2aaSAndroid Build Coastguard Worker return subprocess.check_output(cmd, **kwargs) 79*c8dee2aaSAndroid Build Coastguard Worker 80*c8dee2aaSAndroid Build Coastguard Workerdef remove(*paths): 81*c8dee2aaSAndroid Build Coastguard Worker for path in paths: 82*c8dee2aaSAndroid Build Coastguard Worker os.remove(path) 83*c8dee2aaSAndroid Build Coastguard Worker 84*c8dee2aaSAndroid Build Coastguard Workerdef timeout(deadline, cmd): 85*c8dee2aaSAndroid Build Coastguard Worker #print_cmd(cmd, sys.stdout) 86*c8dee2aaSAndroid Build Coastguard Worker with open(os.devnull, 'w') as o: 87*c8dee2aaSAndroid Build Coastguard Worker proc = subprocess.Popen(cmd, stdout=o, stderr=subprocess.STDOUT) 88*c8dee2aaSAndroid Build Coastguard Worker timer = threading.Timer(deadline, proc.terminate) 89*c8dee2aaSAndroid Build Coastguard Worker timer.start() 90*c8dee2aaSAndroid Build Coastguard Worker proc.wait() 91*c8dee2aaSAndroid Build Coastguard Worker timer.cancel() 92*c8dee2aaSAndroid Build Coastguard Worker return proc.returncode 93*c8dee2aaSAndroid Build Coastguard Worker 94*c8dee2aaSAndroid Build Coastguard Workerdef is_same(path1, path2): 95*c8dee2aaSAndroid Build Coastguard Worker if not os.path.isfile(path1) or not os.path.isfile(path2): 96*c8dee2aaSAndroid Build Coastguard Worker return os.path.isfile(path1) == os.path.isfile(path2) 97*c8dee2aaSAndroid Build Coastguard Worker with open(path1, 'rb') as f1: 98*c8dee2aaSAndroid Build Coastguard Worker with open(path2, 'rb') as f2: 99*c8dee2aaSAndroid Build Coastguard Worker while True: 100*c8dee2aaSAndroid Build Coastguard Worker c1, c2 = f1.read(4096), f2.read(4096) 101*c8dee2aaSAndroid Build Coastguard Worker if c1 != c2: 102*c8dee2aaSAndroid Build Coastguard Worker return False 103*c8dee2aaSAndroid Build Coastguard Worker if not c1: 104*c8dee2aaSAndroid Build Coastguard Worker return True 105*c8dee2aaSAndroid Build Coastguard Worker 106*c8dee2aaSAndroid Build Coastguard Worker 107*c8dee2aaSAndroid Build Coastguard Workerdef getfilesoftype(directory, ending): 108*c8dee2aaSAndroid Build Coastguard Worker for dirpath, _, filenames in os.walk(directory): 109*c8dee2aaSAndroid Build Coastguard Worker rp = os.path.normpath(os.path.relpath(dirpath, directory)) 110*c8dee2aaSAndroid Build Coastguard Worker for f in filenames: 111*c8dee2aaSAndroid Build Coastguard Worker if f.endswith(ending): 112*c8dee2aaSAndroid Build Coastguard Worker yield os.path.join(rp, f) 113*c8dee2aaSAndroid Build Coastguard Worker 114*c8dee2aaSAndroid Build Coastguard Workerdef get_common_paths(dirs, ext): 115*c8dee2aaSAndroid Build Coastguard Worker return sorted(list( 116*c8dee2aaSAndroid Build Coastguard Worker set.intersection(*(set(getfilesoftype(d, ext)) for d in dirs)))) 117*c8dee2aaSAndroid Build Coastguard Worker 118*c8dee2aaSAndroid Build Coastguard Workerdef printable_path(d): 119*c8dee2aaSAndroid Build Coastguard Worker if 'TMPDIR' in os.environ: 120*c8dee2aaSAndroid Build Coastguard Worker return d.replace(os.path.normpath(os.environ['TMPDIR']) + '/', '$TMPDIR/') 121*c8dee2aaSAndroid Build Coastguard Worker return d 122*c8dee2aaSAndroid Build Coastguard Worker 123*c8dee2aaSAndroid Build Coastguard Workerdef spawn(cmd): 124*c8dee2aaSAndroid Build Coastguard Worker with open(os.devnull, 'w') as o: 125*c8dee2aaSAndroid Build Coastguard Worker subprocess.Popen(cmd, stdout=o, stderr=o) 126*c8dee2aaSAndroid Build Coastguard Worker 127*c8dee2aaSAndroid Build Coastguard Workerdef sysopen(arg): 128*c8dee2aaSAndroid Build Coastguard Worker plat = sys.platform 129*c8dee2aaSAndroid Build Coastguard Worker if plat.startswith('darwin'): 130*c8dee2aaSAndroid Build Coastguard Worker spawn(["open", arg]) 131*c8dee2aaSAndroid Build Coastguard Worker elif plat.startswith('win'): 132*c8dee2aaSAndroid Build Coastguard Worker # pylint: disable=no-member 133*c8dee2aaSAndroid Build Coastguard Worker os.startfile(arg) 134*c8dee2aaSAndroid Build Coastguard Worker else: 135*c8dee2aaSAndroid Build Coastguard Worker spawn(["xdg-open", arg]) 136*c8dee2aaSAndroid Build Coastguard Worker 137*c8dee2aaSAndroid Build Coastguard WorkerHTML_HEAD = ''' 138*c8dee2aaSAndroid Build Coastguard Worker<!DOCTYPE html> 139*c8dee2aaSAndroid Build Coastguard Worker<html lang="en"> 140*c8dee2aaSAndroid Build Coastguard Worker<head> 141*c8dee2aaSAndroid Build Coastguard Worker<meta charset="utf-8"> 142*c8dee2aaSAndroid Build Coastguard Worker<title>DIFF</title> 143*c8dee2aaSAndroid Build Coastguard Worker<style> 144*c8dee2aaSAndroid Build Coastguard Workerbody{ 145*c8dee2aaSAndroid Build Coastguard Workerbackground-size:16px 16px; 146*c8dee2aaSAndroid Build Coastguard Workerbackground-color:rgb(230,230,230); 147*c8dee2aaSAndroid Build Coastguard Workerbackground-image: 148*c8dee2aaSAndroid Build Coastguard Workerlinear-gradient(45deg,rgba(255,255,255,.2) 25%,transparent 25%,transparent 50%, 149*c8dee2aaSAndroid Build Coastguard Workerrgba(255,255,255,.2) 50%,rgba(255,255,255,.2) 75%,transparent 75%,transparent)} 150*c8dee2aaSAndroid Build Coastguard Workerdiv.r{position:relative;left:0;top:0} 151*c8dee2aaSAndroid Build Coastguard Workertable{table-layout:fixed;width:100%} 152*c8dee2aaSAndroid Build Coastguard Workerimg.s{max-width:100%;max-height:320;left:0;top:0} 153*c8dee2aaSAndroid Build Coastguard Workerimg.b{position:absolute;mix-blend-mode:difference} 154*c8dee2aaSAndroid Build Coastguard Worker</style> 155*c8dee2aaSAndroid Build Coastguard Worker<script> 156*c8dee2aaSAndroid Build Coastguard Workerfunction r(c,e,n,g){ 157*c8dee2aaSAndroid Build Coastguard Workert=document.getElementById("t"); 158*c8dee2aaSAndroid Build Coastguard Workerfunction ce(t){return document.createElement(t);} 159*c8dee2aaSAndroid Build Coastguard Workerfunction ct(n){return document.createTextNode(n);} 160*c8dee2aaSAndroid Build Coastguard Workerfunction ac(u,v){u.appendChild(v);} 161*c8dee2aaSAndroid Build Coastguard Workerfunction cn(u,v){u.className=v;} 162*c8dee2aaSAndroid Build Coastguard Workerfunction it(s){ td=ce("td"); a=ce("a"); a.href=s; img=ce("img"); img.src=s; 163*c8dee2aaSAndroid Build Coastguard Worker cn(img,"s"); ac(a,img); ac(td,a); return td; } 164*c8dee2aaSAndroid Build Coastguard Workertr=ce("tr"); td=ce("td"); td.colSpan="4"; ac(td, ct(n)); ac(tr,td); 165*c8dee2aaSAndroid Build Coastguard Workerac(t,tr); tr=ce("tr"); td=ce("td"); dv=ce("div"); cn(dv,"r"); 166*c8dee2aaSAndroid Build Coastguard Workerimg=ce("img"); img.src=c; cn(img,"s"); ac(dv,img); img=ce("img"); 167*c8dee2aaSAndroid Build Coastguard Workerimg.src=e; cn(img,"s b"); ac(dv,img); ac(td,dv); ac(tr,td); 168*c8dee2aaSAndroid Build Coastguard Workerac(tr,it(c)); ac(tr,it(e)); ac(tr,it(g)); ac(t,tr); } 169*c8dee2aaSAndroid Build Coastguard Workerdocument.addEventListener('DOMContentLoaded',function(){ 170*c8dee2aaSAndroid Build Coastguard Worker''' 171*c8dee2aaSAndroid Build Coastguard Worker 172*c8dee2aaSAndroid Build Coastguard WorkerHTML_TAIL = ''']; 173*c8dee2aaSAndroid Build Coastguard Workerfor(i=0;i<z.length;i++){ 174*c8dee2aaSAndroid Build Coastguard Workerr(c+z[i][0],e+z[i][0],z[i][2],c+z[i][1]);}},false); 175*c8dee2aaSAndroid Build Coastguard Worker</script></head><body><table id="t"> 176*c8dee2aaSAndroid Build Coastguard Worker<tr><th>BEFORE-AFTER DIFF</th> 177*c8dee2aaSAndroid Build Coastguard Worker<th>BEFORE</th><th>AFTER</th> 178*c8dee2aaSAndroid Build Coastguard Worker<th>REFERENCE</th></tr> 179*c8dee2aaSAndroid Build Coastguard Worker</table></body></html>''' 180*c8dee2aaSAndroid Build Coastguard Worker 181*c8dee2aaSAndroid Build Coastguard Workerdef shard(fn, arglist): 182*c8dee2aaSAndroid Build Coastguard Worker jobs = [[arg for j, arg in enumerate(arglist) if j % NUM_THREADS == i] 183*c8dee2aaSAndroid Build Coastguard Worker for i in range(NUM_THREADS)] 184*c8dee2aaSAndroid Build Coastguard Worker results = [] 185*c8dee2aaSAndroid Build Coastguard Worker def do_shard(*args): 186*c8dee2aaSAndroid Build Coastguard Worker for arg in args: 187*c8dee2aaSAndroid Build Coastguard Worker results.append(fn(arg)) 188*c8dee2aaSAndroid Build Coastguard Worker thread_list = [] 189*c8dee2aaSAndroid Build Coastguard Worker for job in jobs: 190*c8dee2aaSAndroid Build Coastguard Worker t = threading.Thread(target=do_shard, args=job) 191*c8dee2aaSAndroid Build Coastguard Worker t.start() 192*c8dee2aaSAndroid Build Coastguard Worker thread_list += [t] 193*c8dee2aaSAndroid Build Coastguard Worker for t in thread_list: 194*c8dee2aaSAndroid Build Coastguard Worker t.join() 195*c8dee2aaSAndroid Build Coastguard Worker return results 196*c8dee2aaSAndroid Build Coastguard Worker 197*c8dee2aaSAndroid Build Coastguard Workerdef shardsum(fn, arglist): 198*c8dee2aaSAndroid Build Coastguard Worker 'return the number of True results returned by fn(arg) for arg in arglist.' 199*c8dee2aaSAndroid Build Coastguard Worker return sum(1 for result in shard(fn, arglist) if result) 200*c8dee2aaSAndroid Build Coastguard Worker 201*c8dee2aaSAndroid Build Coastguard Workerdef checkout_worktree(checkoutable): 202*c8dee2aaSAndroid Build Coastguard Worker directory = os.path.join(tempfile.gettempdir(), 'skpdf_control_tree') 203*c8dee2aaSAndroid Build Coastguard Worker commit = check_output(['git', 'rev-parse', checkoutable]).strip() 204*c8dee2aaSAndroid Build Coastguard Worker if os.path.isdir(directory): 205*c8dee2aaSAndroid Build Coastguard Worker try: 206*c8dee2aaSAndroid Build Coastguard Worker check_call(['git', 'checkout', commit], cwd=directory) 207*c8dee2aaSAndroid Build Coastguard Worker return directory 208*c8dee2aaSAndroid Build Coastguard Worker except subprocess.CalledProcessError: 209*c8dee2aaSAndroid Build Coastguard Worker shutil.rmtree(directory) 210*c8dee2aaSAndroid Build Coastguard Worker check_call(['git', 'worktree', 'add', '-f', directory, commit]) 211*c8dee2aaSAndroid Build Coastguard Worker return directory 212*c8dee2aaSAndroid Build Coastguard Worker 213*c8dee2aaSAndroid Build Coastguard Workerdef build_skia(directory, executable): 214*c8dee2aaSAndroid Build Coastguard Worker args = ('--args=is_debug=false' 215*c8dee2aaSAndroid Build Coastguard Worker ' extra_cflags=["-DSK_PDF_LESS_COMPRESSION",' 216*c8dee2aaSAndroid Build Coastguard Worker ' "-DSK_PDF_BASE85_BINARY"] ') 217*c8dee2aaSAndroid Build Coastguard Worker if test_exe('ccache'): 218*c8dee2aaSAndroid Build Coastguard Worker args += ' cc_wrapper="ccache"' 219*c8dee2aaSAndroid Build Coastguard Worker args += EXTRA_GN_ARGS 220*c8dee2aaSAndroid Build Coastguard Worker build_dir = directory + '/out/pdftest' 221*c8dee2aaSAndroid Build Coastguard Worker check_call([sys.executable, 'bin/sync'], cwd=directory) 222*c8dee2aaSAndroid Build Coastguard Worker check_call([directory + '/bin/gn', 'gen', 'out/pdftest', args], 223*c8dee2aaSAndroid Build Coastguard Worker cwd=directory) 224*c8dee2aaSAndroid Build Coastguard Worker check_call([NINJA, executable], cwd=build_dir) 225*c8dee2aaSAndroid Build Coastguard Worker return os.path.join(build_dir, executable) 226*c8dee2aaSAndroid Build Coastguard Worker 227*c8dee2aaSAndroid Build Coastguard Workerdef build_and_run_dm(directory, data_dir): 228*c8dee2aaSAndroid Build Coastguard Worker dm = build_skia(directory, 'dm') 229*c8dee2aaSAndroid Build Coastguard Worker for source in SOURCES: 230*c8dee2aaSAndroid Build Coastguard Worker os.makedirs(os.path.join(data_dir, PDF_CONFIG, source)) 231*c8dee2aaSAndroid Build Coastguard Worker dm_args = [dm, '--src'] + SOURCES + ['--config', PDF_CONFIG, '-w', data_dir] 232*c8dee2aaSAndroid Build Coastguard Worker if BAD_TESTS: 233*c8dee2aaSAndroid Build Coastguard Worker dm_args += ['-m'] + ['~^%s$' % x for x in BAD_TESTS] 234*c8dee2aaSAndroid Build Coastguard Worker check_call(dm_args, cwd=directory) 235*c8dee2aaSAndroid Build Coastguard Worker return dm 236*c8dee2aaSAndroid Build Coastguard Worker 237*c8dee2aaSAndroid Build Coastguard Workerdef rasterize(path): 238*c8dee2aaSAndroid Build Coastguard Worker ret = timeout(30, [PDFIUM_TEST, '--png', '--scale=%g' % (DPI / 72.0), path]) 239*c8dee2aaSAndroid Build Coastguard Worker if ret != 0: 240*c8dee2aaSAndroid Build Coastguard Worker sys.stdout.write( 241*c8dee2aaSAndroid Build Coastguard Worker '\nTIMEOUT OR ERROR [%d] "%s"\n' % (ret, printable_path(path))) 242*c8dee2aaSAndroid Build Coastguard Worker return 243*c8dee2aaSAndroid Build Coastguard Worker assert os.path.isfile(path + '.0.png') 244*c8dee2aaSAndroid Build Coastguard Worker 245*c8dee2aaSAndroid Build Coastguard Workerdef main(control_commitish): 246*c8dee2aaSAndroid Build Coastguard Worker assert os.pardir == '..' and '/' in [os.sep, os.altsep] 247*c8dee2aaSAndroid Build Coastguard Worker assert test_exe(NINJA) 248*c8dee2aaSAndroid Build Coastguard Worker assert test_exe(PDFIUM_TEST) 249*c8dee2aaSAndroid Build Coastguard Worker os.chdir(os.path.dirname(__file__) + '/../..') 250*c8dee2aaSAndroid Build Coastguard Worker control_worktree = checkout_worktree(control_commitish) 251*c8dee2aaSAndroid Build Coastguard Worker tmpdir = tempfile.mkdtemp(prefix='skpdf_') 252*c8dee2aaSAndroid Build Coastguard Worker exp = tmpdir + '/experim' 253*c8dee2aaSAndroid Build Coastguard Worker con = tmpdir + '/control' 254*c8dee2aaSAndroid Build Coastguard Worker build_and_run_dm(os.curdir, exp) 255*c8dee2aaSAndroid Build Coastguard Worker dm = build_and_run_dm(control_worktree, con) 256*c8dee2aaSAndroid Build Coastguard Worker image_diff_metric = build_skia(control_worktree, 'image_diff_metric') 257*c8dee2aaSAndroid Build Coastguard Worker 258*c8dee2aaSAndroid Build Coastguard Worker out = sys.stdout 259*c8dee2aaSAndroid Build Coastguard Worker common_paths = get_common_paths([con, exp], '.pdf') 260*c8dee2aaSAndroid Build Coastguard Worker out.write('\nNumber of PDFs: %d\n\n' % len(common_paths)) 261*c8dee2aaSAndroid Build Coastguard Worker def compare_identical(path): 262*c8dee2aaSAndroid Build Coastguard Worker cpath, epath = (os.path.join(x, path) for x in (con, exp)) 263*c8dee2aaSAndroid Build Coastguard Worker if is_same(cpath, epath): 264*c8dee2aaSAndroid Build Coastguard Worker remove(cpath, epath) 265*c8dee2aaSAndroid Build Coastguard Worker return True 266*c8dee2aaSAndroid Build Coastguard Worker return False 267*c8dee2aaSAndroid Build Coastguard Worker identical_count = shardsum(compare_identical, common_paths) 268*c8dee2aaSAndroid Build Coastguard Worker out.write('Number of identical PDFs: %d\n\n' % identical_count) 269*c8dee2aaSAndroid Build Coastguard Worker 270*c8dee2aaSAndroid Build Coastguard Worker differing_paths = get_common_paths([con, exp], '.pdf') 271*c8dee2aaSAndroid Build Coastguard Worker if not differing_paths: 272*c8dee2aaSAndroid Build Coastguard Worker out.write('All PDFs are the same!\n') 273*c8dee2aaSAndroid Build Coastguard Worker sys.exit(0) 274*c8dee2aaSAndroid Build Coastguard Worker out.write('Number of differing PDFs: %d\n' % len(differing_paths)) 275*c8dee2aaSAndroid Build Coastguard Worker for p in differing_paths: 276*c8dee2aaSAndroid Build Coastguard Worker out.write(' %s\n' % printable_path(tmpdir + '/*/' + p)) 277*c8dee2aaSAndroid Build Coastguard Worker out.write('\n') 278*c8dee2aaSAndroid Build Coastguard Worker shard(rasterize, 279*c8dee2aaSAndroid Build Coastguard Worker [os.path.join(x, p) for p in differing_paths for x in [con, exp]]) 280*c8dee2aaSAndroid Build Coastguard Worker 281*c8dee2aaSAndroid Build Coastguard Worker common_pngs = get_common_paths([con, exp], '.pdf.0.png') 282*c8dee2aaSAndroid Build Coastguard Worker identical_count = shardsum(compare_identical, common_pngs) 283*c8dee2aaSAndroid Build Coastguard Worker out.write('Number of PDFs that rasterize the same: %d\n\n' 284*c8dee2aaSAndroid Build Coastguard Worker % identical_count) 285*c8dee2aaSAndroid Build Coastguard Worker 286*c8dee2aaSAndroid Build Coastguard Worker differing_pngs = get_common_paths([con, exp], '.pdf.0.png') 287*c8dee2aaSAndroid Build Coastguard Worker if not differing_pngs: 288*c8dee2aaSAndroid Build Coastguard Worker out.write('All PDFs rasterize the same!\n') 289*c8dee2aaSAndroid Build Coastguard Worker sys.exit(0) 290*c8dee2aaSAndroid Build Coastguard Worker out.write('Number of PDFs that rasterize differently: %d\n' 291*c8dee2aaSAndroid Build Coastguard Worker % len(differing_pngs)) 292*c8dee2aaSAndroid Build Coastguard Worker for p in differing_pngs: 293*c8dee2aaSAndroid Build Coastguard Worker out.write(' %s\n' % printable_path(tmpdir + '/*/' + p)) 294*c8dee2aaSAndroid Build Coastguard Worker out.write('\n') 295*c8dee2aaSAndroid Build Coastguard Worker 296*c8dee2aaSAndroid Build Coastguard Worker scores = dict() 297*c8dee2aaSAndroid Build Coastguard Worker def compare_differing_pngs(path): 298*c8dee2aaSAndroid Build Coastguard Worker cpath, epath = (os.path.join(x, path) for x in (con, exp)) 299*c8dee2aaSAndroid Build Coastguard Worker s = float(subprocess.check_output([image_diff_metric, cpath, epath])) 300*c8dee2aaSAndroid Build Coastguard Worker indicator = '.' if s < 0.001 else ':' if s < 0.01 else '!' 301*c8dee2aaSAndroid Build Coastguard Worker sys.stdout.write(indicator) 302*c8dee2aaSAndroid Build Coastguard Worker sys.stdout.flush() 303*c8dee2aaSAndroid Build Coastguard Worker scores[path] = s 304*c8dee2aaSAndroid Build Coastguard Worker shard(compare_differing_pngs, differing_pngs) 305*c8dee2aaSAndroid Build Coastguard Worker paths = sorted(scores.iterkeys(), key=lambda p: -scores[p]) 306*c8dee2aaSAndroid Build Coastguard Worker out.write('\n\n') 307*c8dee2aaSAndroid Build Coastguard Worker for p in paths: 308*c8dee2aaSAndroid Build Coastguard Worker pdfpath = printable_path(tmpdir + '/*/' + p.replace('.0.png', '')) 309*c8dee2aaSAndroid Build Coastguard Worker out.write(' %6.4f %s\n' % (scores[p], pdfpath)) 310*c8dee2aaSAndroid Build Coastguard Worker out.write('\n') 311*c8dee2aaSAndroid Build Coastguard Worker 312*c8dee2aaSAndroid Build Coastguard Worker errors = [] 313*c8dee2aaSAndroid Build Coastguard Worker rc = re.compile('^' + PDF_CONFIG + r'/([^/]*)/([^/]*)\.pdf\.0\.png$') 314*c8dee2aaSAndroid Build Coastguard Worker for p in paths: 315*c8dee2aaSAndroid Build Coastguard Worker m = rc.match(p) 316*c8dee2aaSAndroid Build Coastguard Worker assert(m) 317*c8dee2aaSAndroid Build Coastguard Worker source, name = m.groups() 318*c8dee2aaSAndroid Build Coastguard Worker errors.append((source, name, scores[p])) 319*c8dee2aaSAndroid Build Coastguard Worker 320*c8dee2aaSAndroid Build Coastguard Worker for source in SOURCES: 321*c8dee2aaSAndroid Build Coastguard Worker os.makedirs(os.path.join(con, REFERENCE_BACKEND, source)) 322*c8dee2aaSAndroid Build Coastguard Worker dm_args = [dm, '--src'] + SOURCES + [ 323*c8dee2aaSAndroid Build Coastguard Worker '--config', REFERENCE_BACKEND, '-w', con, '-m'] + [ 324*c8dee2aaSAndroid Build Coastguard Worker '^%s$' % name for _, name, _ in errors] 325*c8dee2aaSAndroid Build Coastguard Worker check_call(dm_args, cwd=control_worktree) 326*c8dee2aaSAndroid Build Coastguard Worker 327*c8dee2aaSAndroid Build Coastguard Worker report = tmpdir + '/report.html' 328*c8dee2aaSAndroid Build Coastguard Worker with open(report, 'w') as o: 329*c8dee2aaSAndroid Build Coastguard Worker o.write(HTML_HEAD) 330*c8dee2aaSAndroid Build Coastguard Worker o.write('c="%s/";\n' % os.path.relpath(con, tmpdir)) 331*c8dee2aaSAndroid Build Coastguard Worker o.write('e="%s/";\n' % os.path.relpath(exp, tmpdir)) 332*c8dee2aaSAndroid Build Coastguard Worker o.write('z=[\n') 333*c8dee2aaSAndroid Build Coastguard Worker for source, name, score in errors: 334*c8dee2aaSAndroid Build Coastguard Worker gt = REFERENCE_BACKEND + '/' + source + '/' + name + '.png' 335*c8dee2aaSAndroid Build Coastguard Worker p = '%s/%s/%s.pdf.0.png' % (PDF_CONFIG, source, name) 336*c8dee2aaSAndroid Build Coastguard Worker desc = '%s | %s | %g' % (source, name, score) 337*c8dee2aaSAndroid Build Coastguard Worker o.write('["%s","%s","%s"],\n' % (p, gt, desc)) 338*c8dee2aaSAndroid Build Coastguard Worker o.write(HTML_TAIL) 339*c8dee2aaSAndroid Build Coastguard Worker out.write(printable_path(report) + '\n') 340*c8dee2aaSAndroid Build Coastguard Worker sysopen(report) 341*c8dee2aaSAndroid Build Coastguard Worker 342*c8dee2aaSAndroid Build Coastguard Workerif __name__ == '__main__': 343*c8dee2aaSAndroid Build Coastguard Worker if len(sys.argv) != 2: 344*c8dee2aaSAndroid Build Coastguard Worker USAGE = ('\nusage:\n {0} COMMIT_OR_BRANCH_TO_COMPARE_TO\n\n' 345*c8dee2aaSAndroid Build Coastguard Worker 'e.g.:\n {0} HEAD\nor\n {0} HEAD~1\n\n') 346*c8dee2aaSAndroid Build Coastguard Worker sys.stderr.write(USAGE.format(sys.argv[0])) 347*c8dee2aaSAndroid Build Coastguard Worker sys.exit(1) 348*c8dee2aaSAndroid Build Coastguard Worker main(sys.argv[1]) 349