| #!/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.startswith('FSDK_') 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()) |