mirror of
https://github.com/rizsotto/scan-build.git
synced 2026-10-30 12:15:25 +01:00
292 lines
12 KiB
Python
292 lines
12 KiB
Python
# SPDX-License-Identifier: MIT
|
|
|
|
import json
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
import clanganalyzer.compilation as sut
|
|
|
|
|
|
class CompilationEntryTest(unittest.TestCase):
|
|
def test_from_db_entry_with_arguments(self):
|
|
"""Test parsing compilation database entry with 'arguments' field."""
|
|
entry_data = {
|
|
"directory": "/home/user/project",
|
|
"file": "src/main.c",
|
|
"arguments": ["gcc", "-c", "-Wall", "src/main.c"],
|
|
}
|
|
|
|
entry = sut.CompilationEntry.from_db_entry(entry_data)
|
|
|
|
self.assertEqual(entry.directory, "/home/user/project")
|
|
self.assertEqual(entry.file, "src/main.c")
|
|
self.assertEqual(entry.arguments, ["gcc", "-c", "-Wall", "src/main.c"])
|
|
|
|
def test_from_db_entry_with_command(self):
|
|
"""Test parsing compilation database entry with 'command' field."""
|
|
entry_data = {"directory": "/home/user/project", "file": "src/main.cpp", "command": "g++ -c -std=c++17 src/main.cpp"}
|
|
|
|
entry = sut.CompilationEntry.from_db_entry(entry_data)
|
|
|
|
self.assertEqual(entry.directory, "/home/user/project")
|
|
self.assertEqual(entry.file, "src/main.cpp")
|
|
self.assertEqual(entry.arguments, ["g++", "-c", "-std=c++17", "src/main.cpp"])
|
|
|
|
def test_from_db_entry_with_quoted_command(self):
|
|
"""Test parsing compilation database entry with quoted arguments in command."""
|
|
entry_data = {
|
|
"directory": "/home/user/project",
|
|
"file": "src/test.c",
|
|
"command": 'gcc -DVERSION="1.0.0" -c src/test.c',
|
|
}
|
|
|
|
entry = sut.CompilationEntry.from_db_entry(entry_data)
|
|
|
|
self.assertEqual(entry.directory, "/home/user/project")
|
|
self.assertEqual(entry.file, "src/test.c")
|
|
self.assertEqual(entry.arguments, ["gcc", "-DVERSION=1.0.0", "-c", "src/test.c"])
|
|
|
|
|
|
class CompilationTest(unittest.TestCase):
|
|
def test_c_compilation(self):
|
|
"""Test creation of C compilation from entry."""
|
|
entry = sut.CompilationEntry(directory="/project", file="main.c", arguments=["gcc", "-Wall", "-O2", "main.c"])
|
|
|
|
compilation = sut.Compilation.from_entry(entry)
|
|
|
|
self.assertEqual(compilation.compiler, "c")
|
|
self.assertEqual(compilation.flags, ["-Wall", "-O2", "main.c"])
|
|
self.assertEqual(compilation.directory, os.path.normpath("/project"))
|
|
self.assertEqual(compilation.source, os.path.normpath("/project/main.c"))
|
|
|
|
def test_cpp_compilation(self):
|
|
"""Test creation of C++ compilation from entry."""
|
|
entry = sut.CompilationEntry(
|
|
directory="/project", file="main.cpp", arguments=["g++", "-std=c++17", "-Wall", "main.cpp"]
|
|
)
|
|
|
|
compilation = sut.Compilation.from_entry(entry)
|
|
|
|
self.assertEqual(compilation.compiler, "c++")
|
|
self.assertEqual(compilation.flags, ["-std=c++17", "-Wall", "main.cpp"])
|
|
self.assertEqual(compilation.directory, os.path.normpath("/project"))
|
|
self.assertEqual(compilation.source, os.path.normpath("/project/main.cpp"))
|
|
|
|
def test_clang_cpp_compilation(self):
|
|
"""Test C++ detection with clang++."""
|
|
entry = sut.CompilationEntry(directory="/project", file="test.cc", arguments=["clang++", "-O3", "test.cc"])
|
|
|
|
compilation = sut.Compilation.from_entry(entry)
|
|
|
|
self.assertEqual(compilation.compiler, "c++")
|
|
self.assertEqual(compilation.flags, ["-O3", "test.cc"])
|
|
|
|
def test_absolute_file_path(self):
|
|
"""Test handling of absolute file paths."""
|
|
entry = sut.CompilationEntry(directory="/project", file="/absolute/path/to/file.c", arguments=["gcc", "file.c"])
|
|
|
|
compilation = sut.Compilation.from_entry(entry)
|
|
|
|
self.assertEqual(compilation.source, os.path.normpath("/absolute/path/to/file.c"))
|
|
|
|
def test_empty_arguments(self):
|
|
"""Test handling of empty arguments list."""
|
|
entry = sut.CompilationEntry(directory="/project", file="empty.c", arguments=[])
|
|
|
|
compilation = sut.Compilation.from_entry(entry)
|
|
|
|
self.assertEqual(compilation.compiler, "c")
|
|
self.assertEqual(compilation.flags, [])
|
|
|
|
def test_equality(self):
|
|
"""Test compilation equality comparison."""
|
|
entry1 = sut.CompilationEntry("/project", "main.c", ["gcc", "main.c"])
|
|
entry2 = sut.CompilationEntry("/project", "main.c", ["gcc", "main.c"])
|
|
entry3 = sut.CompilationEntry("/project", "other.c", ["gcc", "other.c"])
|
|
|
|
comp1 = sut.Compilation.from_entry(entry1)
|
|
comp2 = sut.Compilation.from_entry(entry2)
|
|
comp3 = sut.Compilation.from_entry(entry3)
|
|
|
|
self.assertEqual(comp1, comp2)
|
|
self.assertNotEqual(comp1, comp3)
|
|
self.assertNotEqual(comp1, "not a compilation")
|
|
|
|
def test_as_dict(self):
|
|
"""Test conversion to dictionary."""
|
|
entry = sut.CompilationEntry(directory="/project", file="main.c", arguments=["gcc", "-Wall", "main.c"])
|
|
|
|
compilation = sut.Compilation.from_entry(entry)
|
|
result = compilation.as_dict()
|
|
|
|
expected = {
|
|
"source": os.path.normpath("/project/main.c"),
|
|
"directory": os.path.normpath("/project"),
|
|
"compiler": "c",
|
|
"flags": ["-Wall", "main.c"],
|
|
}
|
|
|
|
self.assertEqual(result, expected)
|
|
|
|
|
|
class CompilationDatabaseTest(unittest.TestCase):
|
|
def test_load_simple_database(self):
|
|
"""Test loading a simple compilation database."""
|
|
database_content = [
|
|
{"directory": "/project", "file": "main.c", "arguments": ["gcc", "-c", "main.c"]},
|
|
{"directory": "/project", "file": "utils.cpp", "command": "g++ -std=c++11 -c utils.cpp"},
|
|
]
|
|
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
json.dump(database_content, f)
|
|
temp_path = f.name
|
|
|
|
try:
|
|
compilations = list(sut.CompilationDatabase.load(temp_path))
|
|
|
|
self.assertEqual(len(compilations), 2)
|
|
|
|
# Check first compilation (C)
|
|
comp1 = compilations[0]
|
|
self.assertEqual(comp1.compiler, "c")
|
|
self.assertEqual(comp1.source, os.path.normpath("/project/main.c"))
|
|
self.assertEqual(comp1.flags, ["-c", "main.c"])
|
|
|
|
# Check second compilation (C++)
|
|
comp2 = compilations[1]
|
|
self.assertEqual(comp2.compiler, "c++")
|
|
self.assertEqual(comp2.source, os.path.normpath("/project/utils.cpp"))
|
|
self.assertEqual(comp2.flags, ["-std=c++11", "-c", "utils.cpp"])
|
|
|
|
finally:
|
|
os.unlink(temp_path)
|
|
|
|
def test_load_empty_database(self):
|
|
"""Test loading an empty compilation database."""
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
json.dump([], f)
|
|
temp_path = f.name
|
|
|
|
try:
|
|
compilations = list(sut.CompilationDatabase.load(temp_path))
|
|
self.assertEqual(len(compilations), 0)
|
|
finally:
|
|
os.unlink(temp_path)
|
|
|
|
|
|
class SourceClassifierTest(unittest.TestCase):
|
|
def test_sources(self):
|
|
"""Test source file classification by extension."""
|
|
tests = [
|
|
("header.h", None),
|
|
("impl.c", "c"),
|
|
("impl.cc", "c++"),
|
|
("impl.cpp", "c++"),
|
|
("impl.cxx", "c++"),
|
|
("impl.C", "c++"),
|
|
("impl.c++", "c++"),
|
|
("preprocessed.i", "c-cpp-output"),
|
|
("preprocessed.ii", "c++-cpp-output"),
|
|
("objc.m", "objective-c"),
|
|
("objcpp.mm", "objective-c++"),
|
|
("template.txx", "c++"),
|
|
("unknown.xyz", None),
|
|
]
|
|
|
|
for filename, expected in tests:
|
|
with self.subTest(filename=filename):
|
|
result = sut.classify_source(filename)
|
|
self.assertEqual(result, expected)
|
|
|
|
def test_c_compiler_context(self):
|
|
"""Test source classification with C compiler context."""
|
|
# With C compiler context, .i files are C preprocessed output
|
|
result = sut.classify_source("test.i", c_compiler=True)
|
|
self.assertEqual(result, "c-cpp-output")
|
|
|
|
# With C++ compiler context, .i files are C++ preprocessed output
|
|
result = sut.classify_source("test.i", c_compiler=False)
|
|
self.assertEqual(result, "c++-cpp-output")
|
|
|
|
# .c files change meaning based on compiler context
|
|
result = sut.classify_source("test.c", c_compiler=True)
|
|
self.assertEqual(result, "c")
|
|
|
|
result = sut.classify_source("test.c", c_compiler=False)
|
|
self.assertEqual(result, "c++")
|
|
|
|
|
|
class CommonPrefixTest(unittest.TestCase):
|
|
def test_empty(self):
|
|
self.assertEqual(sut._commonprefix([]), None)
|
|
|
|
@unittest.skipIf(os.name == "nt", "windows has different path patterns")
|
|
def test_with_different_filenames(self):
|
|
self.assertEqual(sut._commonprefix(["/tmp/a.c", "/tmp/b.c"]), Path("/tmp"))
|
|
|
|
@unittest.skipIf(os.name == "nt", "windows has different path patterns")
|
|
def test_with_different_dirnames(self):
|
|
self.assertEqual(sut._commonprefix(["/tmp/abs/a.c", "/tmp/ack/b.c"]), Path("/tmp"))
|
|
|
|
@unittest.skipIf(os.name == "nt", "windows has different path patterns")
|
|
def test_no_common_prefix(self):
|
|
self.assertEqual(sut._commonprefix(["/tmp/abs/a.c", "/usr/ack/b.c"]), Path("/"))
|
|
|
|
@unittest.skipIf(os.name == "nt", "windows has different path patterns")
|
|
def test_with_single_file(self):
|
|
self.assertEqual(sut._commonprefix(["/tmp/a.c"]), Path("/tmp"))
|
|
|
|
@unittest.skipIf(os.name != "nt", "windows has different path patterns")
|
|
def test_with_different_filenames_on_windows(self):
|
|
self.assertEqual(sut._commonprefix(["c:\\tmp\\a.c", "c:\\tmp\\b.c"]), Path("c:\\tmp"))
|
|
|
|
@unittest.skipIf(os.name != "nt", "windows has different path patterns")
|
|
def test_with_different_dirnames_on_windows(self):
|
|
self.assertEqual(sut._commonprefix(["c:\\tmp\\abs\\a.c", "c:\\tmp\\ack\\b.c"]), Path("c:\\tmp"))
|
|
|
|
@unittest.skipIf(os.name != "nt", "windows has different path patterns")
|
|
def test_no_common_prefix_on_windows(self):
|
|
self.assertEqual(sut._commonprefix(["z:\\tmp\\abs\\a.c", "z:\\usr\\ack\\b.c"]), Path("z:\\"))
|
|
|
|
@unittest.skipIf(os.name != "nt", "windows has different path patterns")
|
|
def test_different_drive_on_windows(self):
|
|
self.assertEqual(sut._commonprefix(["c:\\tmp\\abs\\a.c", "z:\\usr\\ack\\b.c"]), None)
|
|
|
|
@unittest.skipIf(os.name != "nt", "windows has different path patterns")
|
|
def test_with_single_file_on_windows(self):
|
|
self.assertEqual(sut._commonprefix(["z:\\tmp\\a.c"]), Path("z:\\tmp"))
|
|
|
|
|
|
class CommonPrefixFromTest(unittest.TestCase):
|
|
def test_commonprefix_from(self):
|
|
"""Test commonprefix_from with a compilation database file."""
|
|
database_content = [
|
|
{"directory": "/project", "file": "/project/src/main.c"},
|
|
{"directory": "/project", "file": "/project/src/utils.c"},
|
|
{"directory": "/project", "file": "/project/include/header.h"},
|
|
]
|
|
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
json.dump(database_content, f)
|
|
temp_path = f.name
|
|
|
|
try:
|
|
result = sut.CompilationDatabase.file_commonprefix(temp_path)
|
|
self.assertEqual(result, Path("/project"))
|
|
finally:
|
|
os.unlink(temp_path)
|
|
|
|
def test_commonprefix_from_empty_database(self):
|
|
"""Test commonprefix_from with an empty compilation database."""
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
json.dump([], f)
|
|
temp_path = f.name
|
|
|
|
try:
|
|
result = sut.CompilationDatabase.file_commonprefix(temp_path)
|
|
self.assertEqual(result, None)
|
|
finally:
|
|
os.unlink(temp_path)
|