1#!/usr/bin/env python 2 3from __future__ import print_function, division, absolute_import 4 5import sys, os, subprocess, tempfile, threading 6 7 8def which(program): 9 # https://stackoverflow.com/a/377028 10 def is_exe(fpath): 11 return os.path.isfile(fpath) and os.access(fpath, os.X_OK) 12 13 fpath, _ = os.path.split(program) 14 if fpath: 15 if is_exe(program): 16 return program 17 else: 18 for path in os.environ["PATH"].split(os.pathsep): 19 exe_file = os.path.join(path, program) 20 if is_exe(exe_file): 21 return exe_file 22 23 return None 24 25 26def cmd(command): 27 # https://stackoverflow.com/a/4408409 28 # https://stackoverflow.com/a/10012262 29 with tempfile.TemporaryFile() as tempf: 30 p = subprocess.Popen (command, stderr=tempf) 31 is_killed = {'value': False} 32 33 def timeout(p, is_killed): 34 is_killed['value'] = True 35 p.kill() 36 timeout_seconds = int (os.environ.get ("HB_TEST_SUBSET_FUZZER_TIMEOUT", "8")) 37 timer = threading.Timer (timeout_seconds, timeout, [p, is_killed]) 38 39 try: 40 timer.start() 41 p.wait () 42 tempf.seek (0) 43 text = tempf.read () 44 45 #TODO: Detect debug mode with a better way 46 is_debug_mode = b"SANITIZE" in text 47 48 text = "" if is_debug_mode else text.decode ("utf-8").strip () 49 returncode = p.returncode 50 finally: 51 timer.cancel() 52 53 if is_killed['value']: 54 text = 'error: timeout, ' + text 55 returncode = 1 56 57 return text, returncode 58 59 60srcdir = os.environ.get ("srcdir", ".") 61EXEEXT = os.environ.get ("EXEEXT", "") 62top_builddir = os.environ.get ("top_builddir", ".") 63hb_subset_fuzzer = os.path.join (top_builddir, "hb-subset-fuzzer" + EXEEXT) 64 65if not os.path.exists (hb_subset_fuzzer): 66 if len (sys.argv) < 2 or not os.path.exists (sys.argv[1]): 67 print ("""Failed to find hb-subset-fuzzer binary automatically, 68please provide it as the first argument to the tool""") 69 sys.exit (1) 70 71 hb_subset_fuzzer = sys.argv[1] 72 73print ('hb_subset_fuzzer:', hb_subset_fuzzer) 74fails = 0 75 76libtool = os.environ.get('LIBTOOL') 77valgrind = None 78if os.environ.get('RUN_VALGRIND', ''): 79 valgrind = which ('valgrind') 80 if valgrind is None: 81 print ("""Valgrind requested but not found.""") 82 sys.exit (1) 83 if libtool is None: 84 print ("""Valgrind support is currently autotools only and needs libtool but not found.""") 85 86 87def run_dir (parent_path): 88 global fails 89 for file in os.listdir (parent_path): 90 path = os.path.join(parent_path, file) 91 # TODO: Run on all the fonts not just subset related ones 92 if "subset" not in path: continue 93 94 print ("running subset fuzzer against %s" % path) 95 if valgrind: 96 text, returncode = cmd (libtool.split(' ') + ['--mode=execute', valgrind + ' --leak-check=full --show-leak-kinds=all --error-exitcode=1', '--', hb_subset_fuzzer, path]) 97 else: 98 text, returncode = cmd ([hb_subset_fuzzer, path]) 99 if 'error' in text: 100 returncode = 1 101 102 if (not valgrind or returncode) and text.strip (): 103 print (text) 104 105 if returncode != 0: 106 print ("failed for %s" % path) 107 fails = fails + 1 108 109 110run_dir (os.path.join (srcdir, "..", "subset", "data", "fonts")) 111run_dir (os.path.join (srcdir, "fonts")) 112 113if fails: 114 print ("%i subset fuzzer related tests failed." % fails) 115 sys.exit (1) 116