1#!/usr/bin/env python 2 3# Runs a subsetting test suite. Compares the results of subsetting via harfbuzz 4# to subsetting via fonttools. 5 6from __future__ import print_function, division, absolute_import 7 8import io 9from difflib import unified_diff 10import os 11import re 12import subprocess 13import sys 14import tempfile 15 16from subset_test_suite import SubsetTestSuite 17 18# https://stackoverflow.com/a/377028 19def which (program): 20 def is_exe (fpath): 21 return os.path.isfile (fpath) and os.access (fpath, os.X_OK) 22 23 fpath, _ = os.path.split (program) 24 if fpath: 25 if is_exe (program): 26 return program 27 else: 28 for path in os.environ["PATH"].split (os.pathsep): 29 exe_file = os.path.join (path, program) 30 if is_exe (exe_file): 31 return exe_file 32 33 return None 34 35fonttools = which ("fonttools") 36ots_sanitize = which ("ots-sanitize") 37 38if not fonttools: 39 print ("fonttools is not present, skipping test.") 40 sys.exit (77) 41 42def cmd(command): 43 p = subprocess.Popen ( 44 command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 45 (stdoutdata, stderrdata) = p.communicate () 46 print (stderrdata, end="") # file=sys.stderr 47 return stdoutdata, p.returncode 48 49def read_binary (file_path): 50 with open (file_path, 'rb') as f: 51 return f.read () 52 53def fail_test (test, cli_args, message): 54 print ('ERROR: %s' % message) 55 print ('Test State:') 56 print (' test.font_path %s' % os.path.abspath (test.font_path)) 57 print (' test.profile_path %s' % os.path.abspath (test.profile_path)) 58 print (' test.unicodes %s' % test.unicodes ()) 59 expected_file = os.path.join(test_suite.get_output_directory (), 60 test.get_font_name ()) 61 print (' expected_file %s' % os.path.abspath (expected_file)) 62 return 1 63 64def run_test (test, should_check_ots): 65 out_file = os.path.join (tempfile.mkdtemp (), test.get_font_name () + '-subset' + test.get_font_extension ()) 66 cli_args = [hb_subset, 67 "--font-file=" + test.font_path, 68 "--output-file=" + out_file, 69 "--unicodes=%s" % test.unicodes (), 70 "--drop-tables+=DSIG,GPOS,GSUB,GDEF,gvar,avar,MVAR,HVAR"] 71 cli_args.extend (test.get_profile_flags ()) 72 print (' '.join (cli_args)) 73 _, return_code = cmd (cli_args) 74 75 if return_code: 76 return fail_test (test, cli_args, "%s returned %d" % (' '.join (cli_args), return_code)) 77 78 expected_ttx, return_code = run_ttx (os.path.join (test_suite.get_output_directory (), 79 test.get_font_name ())) 80 if return_code: 81 return fail_test (test, cli_args, "ttx (expected) returned %d" % (return_code)) 82 83 actual_ttx, return_code = run_ttx (out_file) 84 if return_code: 85 return fail_test (test, cli_args, "ttx (actual) returned %d" % (return_code)) 86 87 print ("stripping checksums.") 88 expected_ttx = strip_check_sum (expected_ttx) 89 actual_ttx = strip_check_sum (actual_ttx) 90 91 if not actual_ttx == expected_ttx: 92 for line in unified_diff (expected_ttx.splitlines (1), actual_ttx.splitlines (1)): 93 sys.stdout.write (line) 94 sys.stdout.flush () 95 return fail_test (test, cli_args, 'ttx for expected and actual does not match.') 96 97 if should_check_ots: 98 print ("Checking output with ots-sanitize.") 99 if not check_ots (out_file): 100 return fail_test (test, cli_args, 'ots for subsetted file fails.') 101 102 return 0 103 104def run_ttx (file): 105 print ("fonttools ttx %s" % file) 106 return cmd ([fonttools, "ttx", "-q", "-o-", file]) 107 108def strip_check_sum (ttx_string): 109 return re.sub ('checkSumAdjustment value=["]0x([0-9a-fA-F])+["]', 110 'checkSumAdjustment value="0x00000000"', 111 ttx_string.decode ("utf-8"), count=1) 112 113def has_ots (): 114 if not ots_sanitize: 115 print("OTS is not present, skipping all ots checks.") 116 return False 117 return True 118 119def check_ots (path): 120 ots_report, returncode = cmd ([ots_sanitize, path]) 121 if returncode: 122 print("OTS Failure: %s" % ots_report); 123 return False 124 return True 125 126args = sys.argv[1:] 127if not args or sys.argv[1].find ('hb-subset') == -1 or not os.path.exists (sys.argv[1]): 128 print ("First argument does not seem to point to usable hb-subset.") 129 sys.exit (1) 130hb_subset, args = args[0], args[1:] 131 132if not len (args): 133 print ("No tests supplied.") 134 sys.exit (1) 135 136has_ots = has_ots() 137 138fails = 0 139for path in args: 140 with io.open (path, mode="r", encoding="utf-8") as f: 141 print ("Running tests in " + path) 142 test_suite = SubsetTestSuite (path, f.read ()) 143 for test in test_suite.tests (): 144 fails += run_test (test, has_ots) 145 146if fails != 0: 147 print (str (fails) + " test(s) failed.") 148 sys.exit(1) 149else: 150 print ("All tests passed.") 151