| #!/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()) |