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