chromium/third_party/sqlite/scripts/extract_sqlite_api_unittest.py

#!/usr/bin/env python3

# Copyright 2018 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests for extract_sqlite_api.py.

These tests should be getting picked up by the PRESUBMIT.py in this directory.
"""

from importlib.machinery import SourceFileLoader
import os
import shutil
import sys
import tempfile
import unittest


class ExtractSqliteApiUnittest(unittest.TestCase):
    def setUp(self):
        self.test_root = tempfile.mkdtemp()
        source_path = os.path.join(
            os.path.dirname(os.path.realpath(__file__)),
            'extract_sqlite_api.py')
        self.extractor = SourceFileLoader('extract_api',
                                          source_path).load_module()

    def tearDown(self):
        if self.test_root:
            shutil.rmtree(self.test_root)

    def testExtractLineTuples(self):
        golden = [(1, 'Line1'), (2, ''), (3, 'Line 2'), (4, 'Line3'), (5, '')]
        text_with_newline = "Line1\n\nLine 2  \nLine3\n"
        self.assertEqual(
            self.extractor.ExtractLineTuples(text_with_newline), golden)

        golden = [(1, 'Line1'), (2, ''), (3, 'Line 2'), (4, 'Line3')]
        text_without_newline = "Line1\n\nLine 2  \nLine3"
        self.assertEqual(
            self.extractor.ExtractLineTuples(text_without_newline), golden)

    def testExtractPreprocessorDirectives(self):
        lines = [
            (1, '// Header comment'),
            (2, '#define DIRECTIVE 1'),
            (3, 'int main() { // \\'),
            (4, '}'),
            (5, ''),
            (6, '#define MULTILINE \\'),
            (7, 'MORE_MULTILINE_DIRECTIVE\\'),
            (8, 'END_MULTILINE_DIRECTIVE'),
            (9, 'void code() { }'),
        ]

        directives, code_lines = self.extractor.ExtractPreprocessorDirectives(
            lines)
        self.assertEqual(directives, [
            '#define DIRECTIVE 1',
            '#define MULTILINE \nMORE_MULTILINE_DIRECTIVE\nEND_MULTILINE_DIRECTIVE',
        ])
        self.assertEqual(code_lines, [
            (1, '// Header comment'),
            (3, 'int main() { // \\'),
            (4, '}'),
            (5, ''),
            (9, 'void code() { }'),
        ])

    def testExtractDefineMacroName(self):
        self.assertEqual(
            'SQLITE_API',
            self.extractor.ExtractDefineMacroName('#define SQLITE_API 1'))
        self.assertEqual(
            'SQLITE_API',
            self.extractor.ExtractDefineMacroName('#define SQLITE_API'))
        self.assertEqual(
            'SQLITE_API',
            self.extractor.ExtractDefineMacroName('#define SQLITE_API\n1'))
        self.assertEqual(
            'SQLITE_API',
            self.extractor.ExtractDefineMacroName(
                '#    define   SQLITE_API   1'))
        self.assertEqual(
            'SQLITE_API',
            self.extractor.ExtractDefineMacroName('#\tdefine\tSQLITE_API\t1'))
        self.assertEqual(
            None,
            self.extractor.ExtractDefineMacroName(' #define SQLITE_API 1'))
        self.assertEqual(
            None,
            self.extractor.ExtractDefineMacroName(' #define SQLITE_API() 1'))
        self.assertEqual(None, self.extractor.ExtractDefineMacroName(''))

    def testRemoveLineComments(self):
        self.assertEqual('word;', self.extractor.RemoveLineComments('word;'))
        self.assertEqual('', self.extractor.RemoveLineComments(''))
        self.assertEqual('', self.extractor.RemoveLineComments('// comment'))
        self.assertEqual('',
                         self.extractor.RemoveLineComments('/* comment */'))
        self.assertEqual('word;',
                         self.extractor.RemoveLineComments('wo/*comment*/rd;'))
        self.assertEqual(
            'word;*/', self.extractor.RemoveLineComments('wo/*comment*/rd;*/'))
        self.assertEqual(
            'word;*/',
            self.extractor.RemoveLineComments('wo/*/*comment*/rd;*/'))
        self.assertEqual(
            'word;', self.extractor.RemoveLineComments('wo/*comm//ent*/rd;'))

    def testRemoveComments(self):
        lines = [
            (1, 'code();'),
            (2, 'more_code(); /* with comment */ more_code();'),
            (3, '/**'),
            (4, 'Spec text'),
            (5, '**/ spec_code();'),
            (6,
             'late_code(); /* with comment */ more_late_code(); /* late comment'
             ),
            (7, 'ends here // C++ trap */ code(); // /* C trap'),
            (8, 'last_code();'),
        ]

        self.assertEqual(
            self.extractor.RemoveComments(lines), [
                (1, 'code();'),
                (2, 'more_code();  more_code();'),
                (3, ''),
                (5, ' spec_code();'),
                (6, 'late_code();  more_late_code(); '),
                (7, ' code(); '),
                (8, 'last_code();'),
            ])

    def testToStatementTuples(self):
        lines = [(1, 'void function();'), (2, 'int main('),
                 (3, '  int argc, char* argv) {'),
                 (4, '  statement1; statement2;'), (5, '}'), (6, 'stat'),
                 (7, 'ement4; statement5; sta'), (8, 'tem'),
                 (9, 'ent6; statement7;')]

        self.assertEqual(
            self.extractor.ToStatementTuples(lines), [
                (1, 1, 'void function()'),
                (2, 3, 'int main(\n  int argc, char* argv)'),
                (4, 4, 'statement1'),
                (4, 4, 'statement2'),
                (5, 5, ''),
                (6, 7, 'stat\nement4'),
                (7, 7, 'statement5'),
                (7, 9, 'sta\ntem\nent6'),
                (9, 9, 'statement7'),
            ])

    def testExtractApiExport(self):
        self.assertEqual(
            'sqlite3_init',
            self.extractor.ExtractApiExport(set(), 'SQLITE_API',
                                            'SQLITE_API void sqlite3_init()'))
        self.assertEqual(
            'sqlite3_sleep',
            self.extractor.ExtractApiExport(
                set(), 'SQLITE_API', 'SQLITE_API int sqlite3_sleep(int ms)'))
        self.assertEqual(
            'sqlite3_sleep',
            self.extractor.ExtractApiExport(
                set(), 'SQLITE_API',
                'SQLITE_API long long sqlite3_sleep(int ms)'))
        self.assertEqual(
            'sqlite3rbu_temp_size',
            self.extractor.ExtractApiExport(
                set(), 'SQLITE_API',
                'SQLITE_API sqlite3_int64 sqlite3rbu_temp_size(sqlite3rbu *pRbu)'
            ))
        self.assertEqual(
            'sqlite3_expired',
            self.extractor.ExtractApiExport(
                set(['SQLITE_DEPRECATED']), 'SQLITE_API',
                'SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*)'
            ))
        # SQLite's header actually #defines double (in some cases).
        self.assertEqual(
            'sqlite3_column_double',
            self.extractor.ExtractApiExport(
                set(['double']), 'SQLITE_API',
                'SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol)'
            ))
        self.assertEqual(
            'sqlite3_temp_directory',
            self.extractor.ExtractApiExport(
                set(['SQLITE_EXTERN']), 'SQLITE_API',
                'SQLITE_API SQLITE_EXTERN char *sqlite3_temp_directory'))
        self.assertEqual(
            'sqlite3_version',
            self.extractor.ExtractApiExport(
                set(['SQLITE_EXTERN']), 'SQLITE_API',
                'SQLITE_API SQLITE_EXTERN const char sqlite3_version[]'))
        self.assertEqual(
            None,
            self.extractor.ExtractApiExport(
                set(['SQLITE_DEPRECATED']), 'SQLITE_API',
                'NOT_SQLITE_API struct sqlite_type sqlite3_sleep(int ms)'))

        with self.assertRaisesRegex(self.extractor.ExtractError,
                                    'Mixed simple .* and composite'):
            self.extractor.ExtractApiExport(
                set(), 'SQLITE_API',
                'SQLITE_API void int sqlite3_sleep(int ms)')
        with self.assertRaisesRegex(self.extractor.ExtractError,
                                    'Unsupported keyword struct'):
            self.extractor.ExtractApiExport(
                set(), 'SQLITE_API',
                'SQLITE_API struct sqlite_type sqlite3_sleep(int ms)')
        with self.assertRaisesRegex(self.extractor.ExtractError,
                                    'int\+\+ parsed as type name'):
            self.extractor.ExtractApiExport(
                set(), 'SQLITE_API', 'SQLITE_API int++ sqlite3_sleep(int ms)')
        with self.assertRaisesRegex(self.extractor.ExtractError,
                                    'sqlite3\+sleep parsed as symbol'):
            self.extractor.ExtractApiExport(
                set(), 'SQLITE_API', 'SQLITE_API int sqlite3+sleep(int ms)')

    def testExportedSymbolLine(self):
        self.assertEqual(
            '#define sqlite3_sleep chrome_sqlite3_sleep  // Line 42',
            self.extractor.ExportedSymbolLine(
                'chrome_', 'sqlite3_sleep',
                (42, 42, 'SQLITE_API int chrome_sqlite3_sleep(int ms)')))
        self.assertEqual(
            '#define sqlite3_sleep chrome_sqlite3_sleep  // Lines 42-44',
            self.extractor.ExportedSymbolLine(
                'chrome_', 'sqlite3_sleep',
                (42, 44, 'SQLITE_API int chrome_sqlite3_sleep(int ms)')))

    def testExportedExceptionLine(self):
        self.assertEqual(
            '// TODO: Lines 42-44 -- Something went wrong',
            self.extractor.ExportedExceptionLine(
                self.extractor.ExtractError('Something went wrong'),
                (42, 44, 'SQLITE_API int chrome_sqlite3_sleep(int ms)')))

    def testProcessSource(self):
        file_content = '\n'.join([
            '/*',
            'struct sqlite_type sqlite3_sleep;  // Remove comments',
            '*/',
            '#define SQLITE_DEPRECATED',
            'SQLITE_API int sqlite3_sleep(int ms);',
            'SQLITE_API struct sqlite_type sqlite3_sleep(int ms);',
            'SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*);',
        ])
        golden_output = [
            '// Header',
            '#define sqlite3_expired chrome_sqlite3_expired  // Line 7',
            '#define sqlite3_sleep chrome_sqlite3_sleep  // Line 5',
            '// TODO: Lines 6-6 -- Unsupported keyword struct',
            '// Footer',
        ]
        self.assertEqual(
            golden_output,
            self.extractor.ProcessSource('SQLITE_API', 'chrome_', '// Header',
                                         '// Footer', file_content))

    def testProcessSourceFile(self):
        file_content = '\n'.join([
            '/*',
            'struct sqlite_type sqlite3_sleep;  // Remove comments',
            '*/',
            '#define SQLITE_DEPRECATED',
            'SQLITE_API int sqlite3_sleep(int ms);',
            'SQLITE_API struct sqlite_type sqlite3_sleep(int ms);',
            'SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*);',
        ])
        golden_output = '\n'.join([
            '// Header',
            '#define sqlite3_expired chrome_sqlite3_expired  // Line 7',
            '#define sqlite3_sleep chrome_sqlite3_sleep  // Line 5',
            '// TODO: Lines 6-6 -- Unsupported keyword struct',
            '// Footer',
            '',
        ])

        input_file = os.path.join(self.test_root, 'input.h')
        output_file = os.path.join(self.test_root, 'macros.h')
        with open(input_file, 'w') as f:
            f.write(file_content)
        self.extractor.ProcessSourceFile('SQLITE_API', 'chrome_', '// Header',
                                         '// Footer', input_file, output_file)
        with open(output_file, 'r') as f:
            self.assertEqual(f.read(), golden_output)


if __name__ == '__main__':
    unittest.main()