Home | History | Annotate | Download | only in tools
      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 
      6 """Verifies exported functions in public/*.h are in fpdfview_c_api_test.c.
      7 
      8 This script gathers a list of functions from public/*.h that contain
      9 FPDF_EXPORT. It then gathers a list of functions from
     10 fpdfsdk/fpdfview_c_api_test.c. It then verifies both lists do not contain
     11 duplicates, and they match each other.
     12 
     13 """
     14 
     15 import os
     16 import re
     17 import sys
     18 
     19 def _IsValidFunctionName(function, filename):
     20   if function.startswith('FPDF'):
     21     return True
     22   if function == 'FSDK_SetUnSpObjProcessHandler' and filename == 'fpdf_ext.h':
     23     return True
     24   if function.startswith('FORM_') and filename == 'fpdf_formfill.h':
     25     return True
     26   return False
     27 
     28 
     29 def _FindFunction(function_snippet, filename):
     30   function_split = function_snippet.split('(')
     31   assert len(function_split) == 2
     32   function = function_split[0]
     33   assert _IsValidFunctionName(function, filename)
     34   return function
     35 
     36 
     37 def _GetExportsFromHeader(dirname, filename):
     38   with open(os.path.join(dirname, filename)) as f:
     39     contents = f.readlines()
     40     look_for_function_name = False
     41     functions = []
     42     for line in contents:
     43       if look_for_function_name:
     44         look_for_function_name = False
     45         split_line = line.rstrip().split(' ')
     46         functions.append(_FindFunction(split_line[0], filename))
     47         continue
     48 
     49       if not line.startswith('FPDF_EXPORT '):
     50         continue
     51 
     52       # Format should be: FPDF_EXPORT return_type FPDF_CALLCONV
     53       split_line = line.rstrip().split(' ')
     54       callconv_index = split_line.index('FPDF_CALLCONV')
     55       assert callconv_index >= 2
     56       if callconv_index + 1 == len(split_line):
     57         look_for_function_name = True
     58         continue
     59 
     60       functions.append(_FindFunction(split_line[callconv_index + 1], filename))
     61     return functions
     62 
     63 
     64 def _GetFunctionsFromPublicHeaders(src_path):
     65   public_path = os.path.join(src_path, 'public')
     66   functions = []
     67   for filename in os.listdir(public_path):
     68     if filename.endswith('.h'):
     69       functions.extend(_GetExportsFromHeader(public_path, filename))
     70   return functions
     71 
     72 
     73 def _GetFunctionsFromTest(api_test_path):
     74   chk_regex = re.compile('^    CHK\((.*)\);\n$')
     75   with open(api_test_path) as f:
     76     contents = f.readlines()
     77     functions = []
     78     for line in contents:
     79       match = chk_regex.match(line)
     80       if match:
     81         functions.append(match.groups()[0])
     82     return functions
     83 
     84 
     85 def _FindDuplicates(functions):
     86   return set([f for f in functions if functions.count(f) > 1])
     87 
     88 
     89 def _CheckAndPrintFailures(failure_list, failure_message):
     90   if not failure_list:
     91     return True
     92 
     93   print '%s:' % failure_message
     94   for f in sorted(failure_list):
     95     print f
     96   return False
     97 
     98 
     99 def main():
    100   script_abspath = os.path.abspath(__file__)
    101   src_path = os.path.dirname(os.path.dirname(os.path.dirname(script_abspath)))
    102   public_functions = _GetFunctionsFromPublicHeaders(src_path)
    103 
    104   api_test_relative_path = os.path.join('fpdfsdk', 'fpdfview_c_api_test.c')
    105   api_test_path = os.path.join(src_path, api_test_relative_path)
    106   test_functions = _GetFunctionsFromTest(api_test_path)
    107 
    108   result = True
    109   duplicate_public_functions = _FindDuplicates(public_functions)
    110   check = _CheckAndPrintFailures(duplicate_public_functions,
    111                                 'Found duplicate functions in public headers')
    112   result = result and check
    113 
    114   duplicate_test_functions = _FindDuplicates(test_functions)
    115   check = _CheckAndPrintFailures(duplicate_test_functions,
    116                                 'Found duplicate functions in API test')
    117   result = result and check
    118 
    119   public_functions_set = set(public_functions)
    120   test_functions_set = set(test_functions)
    121   not_tested = public_functions_set.difference(test_functions_set)
    122   check = _CheckAndPrintFailures(not_tested, 'Functions not tested')
    123   result = result and check
    124   non_existent = test_functions_set.difference(public_functions_set)
    125   check = _CheckAndPrintFailures(non_existent, 'Tested functions do not exist')
    126   result = result and check
    127 
    128   if not result:
    129     print ('Some checks failed. Make sure %s is in sync with the public API '
    130            'headers.'
    131            % api_test_relative_path);
    132     return 1
    133 
    134   return 0
    135 
    136 
    137 if __name__ == '__main__':
    138   sys.exit(main())
    139