#!/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 fpdfview_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/fpdfview_c_api_test.c. It then verifies both lists do not contain
duplicates, and they match each other.
"""
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.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 _GetFunctionsFromTest(api_test_path):
chk_regex = re.compile('^ CHK\((.*)\);\n$')
with open(api_test_path) as f:
contents = f.readlines()
functions = []
for line in contents:
match = chk_regex.match(line)
if match:
functions.append(match.groups()[0])
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', 'fpdfview_c_api_test.c')
api_test_path = os.path.join(src_path, api_test_relative_path)
test_functions = _GetFunctionsFromTest(api_test_path)
result = True
duplicate_public_functions = _FindDuplicates(public_functions)
check = _CheckAndPrintFailures(duplicate_public_functions,
'Found duplicate functions in public headers')
result = result and check
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())