summaryrefslogtreecommitdiff
path: root/testing/tools/api_check.py
blob: 934577b21be970cbb2a5334742feda394e9bef36 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/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())