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 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
|
# python -m pytest test-clang-import.py
import os
import re
import subprocess
import sys
import pytest
from testutils import cppcheck, assert_cppcheck
try:
# TODO: handle exitcode?
subprocess.call(['clang', '--version'])
except OSError:
pytest.skip("'clang' does not exist", allow_module_level=True)
# the IDs differ with Visual Studio
if sys.platform == 'win32':
pytest.skip(allow_module_level=True)
def __get_debug_section(title, stdout):
s = re.sub(r'0x[0-9a-fA-F]+', '0x12345678', stdout)
s = re.sub(r'nestedIn: Struct', 'nestedIn: Class', s)
s = re.sub(r'classDef: struct', 'classDef: class', s)
s = re.sub(r'isInline: [a-z]+', 'isInline: ---', s)
s = re.sub(r'needInitialization: .*', 'needInitialization: ---', s)
s = re.sub(r'functionOf: .*', 'functionOf: ---', s)
s = re.sub(r'0x12345678 Struct', '0x12345678 Class', s)
if title == '##AST':
# TODO set types
s = re.sub(r"return '[a-zA-Z0-9: *]+'", "return", s)
pos1 = s.find(title)
assert pos1 > 0, 'title not found'
pos1 = s.find('\n', pos1) + 1
assert pos1 > 0
pos2 = s.find("\n##", pos1)
if pos2 < 0:
return s[pos1:]
return s[pos1:pos2-1]
def __check_symbol_database(tmpdir, code):
testfile = os.path.join(tmpdir, 'test.cpp')
with open(testfile, 'w+t') as f:
f.write(code)
ret1, stdout1, _ = cppcheck(['-q', '--clang', '--debug-symdb', testfile])
ret2, stdout2, _ = cppcheck(['-q', '--debug-symdb', testfile])
assert 0 == ret1, stdout1
assert 0 == ret2, stdout2
assert __get_debug_section('### Symbol database', stdout1) == __get_debug_section('### Symbol database', stdout2)
def __check_ast(tmpdir, code):
testfile = os.path.join(tmpdir, 'test.cpp')
with open(testfile, 'w+t') as f:
f.write(code)
ret1, stdout1, _ = cppcheck(['-q', '--clang', '--debug-ast', testfile])
ret2, stdout2, _ = cppcheck(['-q', '--debug-ast', testfile])
assert 0 == ret1, stdout1
assert 0 == ret2, stdout1
assert __get_debug_section('##AST', stdout1) == __get_debug_section('##AST', stdout2)
def test_symbol_database_1(tmpdir):
__check_symbol_database(tmpdir, 'int main(){return 0;}')
def test_symbol_database_2(tmpdir):
__check_symbol_database(tmpdir, 'struct Foo { void f(); }; void Foo::f() {}')
def test_symbol_database_3(tmpdir):
__check_symbol_database(tmpdir, 'struct Fred { int a; }; int b; void f(int c, int d) { int e; }')
def test_symbol_database_4(tmpdir):
__check_symbol_database(tmpdir, 'void f(const int x) {}')
def test_symbol_database_5(tmpdir):
__check_symbol_database(tmpdir, 'void f(int);')
def test_symbol_database_6(tmpdir):
__check_symbol_database(tmpdir, 'inline static int foo(int x) { return x; }')
def test_symbol_database_7(tmpdir):
__check_symbol_database(tmpdir, 'struct S {int x;}; void f(struct S *s) {}')
def test_symbol_database_class_access_1(tmpdir):
__check_symbol_database(tmpdir, 'class Fred { void foo ( ) {} } ;')
def test_symbol_database_class_access_2(tmpdir):
__check_symbol_database(tmpdir, 'class Fred { protected: void foo ( ) {} } ;')
def test_symbol_database_class_access_3(tmpdir):
__check_symbol_database(tmpdir, 'class Fred { public: void foo ( ) {} } ;')
def test_symbol_database_operator(tmpdir):
__check_symbol_database(tmpdir, 'struct Fred { void operator=(int x); };')
def test_symbol_database_struct_1(tmpdir):
__check_symbol_database(tmpdir, 'struct S {};')
def test_ast_calculations(tmpdir):
__check_ast(tmpdir, 'int x = 5; int y = (x + 4) * 2;')
__check_ast(tmpdir, 'long long dostuff(int x) { return x ? 3 : 5; }')
def test_ast_control_flow(tmpdir):
__check_ast(tmpdir, 'void foo(int x) { if (x > 5){} }')
__check_ast(tmpdir, 'int dostuff() { for (int x = 0; x < 10; x++); }')
__check_ast(tmpdir, 'void foo(int x) { switch (x) {case 1: break; } }')
__check_ast(tmpdir, 'void foo(int a, int b, int c) { foo(a,b,c); }')
def test_ast(tmpdir):
__check_ast(tmpdir, 'struct S { int x; }; S* foo() { return new S(); }')
def test_log(tmpdir):
test_file = os.path.join(tmpdir, 'test.cpp')
with open(test_file, 'wt'):
pass
args = ['--clang', test_file]
out_lines = [
'Checking {} ...'.format(test_file).replace('\\', '/'),
]
assert_cppcheck(args, ec_exp=0, err_exp=[], out_exp=out_lines)
def test_warning(tmpdir): # #12424
test_file = os.path.join(tmpdir, 'test_2')
with open(test_file, 'wt') as f:
f.write('''void f() {}''')
exitcode, stdout, stderr = cppcheck(['-q', '--enable=warning', '--clang', test_file])
assert exitcode == 0, stderr # do not assert
assert stdout == ''
assert stderr == ''
def __test_cmd(tmp_path, file_name, extra_args, stdout_exp_1, content=''):
test_file = tmp_path / file_name
with open(test_file, 'wt') as f:
f.write(content)
args = [
'--enable=information',
'--verbose',
'--clang',
file_name
]
args += extra_args
if stdout_exp_1:
stdout_exp_1 += ' '
exitcode, stdout, stderr = cppcheck(args, cwd=tmp_path)
assert exitcode == 0, stderr if not stdout else stdout
assert stderr == ''
assert stdout.splitlines() == [
'Checking {} ...'.format(file_name),
'clang -fsyntax-only -Xclang -ast-dump -fno-color-diagnostics {}{}'.format(stdout_exp_1, file_name)
]
def test_cmd_c(tmp_path):
__test_cmd(tmp_path, 'test.c', [], '-x c')
def test_cmd_cpp(tmp_path):
__test_cmd(tmp_path, 'test.cpp', [], '-x c++')
# files with unknown extensions are treated as C++
def test_cmd_unk(tmp_path):
__test_cmd(tmp_path, 'test.cplusplus', [], '-x c++')
# headers are treated as C by default
def test_cmd_hdr(tmp_path):
__test_cmd(tmp_path, 'test.h', [], '-x c')
def test_cmd_hdr_probe(tmp_path):
__test_cmd(tmp_path, 'test.h', ['--cpp-header-probe'], '-x c++', '// -*- C++ -*-')
def test_cmd_inc(tmp_path):
inc_path = tmp_path / 'inc'
os.makedirs(inc_path)
__test_cmd(tmp_path, 'test.cpp',['-Iinc'], '-x c++ -Iinc/')
def test_cmd_def(tmp_path):
__test_cmd(tmp_path, 'test.cpp',['-DDEF'], '-x c++ -DDEF=1')
def test_cmd_include(tmp_path):
inc_file = tmp_path / 'inc.h'
with open(inc_file, 'wt'):
pass
__test_cmd(tmp_path, 'test.cpp',['--include=inc.h'], '-x c++ --include inc.h')
def test_cmd_enforce_c(tmp_path): # #13128
__test_cmd(tmp_path, 'test.cpp',['-x', 'c'], '-x c')
def test_cmd_enforce_cpp(tmp_path): # #13128
__test_cmd(tmp_path, 'test.c',['-x', 'c++'], '-x c++')
def test_cmd_std_c(tmp_path): # #13129
__test_cmd(tmp_path, 'test.cpp',['--std=c89', '--std=c++14'], '-x c++ -std=c++14')
# TODO: remove when we inject the build-dir into all tests
def test_cmd_std_c_builddir(tmp_path): # #13129
build_dir = tmp_path / 'b1'
os.makedirs(build_dir)
__test_cmd(tmp_path, 'test.cpp',['--std=c89', '--std=c++14', '--cppcheck-build-dir={}'.format(build_dir)], '-x c++ -std=c++14')
def test_cmd_std_cpp(tmp_path): # #13129
__test_cmd(tmp_path, 'test.c',['--std=c89', '--std=c++14'], '-x c -std=c89')
def test_cmd_std_c_enforce(tmp_path): # #13128/#13129
__test_cmd(tmp_path, 'test.cpp',['--language=c', '--std=c89', '--std=c++14'], '-x c -std=c89')
def test_cmd_std_cpp_enforce(tmp_path): # #13128/#13129
__test_cmd(tmp_path, 'test.c',['--language=c++', '--std=c89', '--std=c++14'], '-x c++ -std=c++14')
def test_cmd_std_c_enforce_alias(tmp_path): # #13128/#13129/#13130
__test_cmd(tmp_path, 'test.c',['--language=c', '--std=gnu99', '--std=gnu++11'], '-x c -std=gnu99')
def test_cmd_std_c_enforce_alias_2(tmp_path): # #13128/#13129/#13130
__test_cmd(tmp_path, 'test.c',['--language=c', '--std=iso9899:1999', '--std=gnu++11'], '-x c -std=iso9899:1999')
def test_cmd_std_cpp_enforce_alias(tmp_path): # #13128/#13129/#13130
__test_cmd(tmp_path, 'test.c',['--language=c++', '--std=gnu99', '--std=gnu++11'], '-x c++ -std=gnu++11')
def test_debug_clang_output(tmp_path):
test_file = tmp_path / 'test.c'
with open(test_file, 'wt') as f:
f.write(
"""
void f() {}
""")
args = [
'-q',
'--clang',
'--debug-clang-output',
str(test_file)
]
exitcode, stdout, stderr = cppcheck(args)
assert exitcode == 0, stderr if not stdout else stdout
assert stderr == ''
assert stdout.startswith('TranslationUnitDecl'), stdout
assert stdout.find(str(test_file)) != -1, stdout
def test_debug_clang_output_failure_exitcode(tmp_path):
# the given code will cause clang to fail with an exitcode
#
# Failed to execute 'clang -fsyntax-only -Xclang -ast-dump -fno-color-diagnostics -x c++ a.cpp 2>&1' - (exitcode: 1 / output: a.cpp:3:12: error: indirection requires pointer operand ('int' invalid)
# 3 | (void)(*0);
# | ^~
# 1 error generated.
# TranslationUnitDecl 0x6127d5d9d4e8 <<invalid sloc>> <invalid sloc>
# ...
test_file = tmp_path / 'test.c'
with open(test_file, 'wt') as f:
f.write(
"""void f()
{
(void)(*0);
}
""")
args = [
'-q',
'--clang',
'--debug-clang-output',
'--no-cppcheck-build-dir', # TODO: test without this?
str(test_file)
]
exitcode, stdout, stderr = cppcheck(args)
assert exitcode == 0, stderr if not stdout else stdout
stderr_lines = stderr.splitlines()
assert len(stderr_lines) > 5, stderr_lines
assert (stderr_lines[0] ==
"Failed to execute 'clang -fsyntax-only -Xclang -ast-dump -fno-color-diagnostics -x c {} 2>&1' - (exitcode: 1 / output: {}:3:12: error: indirection requires pointer operand ('int' invalid)".format(test_file, test_file))
assert stdout.find('TranslationUnitDecl') != -1, stdout
assert stdout.find(str(test_file)) != -1, stdout
|