1#!/usr/bin/env python 2# Copyright 2017 The PDFium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""Verifies exported functions in public/*.h are in fpdf_view_c_api_test.c. 6 7This script gathers a list of functions from public/*.h that contain 8FPDF_EXPORT. It then gathers a list of functions from 9fpdfsdk/fpdf_view_c_api_test.c. It then verifies both lists do not contain 10duplicates, and they match each other. It also checks that the order in 11fpdf_view_c_api_test.c is alphabetical within each section. 12 13""" 14 15import os 16import re 17import sys 18 19 20def _IsValidFunctionName(function, filename): 21 if function.startswith('FPDF'): 22 return True 23 if function.startswith('FSDK_') and filename == 'fpdf_ext.h': 24 return True 25 if function.startswith('FORM_') and filename == 'fpdf_formfill.h': 26 return True 27 return False 28 29 30def _FindFunction(function_snippet, filename): 31 function_split = function_snippet.split('(') 32 assert len(function_split) == 2 33 function = function_split[0] 34 assert _IsValidFunctionName(function, filename) 35 return function 36 37 38def _GetExportsFromHeader(dirname, filename): 39 with open(os.path.join(dirname, filename)) as f: 40 contents = f.readlines() 41 look_for_function_name = False 42 functions = [] 43 for line in contents: 44 if look_for_function_name: 45 look_for_function_name = False 46 split_line = line.rstrip().split(' ') 47 functions.append(_FindFunction(split_line[0], filename)) 48 continue 49 50 if not line.startswith('FPDF_EXPORT '): 51 continue 52 53 # Format should be: FPDF_EXPORT return_type FPDF_CALLCONV 54 split_line = line.rstrip().split(' ') 55 callconv_index = split_line.index('FPDF_CALLCONV') 56 assert callconv_index >= 2 57 if callconv_index + 1 == len(split_line): 58 look_for_function_name = True 59 continue 60 61 functions.append(_FindFunction(split_line[callconv_index + 1], filename)) 62 return functions 63 64 65def _GetFunctionsFromPublicHeaders(src_path): 66 public_path = os.path.join(src_path, 'public') 67 functions = [] 68 for filename in os.listdir(public_path): 69 if filename.endswith('.h'): 70 functions.extend(_GetExportsFromHeader(public_path, filename)) 71 return functions 72 73 74def _CheckSorted(functions): 75 unsorted_functions = set() 76 for i in range(len(functions) - 1): 77 if functions[i] > functions[i + 1]: 78 unsorted_functions.add(functions[i]) 79 unsorted_functions.add(functions[i + 1]) 80 return unsorted_functions 81 82 83def _GetFunctionsFromTest(api_test_path): 84 chk_regex = re.compile('^ CHK\((.*)\);\n$') 85 file_regex = re.compile('^ //.*\.h\n$') 86 with open(api_test_path) as f: 87 contents = f.readlines() 88 functions = [] 89 functions_in_file = [] 90 for line in contents: 91 if (file_regex.match(line)): 92 functions.append(functions_in_file) 93 functions_in_file = [] 94 match = chk_regex.match(line) 95 if match: 96 functions_in_file.append(match.groups()[0]) 97 functions.append(functions_in_file) 98 return functions 99 100 101def _FindDuplicates(functions): 102 return set([f for f in functions if functions.count(f) > 1]) 103 104 105def _CheckAndPrintFailures(failure_list, failure_message): 106 if not failure_list: 107 return True 108 109 print '%s:' % failure_message 110 for f in sorted(failure_list): 111 print f 112 return False 113 114 115def main(): 116 script_abspath = os.path.abspath(__file__) 117 src_path = os.path.dirname(os.path.dirname(os.path.dirname(script_abspath))) 118 public_functions = _GetFunctionsFromPublicHeaders(src_path) 119 120 api_test_relative_path = os.path.join('fpdfsdk', 'fpdf_view_c_api_test.c') 121 api_test_path = os.path.join(src_path, api_test_relative_path) 122 test_functions_per_section = _GetFunctionsFromTest(api_test_path) 123 result = True 124 unsorted_functions = set() 125 for functions in test_functions_per_section: 126 unsorted_functions |= _CheckSorted(functions) 127 check = _CheckAndPrintFailures( 128 unsorted_functions, 129 'Found CHKs that are not in alphabetical order within each section in %s' 130 % api_test_path) 131 result = result and check 132 133 duplicate_public_functions = _FindDuplicates(public_functions) 134 check = _CheckAndPrintFailures(duplicate_public_functions, 135 'Found duplicate functions in public headers') 136 result = result and check 137 138 test_functions = [ 139 function for functions in test_functions_per_section 140 for function in functions 141 ] 142 duplicate_test_functions = _FindDuplicates(test_functions) 143 check = _CheckAndPrintFailures(duplicate_test_functions, 144 'Found duplicate functions in API test') 145 result = result and check 146 147 public_functions_set = set(public_functions) 148 test_functions_set = set(test_functions) 149 not_tested = public_functions_set.difference(test_functions_set) 150 check = _CheckAndPrintFailures(not_tested, 'Functions not tested') 151 result = result and check 152 non_existent = test_functions_set.difference(public_functions_set) 153 check = _CheckAndPrintFailures(non_existent, 'Tested functions do not exist') 154 result = result and check 155 156 if not result: 157 print('Some checks failed. Make sure %s is in sync with the public API ' 158 'headers.' % api_test_relative_path) 159 return 1 160 161 return 0 162 163 164if __name__ == '__main__': 165 sys.exit(main()) 166