1#!/usr/bin/env python3
2
3# Copyright 2018 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6"""Tests for extract_sqlite_api.py.
7
8These tests should be getting picked up by the PRESUBMIT.py in this directory.
9"""
10
11from importlib.machinery import SourceFileLoader
12import os
13import shutil
14import sys
15import tempfile
16import unittest
17
18class ExtractSqliteApiUnittest(unittest.TestCase):
19  def setUp(self):
20    self.test_root = tempfile.mkdtemp()
21    source_path = os.path.join(
22        os.path.dirname(os.path.realpath(__file__)), 'extract_sqlite_api.py')
23    self.extractor = SourceFileLoader('extract_api', source_path).load_module()
24
25  def tearDown(self):
26    if self.test_root:
27      shutil.rmtree(self.test_root)
28
29  def testExtractLineTuples(self):
30    golden = [(1, 'Line1'), (2, ''), (3, 'Line 2'), (4, 'Line3'), (5, '')]
31    text_with_newline = "Line1\n\nLine 2  \nLine3\n"
32    self.assertEqual(self.extractor.ExtractLineTuples(text_with_newline),
33                     golden)
34
35    golden = [(1, 'Line1'), (2, ''), (3, 'Line 2'), (4, 'Line3')]
36    text_without_newline = "Line1\n\nLine 2  \nLine3"
37    self.assertEqual(self.extractor.ExtractLineTuples(text_without_newline),
38                     golden)
39
40  def testExtractPreprocessorDirectives(self):
41    lines = [
42      (1, '// Header comment'),
43      (2, '#define DIRECTIVE 1'),
44      (3, 'int main() { // \\'),
45      (4, '}'),
46      (5, ''),
47      (6, '#define MULTILINE \\'),
48      (7, 'MORE_MULTILINE_DIRECTIVE\\'),
49      (8, 'END_MULTILINE_DIRECTIVE'),
50      (9, 'void code() { }'),
51    ]
52
53    directives, code_lines = self.extractor.ExtractPreprocessorDirectives(lines)
54    self.assertEqual(directives, [
55      '#define DIRECTIVE 1',
56      '#define MULTILINE \nMORE_MULTILINE_DIRECTIVE\nEND_MULTILINE_DIRECTIVE',
57    ])
58    self.assertEqual(code_lines, [
59      (1, '// Header comment'),
60      (3, 'int main() { // \\'),
61      (4, '}'),
62      (5, ''),
63      (9, 'void code() { }'),
64    ])
65
66  def testExtractDefineMacroName(self):
67    self.assertEqual(
68        'SQLITE_API', self.extractor.ExtractDefineMacroName(
69            '#define SQLITE_API 1'))
70    self.assertEqual(
71        'SQLITE_API', self.extractor.ExtractDefineMacroName(
72            '#define SQLITE_API'))
73    self.assertEqual(
74        'SQLITE_API', self.extractor.ExtractDefineMacroName(
75            '#define SQLITE_API\n1'))
76    self.assertEqual(
77        'SQLITE_API', self.extractor.ExtractDefineMacroName(
78            '#    define   SQLITE_API   1'))
79    self.assertEqual(
80        'SQLITE_API', self.extractor.ExtractDefineMacroName(
81            '#\tdefine\tSQLITE_API\t1'))
82    self.assertEqual(
83        None, self.extractor.ExtractDefineMacroName(
84            ' #define SQLITE_API 1'))
85    self.assertEqual(
86        None, self.extractor.ExtractDefineMacroName(
87            ' #define SQLITE_API() 1'))
88    self.assertEqual(None, self.extractor.ExtractDefineMacroName(''))
89
90  def testRemoveLineComments(self):
91    self.assertEqual(
92        'word;', self.extractor.RemoveLineComments('word;'))
93    self.assertEqual(
94        '', self.extractor.RemoveLineComments(''))
95    self.assertEqual(
96        '', self.extractor.RemoveLineComments('// comment'))
97    self.assertEqual(
98        '', self.extractor.RemoveLineComments('/* comment */'))
99    self.assertEqual(
100        'word;', self.extractor.RemoveLineComments('wo/*comment*/rd;'))
101    self.assertEqual(
102        'word;*/', self.extractor.RemoveLineComments('wo/*comment*/rd;*/'))
103    self.assertEqual(
104        'word;*/', self.extractor.RemoveLineComments('wo/*/*comment*/rd;*/'))
105    self.assertEqual(
106        'word;', self.extractor.RemoveLineComments('wo/*comm//ent*/rd;'))
107
108  def testRemoveComments(self):
109    lines = [
110      (1, 'code();'),
111      (2, 'more_code(); /* with comment */ more_code();'),
112      (3, '/**'),
113      (4, 'Spec text'),
114      (5, '**/ spec_code();'),
115      (6, 'late_code(); /* with comment */ more_late_code(); /* late comment'),
116      (7, 'ends here // C++ trap */ code(); // /* C trap'),
117      (8, 'last_code();'),
118    ]
119
120    self.assertEqual(self.extractor.RemoveComments(lines), [
121      (1, 'code();'),
122      (2, 'more_code();  more_code();'),
123      (3, ''),
124      (5, ' spec_code();'),
125      (6, 'late_code();  more_late_code(); '),
126      (7, ' code(); '),
127      (8, 'last_code();'),
128    ])
129
130  def testToStatementTuples(self):
131    lines = [
132      (1, 'void function();'),
133      (2, 'int main('),
134      (3, '  int argc, char* argv) {'),
135      (4, '  statement1; statement2;'),
136      (5, '}'),
137      (6, 'stat'),
138      (7, 'ement4; statement5; sta'),
139      (8, 'tem'),
140      (9, 'ent6; statement7;')
141    ]
142
143    self.assertEqual(self.extractor.ToStatementTuples(lines), [
144      (1, 1, 'void function()'),
145      (2, 3, 'int main(\n  int argc, char* argv)'),
146      (4, 4, 'statement1'),
147      (4, 4, 'statement2'),
148      (5, 5, ''),
149      (6, 7, 'stat\nement4'),
150      (7, 7, 'statement5'),
151      (7, 9, 'sta\ntem\nent6'),
152      (9, 9, 'statement7'),
153    ])
154
155  def testExtractApiExport(self):
156    self.assertEqual(
157        'sqlite3_init',
158        self.extractor.ExtractApiExport(
159            set(), 'SQLITE_API', 'SQLITE_API void sqlite3_init()'))
160    self.assertEqual(
161        'sqlite3_sleep',
162        self.extractor.ExtractApiExport(
163            set(), 'SQLITE_API', 'SQLITE_API int sqlite3_sleep(int ms)'))
164    self.assertEqual(
165        'sqlite3_sleep',
166        self.extractor.ExtractApiExport(
167            set(), 'SQLITE_API',
168            'SQLITE_API long long sqlite3_sleep(int ms)'))
169    self.assertEqual(
170        'sqlite3rbu_temp_size',
171        self.extractor.ExtractApiExport(
172            set(), 'SQLITE_API',
173            'SQLITE_API sqlite3_int64 sqlite3rbu_temp_size(sqlite3rbu *pRbu)'))
174    self.assertEqual(
175        'sqlite3_expired',
176        self.extractor.ExtractApiExport(
177            set(['SQLITE_DEPRECATED']), 'SQLITE_API',
178            'SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*)'))
179    # SQLite's header actually #defines double (in some cases).
180    self.assertEqual(
181        'sqlite3_column_double',
182        self.extractor.ExtractApiExport(
183            set(['double']), 'SQLITE_API',
184            'SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol)'))
185    self.assertEqual(
186        'sqlite3_temp_directory',
187        self.extractor.ExtractApiExport(
188            set(['SQLITE_EXTERN']), 'SQLITE_API',
189            'SQLITE_API SQLITE_EXTERN char *sqlite3_temp_directory'))
190    self.assertEqual(
191        'sqlite3_version',
192        self.extractor.ExtractApiExport(
193            set(['SQLITE_EXTERN']), 'SQLITE_API',
194            'SQLITE_API SQLITE_EXTERN const char sqlite3_version[]'))
195    self.assertEqual(
196        None,
197        self.extractor.ExtractApiExport(
198            set(['SQLITE_DEPRECATED']), 'SQLITE_API',
199            'NOT_SQLITE_API struct sqlite_type sqlite3_sleep(int ms)'))
200
201    with self.assertRaisesRegex(self.extractor.ExtractError,
202                                'Mixed simple .* and composite'):
203      self.extractor.ExtractApiExport(
204          set(), 'SQLITE_API', 'SQLITE_API void int sqlite3_sleep(int ms)')
205    with self.assertRaisesRegex(self.extractor.ExtractError,
206                                'Unsupported keyword struct'):
207      self.extractor.ExtractApiExport(
208          set(), 'SQLITE_API',
209          'SQLITE_API struct sqlite_type sqlite3_sleep(int ms)')
210    with self.assertRaisesRegex(self.extractor.ExtractError,
211                                'int\+\+ parsed as type name'):
212      self.extractor.ExtractApiExport(
213          set(), 'SQLITE_API', 'SQLITE_API int++ sqlite3_sleep(int ms)')
214    with self.assertRaisesRegex(self.extractor.ExtractError,
215                                'sqlite3\+sleep parsed as symbol'):
216      self.extractor.ExtractApiExport(
217          set(), 'SQLITE_API', 'SQLITE_API int sqlite3+sleep(int ms)')
218
219  def testExportedSymbolLine(self):
220    self.assertEqual(
221        '#define sqlite3_sleep chrome_sqlite3_sleep  // Line 42',
222        self.extractor.ExportedSymbolLine(
223            'chrome_', 'sqlite3_sleep',
224            (42, 42, 'SQLITE_API int chrome_sqlite3_sleep(int ms)')))
225    self.assertEqual(
226        '#define sqlite3_sleep chrome_sqlite3_sleep  // Lines 42-44',
227        self.extractor.ExportedSymbolLine(
228            'chrome_', 'sqlite3_sleep',
229            (42, 44, 'SQLITE_API int chrome_sqlite3_sleep(int ms)')))
230
231  def testExportedExceptionLine(self):
232    self.assertEqual(
233        '// TODO: Lines 42-44 -- Something went wrong',
234        self.extractor.ExportedExceptionLine(
235            self.extractor.ExtractError('Something went wrong'),
236            (42, 44, 'SQLITE_API int chrome_sqlite3_sleep(int ms)')))
237
238  def testProcessSource(self):
239    file_content = '\n'.join([
240      '/*',
241      'struct sqlite_type sqlite3_sleep;  // Remove comments',
242      '*/',
243      '#define SQLITE_DEPRECATED',
244      'SQLITE_API int sqlite3_sleep(int ms);',
245      'SQLITE_API struct sqlite_type sqlite3_sleep(int ms);',
246      'SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*);',
247    ])
248    golden_output = [
249      '// Header',
250      '#define sqlite3_expired chrome_sqlite3_expired  // Line 7',
251      '#define sqlite3_sleep chrome_sqlite3_sleep  // Line 5',
252      '// TODO: Lines 6-6 -- Unsupported keyword struct',
253      '// Footer',
254    ]
255    self.assertEqual(
256        golden_output,
257        self.extractor.ProcessSource('SQLITE_API', 'chrome_', '// Header',
258                                     '// Footer', file_content))
259
260  def testProcessSourceFile(self):
261    file_content = '\n'.join([
262      '/*',
263      'struct sqlite_type sqlite3_sleep;  // Remove comments',
264      '*/',
265      '#define SQLITE_DEPRECATED',
266      'SQLITE_API int sqlite3_sleep(int ms);',
267      'SQLITE_API struct sqlite_type sqlite3_sleep(int ms);',
268      'SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*);',
269    ])
270    golden_output = '\n'.join([
271      '// Header',
272      '#define sqlite3_expired chrome_sqlite3_expired  // Line 7',
273      '#define sqlite3_sleep chrome_sqlite3_sleep  // Line 5',
274      '// TODO: Lines 6-6 -- Unsupported keyword struct',
275      '// Footer',
276      '',
277    ])
278
279    input_file = os.path.join(self.test_root, 'input.h')
280    output_file = os.path.join(self.test_root, 'macros.h')
281    with open(input_file, 'w') as f:
282      f.write(file_content)
283    self.extractor.ProcessSourceFile(
284        'SQLITE_API', 'chrome_', '// Header', '// Footer', input_file,
285        output_file)
286    with open(output_file, 'r') as f:
287      self.assertEqual(f.read(), golden_output)
288
289if __name__ == '__main__':
290  unittest.main()
291