brintos

brintos / llvm-project-archived public Read only

0
0
Text · 11.0 KiB · 272cf05 Raw
358 lines · python
1import os2 3from clang.cindex import (4    Config,5    Cursor,6    CursorKind,7    File,8    Index,9    SourceLocation,10    SourceRange,11    TranslationUnit,12    TranslationUnitLoadError,13    TranslationUnitSaveError,14)15 16if "CLANG_LIBRARY_PATH" in os.environ:17    Config.set_library_path(os.environ["CLANG_LIBRARY_PATH"])18 19import gc20import tempfile21import unittest22from contextlib import contextmanager23from pathlib import Path24 25from .util import get_cursor, get_tu26 27INPUTS_DIR = os.path.join(os.path.dirname(__file__), "INPUTS")28 29 30@contextmanager31def save_tu(tu):32    """Convenience API to save a TranslationUnit to a file.33 34    Returns the filename it was saved to.35    """36    with tempfile.NamedTemporaryFile() as t:37        tu.save(t.name)38        yield t.name39 40 41@contextmanager42def save_tu_pathlike(tu):43    """Convenience API to save a TranslationUnit to a file.44 45    Returns the filename it was saved to.46    """47    with tempfile.NamedTemporaryFile() as t:48        tu.save(Path(t.name))49        yield t.name50 51 52class TestTranslationUnit(unittest.TestCase):53    def test_spelling(self):54        path = os.path.join(INPUTS_DIR, "hello.cpp")55        tu = TranslationUnit.from_source(path)56        self.assertEqual(tu.spelling, path)57 58    def test_cursor(self):59        path = os.path.join(INPUTS_DIR, "hello.cpp")60        tu = get_tu(path)61        c = tu.cursor62        self.assertIsInstance(c, Cursor)63        self.assertIs(c.kind, CursorKind.TRANSLATION_UNIT)64 65    def test_parse_arguments(self):66        path = os.path.join(INPUTS_DIR, "parse_arguments.c")67        tu = TranslationUnit.from_source(path, ["-DDECL_ONE=hello", "-DDECL_TWO=hi"])68        spellings = [c.spelling for c in tu.cursor.get_children()]69        self.assertEqual(spellings[-2], "hello")70        self.assertEqual(spellings[-1], "hi")71 72    def test_reparse_arguments(self):73        path = os.path.join(INPUTS_DIR, "parse_arguments.c")74        tu = TranslationUnit.from_source(path, ["-DDECL_ONE=hello", "-DDECL_TWO=hi"])75        tu.reparse()76        spellings = [c.spelling for c in tu.cursor.get_children()]77        self.assertEqual(spellings[-2], "hello")78        self.assertEqual(spellings[-1], "hi")79 80    def test_unsaved_files(self):81        tu = TranslationUnit.from_source(82            "fake.c",83            ["-I./"],84            unsaved_files=[85                (86                    "fake.c",87                    """88#include "fake.h"89int x;90int SOME_DEFINE;91""",92                ),93                (94                    "./fake.h",95                    """96#define SOME_DEFINE y97""",98                ),99            ],100        )101        spellings = [c.spelling for c in tu.cursor.get_children()]102        self.assertEqual(spellings[-2], "x")103        self.assertEqual(spellings[-1], "y")104 105    def test_unsaved_files_2(self):106        from io import StringIO107 108        tu = TranslationUnit.from_source(109            "fake.c", unsaved_files=[("fake.c", StringIO("int x;"))]110        )111        spellings = [c.spelling for c in tu.cursor.get_children()]112        self.assertEqual(spellings[-1], "x")113 114    def test_from_source_accepts_pathlike(self):115        tu = TranslationUnit.from_source(116            Path("fake.c"),117            ["-Iincludes"],118            unsaved_files=[119                (120                    Path("fake.c"),121                    """122#include "fake.h"123    int x;124    int SOME_DEFINE;125    """,126                ),127                (128                    Path("includes/fake.h"),129                    """130#define SOME_DEFINE y131    """,132                ),133            ],134        )135        spellings = [c.spelling for c in tu.cursor.get_children()]136        self.assertEqual(spellings[-2], "x")137        self.assertEqual(spellings[-1], "y")138 139    def assert_normpaths_equal(self, path1, path2):140        """Compares two paths for equality after normalizing them with141        os.path.normpath142        """143        self.assertEqual(os.path.normpath(path1), os.path.normpath(path2))144 145    def test_includes(self):146        def eq(expected, actual):147            if not actual.is_input_file:148                self.assert_normpaths_equal(expected[0], actual.source.name)149                self.assert_normpaths_equal(expected[1], actual.include.name)150            else:151                self.assert_normpaths_equal(expected[1], actual.include.name)152 153        src = os.path.join(INPUTS_DIR, "include.cpp")154        h1 = os.path.join(INPUTS_DIR, "header1.h")155        h2 = os.path.join(INPUTS_DIR, "header2.h")156        h3 = os.path.join(INPUTS_DIR, "header3.h")157        inc = [(src, h1), (h1, h3), (src, h2), (h2, h3)]158 159        tu = TranslationUnit.from_source(src)160        for i in zip(inc, tu.get_includes()):161            eq(i[0], i[1])162 163    def test_inclusion_directive(self):164        src = os.path.join(INPUTS_DIR, "include.cpp")165        h1 = os.path.join(INPUTS_DIR, "header1.h")166        h2 = os.path.join(INPUTS_DIR, "header2.h")167        h3 = os.path.join(INPUTS_DIR, "header3.h")168        inc = [h1, h3, h2, h3, h1]169 170        tu = TranslationUnit.from_source(171            src, options=TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD172        )173        inclusion_directive_files = [174            c.get_included_file().name175            for c in tu.cursor.get_children()176            if c.kind == CursorKind.INCLUSION_DIRECTIVE177        ]178        for i in zip(inc, inclusion_directive_files):179            self.assert_normpaths_equal(i[0], i[1])180 181    def test_save(self):182        """Ensure TranslationUnit.save() works."""183 184        tu = get_tu("int foo();")185 186        with save_tu(tu) as path:187            self.assertTrue(os.path.exists(path))188            self.assertGreater(os.path.getsize(path), 0)189 190    def test_save_pathlike(self):191        """Ensure TranslationUnit.save() works with PathLike filename."""192 193        tu = get_tu("int foo();")194 195        with save_tu_pathlike(tu) as path:196            self.assertTrue(os.path.exists(path))197            self.assertGreater(os.path.getsize(path), 0)198 199    def test_save_translation_errors(self):200        """Ensure that saving to an invalid directory raises."""201 202        tu = get_tu("int foo();")203 204        path = "/does/not/exist/llvm-test.ast"205        self.assertFalse(os.path.exists(os.path.dirname(path)))206 207        with self.assertRaises(TranslationUnitSaveError) as cm:208            tu.save(path)209        ex = cm.exception210        expected = TranslationUnitSaveError.ERROR_UNKNOWN211        self.assertEqual(ex.save_error, expected)212 213    def test_load(self):214        """Ensure TranslationUnits can be constructed from saved files."""215 216        tu = get_tu("int foo();")217        self.assertEqual(len(tu.diagnostics), 0)218        with save_tu(tu) as path:219            self.assertTrue(os.path.exists(path))220            self.assertGreater(os.path.getsize(path), 0)221 222            tu2 = TranslationUnit.from_ast_file(filename=path)223            self.assertEqual(len(tu2.diagnostics), 0)224 225            foo = get_cursor(tu2, "foo")226            self.assertIsNotNone(foo)227 228            # Just in case there is an open file descriptor somewhere.229            del tu2230 231    def test_load_pathlike(self):232        """Ensure TranslationUnits can be constructed from saved files -233        PathLike variant."""234        tu = get_tu("int foo();")235        self.assertEqual(len(tu.diagnostics), 0)236        with save_tu(tu) as path:237            tu2 = TranslationUnit.from_ast_file(filename=Path(path))238            self.assertEqual(len(tu2.diagnostics), 0)239 240            foo = get_cursor(tu2, "foo")241            self.assertIsNotNone(foo)242 243            # Just in case there is an open file descriptor somewhere.244            del tu2245 246    def test_index_parse(self):247        path = os.path.join(INPUTS_DIR, "hello.cpp")248        index = Index.create()249        tu = index.parse(path)250        self.assertIsInstance(tu, TranslationUnit)251 252    def test_get_file(self):253        """Ensure tu.get_file() works appropriately."""254 255        tu = get_tu("int foo();")256 257        f = tu.get_file("t.c")258        self.assertIsInstance(f, File)259        self.assertEqual(f.name, "t.c")260 261        with self.assertRaises(Exception):262            f = tu.get_file("foobar.cpp")263 264    def test_get_file_pathlike(self):265        """Ensure tu.get_file() works appropriately with PathLike filenames."""266 267        tu = get_tu("int foo();")268 269        f = tu.get_file(Path("t.c"))270        self.assertIsInstance(f, File)271        self.assertEqual(f.name, "t.c")272 273        with self.assertRaises(Exception):274            f = tu.get_file(Path("foobar.cpp"))275 276    def test_get_source_location(self):277        """Ensure tu.get_source_location() works."""278 279        tu = get_tu("int foo();")280 281        location = tu.get_location("t.c", 2)282        self.assertIsInstance(location, SourceLocation)283        self.assertEqual(location.offset, 2)284        self.assertEqual(location.file.name, "t.c")285 286        location = tu.get_location("t.c", (1, 3))287        self.assertIsInstance(location, SourceLocation)288        self.assertEqual(location.line, 1)289        self.assertEqual(location.column, 3)290        self.assertEqual(location.file.name, "t.c")291 292    def test_get_source_range(self):293        """Ensure tu.get_source_range() works."""294 295        tu = get_tu("int foo();")296 297        r = tu.get_extent("t.c", (1, 4))298        self.assertIsInstance(r, SourceRange)299        self.assertEqual(r.start.offset, 1)300        self.assertEqual(r.end.offset, 4)301        self.assertEqual(r.start.file.name, "t.c")302        self.assertEqual(r.end.file.name, "t.c")303 304        r = tu.get_extent("t.c", ((1, 2), (1, 3)))305        self.assertIsInstance(r, SourceRange)306        self.assertEqual(r.start.line, 1)307        self.assertEqual(r.start.column, 2)308        self.assertEqual(r.end.line, 1)309        self.assertEqual(r.end.column, 3)310        self.assertEqual(r.start.file.name, "t.c")311        self.assertEqual(r.end.file.name, "t.c")312 313        start = tu.get_location("t.c", 0)314        end = tu.get_location("t.c", 5)315 316        r = tu.get_extent("t.c", (start, end))317        self.assertIsInstance(r, SourceRange)318        self.assertEqual(r.start.offset, 0)319        self.assertEqual(r.end.offset, 5)320        self.assertEqual(r.start.file.name, "t.c")321        self.assertEqual(r.end.file.name, "t.c")322 323    def test_get_tokens_gc(self):324        """Ensures get_tokens() works properly with garbage collection."""325 326        tu = get_tu("int foo();")327        r = tu.get_extent("t.c", (0, 10))328        tokens = list(tu.get_tokens(extent=r))329 330        self.assertEqual(tokens[0].spelling, "int")331        gc.collect()332        self.assertEqual(tokens[0].spelling, "int")333 334        del tokens[1]335        gc.collect()336        self.assertEqual(tokens[0].spelling, "int")337 338        # May trigger segfault if we don't do our job properly.339        del tokens340        gc.collect()341        gc.collect()  # Just in case.342 343    def test_fail_from_source(self):344        path = os.path.join(INPUTS_DIR, "non-existent.cpp")345        try:346            tu = TranslationUnit.from_source(path)347        except TranslationUnitLoadError:348            tu = None349        self.assertEqual(tu, None)350 351    def test_fail_from_ast_file(self):352        path = os.path.join(INPUTS_DIR, "non-existent.ast")353        try:354            tu = TranslationUnit.from_ast_file(path)355        except TranslationUnitLoadError:356            tu = None357        self.assertEqual(tu, None)358