#!/usr/bin/env python # Copyright 2017 The PDFium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Verifies exported functions in public/*.h are in fpdf_view_c_api_test.c. This script gathers a list of functions from public/*.h that contain FPDF_EXPORT. It then gathers a list of functions from fpdfsdk/fpdf_view_c_api_test.c. It then verifies both lists do not contain duplicates, and they match each other. It also checks that the order in fpdf_view_c_api_test.c is alphabetical within each section. """ import os import re import sys def _IsValidFunctionName(function, filename): if function.startswith('FPDF'): return True if function == 'FSDK_SetUnSpObjProcessHandler' and filename == 'fpdf_ext.h': return True if function == 'FSDK_SetTimeFunction' and filename == 'fpdf_ext.h': return True if function.startswith('FORM_') and filename == 'fpdf_formfill.h': return True return False def _FindFunction(function_snippet, filename): function_split = function_snippet.split('(') assert len(function_split) == 2 function = function_split[0] assert _IsValidFunctionName(function, filename) return function def _GetExportsFromHeader(dirname, filename): with open(os.path.join(dirname, filename)) as f: contents = f.readlines() look_for_function_name = False functions = [] for line in contents: if look_for_function_name: look_for_function_name = False split_line = line.rstrip().split(' ') functions.append(_FindFunction(split_line[0], filename)) continue if not line.startswith('FPDF_EXPORT '): continue # Format should be: FPDF_EXPORT return_type FPDF_CALLCONV split_line = line.rstrip().split(' ') callconv_index = split_line.index('FPDF_CALLCONV') assert callconv_index >= 2 if callconv_index + 1 == len(split_line): look_for_function_name = True continue functions.append(_FindFunction(split_line[callconv_index + 1], filename)) return functions def _GetFunctionsFromPublicHeaders(src_path): public_path = os.path.join(src_path, 'public') functions = [] for filename in os.listdir(public_path): if filename.endswith('.h'): functions.extend(_GetExportsFromHeader(public_path, filename)) return functions def _CheckSorted(functions, api_test_path): unsorted_functions = set() for i in range(len(functions) - 1): if functions[i] > functions[i+1]: unsorted_functions.add(functions[i]) unsorted_functions.add(functions[i+1]) return unsorted_functions def _GetFunctionsFromTest(api_test_path): chk_regex = re.compile('^ CHK\((.*)\);\n$') file_regex = re.compile('^ //.*\.h\n$') with open(api_test_path) as f: contents = f.readlines() functions = [] functions_in_file = [] for line in contents: if (file_regex.match(line)): functions.append(functions_in_file) functions_in_file = [] match = chk_regex.match(line) if match: functions_in_file.append(match.groups()[0]) functions.append(functions_in_file) return functions def _FindDuplicates(functions): return set([f for f in functions if functions.count(f) > 1]) def _CheckAndPrintFailures(failure_list, failure_message): if not failure_list: return True print '%s:' % failure_message for f in sorted(failure_list): print f return False def main(): script_abspath = os.path.abspath(__file__) src_path = os.path.dirname(os.path.dirname(os.path.dirname(script_abspath))) public_functions = _GetFunctionsFromPublicHeaders(src_path) api_test_relative_path = os.path.join('fpdfsdk', 'fpdf_view_c_api_test.c') api_test_path = os.path.join(src_path, api_test_relative_path) test_functions_per_section = _GetFunctionsFromTest(api_test_path) result = True unsorted_functions = set() for functions in test_functions_per_section: unsorted_functions |= _CheckSorted(functions, api_test_path) check = _CheckAndPrintFailures(unsorted_functions, 'Found CHKs that are not in alphabetical order within each section in %s' % api_test_path) result = result and check duplicate_public_functions = _FindDuplicates(public_functions) check = _CheckAndPrintFailures(duplicate_public_functions, 'Found duplicate functions in public headers') result = result and check test_functions = [function for functions in test_functions_per_section for function in functions] duplicate_test_functions = _FindDuplicates(test_functions) check = _CheckAndPrintFailures(duplicate_test_functions, 'Found duplicate functions in API test') result = result and check public_functions_set = set(public_functions) test_functions_set = set(test_functions) not_tested = public_functions_set.difference(test_functions_set) check = _CheckAndPrintFailures(not_tested, 'Functions not tested') result = result and check non_existent = test_functions_set.difference(public_functions_set) check = _CheckAndPrintFailures(non_existent, 'Tested functions do not exist') result = result and check if not result: print ('Some checks failed. Make sure %s is in sync with the public API ' 'headers.' % api_test_relative_path) return 1 return 0 if __name__ == '__main__': sys.exit(main())