Skip to content

Commit 6e1e5fd

Browse files
committed
1
1 parent dfad65c commit 6e1e5fd

1 file changed

Lines changed: 305 additions & 0 deletions

File tree

test/cli/clang-import_test.py

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
2+
# python -m pytest test-clang-import.py
3+
4+
import os
5+
import re
6+
import subprocess
7+
import sys
8+
import pytest
9+
from testutils import cppcheck, assert_cppcheck
10+
11+
try:
12+
# TODO: handle exitcode?
13+
subprocess.call(['clang', '--version'])
14+
except OSError:
15+
pytest.skip("'clang' does not exist", allow_module_level=True)
16+
17+
18+
# the IDs differ with Visual Studio
19+
if sys.platform == 'win32':
20+
pytest.skip(allow_module_level=True)
21+
22+
23+
def __get_debug_section(title, stdout):
24+
s = re.sub(r'0x[0-9a-fA-F]+', '0x12345678', stdout)
25+
s = re.sub(r'nestedIn: Struct', 'nestedIn: Class', s)
26+
s = re.sub(r'classDef: struct', 'classDef: class', s)
27+
s = re.sub(r'isInline: [a-z]+', 'isInline: ---', s)
28+
s = re.sub(r'needInitialization: .*', 'needInitialization: ---', s)
29+
s = re.sub(r'functionOf: .*', 'functionOf: ---', s)
30+
s = re.sub(r'0x12345678 Struct', '0x12345678 Class', s)
31+
32+
if title == '##AST':
33+
# TODO set types
34+
s = re.sub(r"return '[a-zA-Z0-9: *]+'", "return", s)
35+
36+
pos1 = s.find(title)
37+
assert pos1 > 0, 'title not found'
38+
pos1 = s.find('\n', pos1) + 1
39+
assert pos1 > 0
40+
pos2 = s.find("\n##", pos1)
41+
if pos2 < 0:
42+
return s[pos1:]
43+
return s[pos1:pos2-1]
44+
45+
46+
def __check_symbol_database(tmpdir, code):
47+
testfile = os.path.join(tmpdir, 'test.cpp')
48+
with open(testfile, 'w+t') as f:
49+
f.write(code)
50+
ret1, stdout1, _ = cppcheck(['--clang', '--debug-symdb', testfile])
51+
ret2, stdout2, _ = cppcheck(['--debug-symdb', testfile])
52+
assert 0 == ret1, stdout1
53+
assert 0 == ret2, stdout2
54+
assert __get_debug_section('### Symbol database', stdout1) == __get_debug_section('### Symbol database', stdout2)
55+
56+
57+
def __check_ast(tmpdir, code):
58+
testfile = os.path.join(tmpdir, 'test.cpp')
59+
with open(testfile, 'w+t') as f:
60+
f.write(code)
61+
ret1, stdout1, _ = cppcheck(['--clang', '--debug-ast', testfile])
62+
ret2, stdout2, _ = cppcheck(['--debug-ast', testfile])
63+
assert 0 == ret1, stdout1
64+
assert 0 == ret2, stdout1
65+
assert __get_debug_section('##AST', stdout1) == __get_debug_section('##AST', stdout2)
66+
67+
68+
69+
def test_symbol_database_1(tmpdir):
70+
__check_symbol_database(tmpdir, 'int main(){return 0;}')
71+
72+
def test_symbol_database_2(tmpdir):
73+
__check_symbol_database(tmpdir, 'struct Foo { void f(); }; void Foo::f() {}')
74+
75+
def test_symbol_database_3(tmpdir):
76+
__check_symbol_database(tmpdir, 'struct Fred { int a; }; int b; void f(int c, int d) { int e; }')
77+
78+
def test_symbol_database_4(tmpdir):
79+
__check_symbol_database(tmpdir, 'void f(const int x) {}')
80+
81+
def test_symbol_database_5(tmpdir):
82+
__check_symbol_database(tmpdir, 'void f(int);')
83+
84+
def test_symbol_database_6(tmpdir):
85+
__check_symbol_database(tmpdir, 'inline static int foo(int x) { return x; }')
86+
87+
def test_symbol_database_7(tmpdir):
88+
__check_symbol_database(tmpdir, 'struct S {int x;}; void f(struct S *s) {}')
89+
90+
def test_symbol_database_class_access_1(tmpdir):
91+
__check_symbol_database(tmpdir, 'class Fred { void foo ( ) {} } ;')
92+
93+
def test_symbol_database_class_access_2(tmpdir):
94+
__check_symbol_database(tmpdir, 'class Fred { protected: void foo ( ) {} } ;')
95+
96+
def test_symbol_database_class_access_3(tmpdir):
97+
__check_symbol_database(tmpdir, 'class Fred { public: void foo ( ) {} } ;')
98+
99+
def test_symbol_database_operator(tmpdir):
100+
__check_symbol_database(tmpdir, 'struct Fred { void operator=(int x); };')
101+
102+
def test_symbol_database_struct_1(tmpdir):
103+
__check_symbol_database(tmpdir, 'struct S {};')
104+
105+
def test_ast_calculations(tmpdir):
106+
__check_ast(tmpdir, 'int x = 5; int y = (x + 4) * 2;')
107+
__check_ast(tmpdir, 'long long dostuff(int x) { return x ? 3 : 5; }')
108+
109+
def test_ast_control_flow(tmpdir):
110+
__check_ast(tmpdir, 'void foo(int x) { if (x > 5){} }')
111+
__check_ast(tmpdir, 'int dostuff() { for (int x = 0; x < 10; x++); }')
112+
__check_ast(tmpdir, 'void foo(int x) { switch (x) {case 1: break; } }')
113+
__check_ast(tmpdir, 'void foo(int a, int b, int c) { foo(a,b,c); }')
114+
115+
def test_ast(tmpdir):
116+
__check_ast(tmpdir, 'struct S { int x; }; S* foo() { return new S(); }')
117+
118+
def test_log(tmpdir):
119+
test_file = os.path.join(tmpdir, 'test.cpp')
120+
with open(test_file, 'wt'):
121+
pass
122+
123+
args = ['--clang', test_file]
124+
out_lines = [
125+
'Checking {} ...'.format(test_file).replace('\\', '/'),
126+
]
127+
128+
assert_cppcheck(args, ec_exp=0, err_exp=[], out_exp=out_lines)
129+
130+
131+
def test_warning(tmpdir): # #12424
132+
test_file = os.path.join(tmpdir, 'test_2')
133+
with open(test_file, 'wt') as f:
134+
f.write('''void f() {}''')
135+
136+
exitcode, stdout, stderr = cppcheck(['-q', '--enable=warning', '--clang', test_file])
137+
assert exitcode == 0, stderr # do not assert
138+
assert stdout == ''
139+
assert stderr == ''
140+
141+
142+
def __test_cmd(tmp_path, file_name, extra_args, stdout_exp_1, content=''):
143+
test_file = tmp_path / file_name
144+
with open(test_file, 'wt') as f:
145+
f.write(content)
146+
147+
args = [
148+
'--enable=information',
149+
'--verbose',
150+
'--clang',
151+
file_name
152+
]
153+
154+
args += extra_args
155+
156+
if stdout_exp_1:
157+
stdout_exp_1 += ' '
158+
159+
exitcode, stdout, stderr = cppcheck(args, cwd=tmp_path)
160+
assert exitcode == 0, stderr if not stdout else stdout
161+
assert stderr == ''
162+
assert stdout.splitlines() == [
163+
'Checking {} ...'.format(file_name),
164+
'clang -fsyntax-only -Xclang -ast-dump -fno-color-diagnostics {}{}'.format(stdout_exp_1, file_name)
165+
]
166+
167+
168+
def test_cmd_c(tmp_path):
169+
__test_cmd(tmp_path, 'test.c', [], '-x c')
170+
171+
172+
def test_cmd_cpp(tmp_path):
173+
__test_cmd(tmp_path, 'test.cpp', [], '-x c++')
174+
175+
176+
# files with unknown extensions are treated as C++
177+
def test_cmd_unk(tmp_path):
178+
__test_cmd(tmp_path, 'test.cplusplus', [], '-x c++')
179+
180+
181+
# headers are treated as C by default
182+
def test_cmd_hdr(tmp_path):
183+
__test_cmd(tmp_path, 'test.h', [], '-x c')
184+
185+
186+
def test_cmd_hdr_probe(tmp_path):
187+
__test_cmd(tmp_path, 'test.h', ['--cpp-header-probe'], '-x c++', '// -*- C++ -*-')
188+
189+
190+
def test_cmd_inc(tmp_path):
191+
inc_path = tmp_path / 'inc'
192+
os.makedirs(inc_path)
193+
__test_cmd(tmp_path, 'test.cpp',['-Iinc'], '-x c++ -Iinc/')
194+
195+
196+
def test_cmd_def(tmp_path):
197+
__test_cmd(tmp_path, 'test.cpp',['-DDEF'], '-x c++ -DDEF=1')
198+
199+
200+
def test_cmd_include(tmp_path):
201+
inc_file = tmp_path / 'inc.h'
202+
with open(inc_file, 'wt'):
203+
pass
204+
__test_cmd(tmp_path, 'test.cpp',['--include=inc.h'], '-x c++ --include inc.h')
205+
206+
207+
def test_cmd_enforce_c(tmp_path): # #13128
208+
__test_cmd(tmp_path, 'test.cpp',['-x', 'c'], '-x c')
209+
210+
211+
def test_cmd_enforce_cpp(tmp_path): # #13128
212+
__test_cmd(tmp_path, 'test.c',['-x', 'c++'], '-x c++')
213+
214+
215+
def test_cmd_std_c(tmp_path): # #13129
216+
__test_cmd(tmp_path, 'test.cpp',['--std=c89', '--std=c++14'], '-x c++ -std=c++14')
217+
218+
219+
# TODO: remove when we inject the build-dir into all tests
220+
def test_cmd_std_c_builddir(tmp_path): # #13129
221+
build_dir = tmp_path / 'b1'
222+
os.makedirs(build_dir)
223+
__test_cmd(tmp_path, 'test.cpp',['--std=c89', '--std=c++14', '--cppcheck-build-dir={}'.format(build_dir)], '-x c++ -std=c++14')
224+
225+
226+
def test_cmd_std_cpp(tmp_path): # #13129
227+
__test_cmd(tmp_path, 'test.c',['--std=c89', '--std=c++14'], '-x c -std=c89')
228+
229+
230+
def test_cmd_std_c_enforce(tmp_path): # #13128/#13129
231+
__test_cmd(tmp_path, 'test.cpp',['--language=c', '--std=c89', '--std=c++14'], '-x c -std=c89')
232+
233+
234+
def test_cmd_std_cpp_enforce(tmp_path): # #13128/#13129
235+
__test_cmd(tmp_path, 'test.c',['--language=c++', '--std=c89', '--std=c++14'], '-x c++ -std=c++14')
236+
237+
238+
def test_cmd_std_c_enforce_alias(tmp_path): # #13128/#13129/#13130
239+
__test_cmd(tmp_path, 'test.c',['--language=c', '--std=gnu99', '--std=gnu++11'], '-x c -std=gnu99')
240+
241+
242+
def test_cmd_std_c_enforce_alias_2(tmp_path): # #13128/#13129/#13130
243+
__test_cmd(tmp_path, 'test.c',['--language=c', '--std=iso9899:1999', '--std=gnu++11'], '-x c -std=iso9899:1999')
244+
245+
246+
def test_cmd_std_cpp_enforce_alias(tmp_path): # #13128/#13129/#13130
247+
__test_cmd(tmp_path, 'test.c',['--language=c++', '--std=gnu99', '--std=gnu++11'], '-x c++ -std=gnu++11')
248+
249+
250+
def test_debug_clang_output(tmp_path):
251+
test_file = tmp_path / 'test.c'
252+
with open(test_file, 'wt') as f:
253+
f.write(
254+
"""
255+
void f() {}
256+
""")
257+
258+
args = [
259+
'-q',
260+
'--clang',
261+
'--debug-clang-output',
262+
str(test_file)
263+
]
264+
265+
exitcode, stdout, stderr = cppcheck(args)
266+
assert exitcode == 0, stderr if not stdout else stdout
267+
assert stderr == ''
268+
assert stdout.startswith('TranslationUnitDecl'), stdout
269+
assert stdout.find(str(test_file)) != -1, stdout
270+
271+
272+
def test_debug_clang_output_failure_exitcode(tmp_path):
273+
# the given code will cause clang to fail with an exitcode
274+
#
275+
# 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)
276+
# 3 | (void)(*0);
277+
# | ^~
278+
# 1 error generated.
279+
# TranslationUnitDecl 0x6127d5d9d4e8 <<invalid sloc>> <invalid sloc>
280+
# ...
281+
test_file = tmp_path / 'test.c'
282+
with open(test_file, 'wt') as f:
283+
f.write(
284+
"""void f()
285+
{
286+
(void)(*0);
287+
}
288+
""")
289+
290+
args = [
291+
'-q',
292+
'--clang',
293+
'--debug-clang-output',
294+
'--no-cppcheck-build-dir', # TODO: test without this?
295+
str(test_file)
296+
]
297+
298+
exitcode, stdout, stderr = cppcheck(args)
299+
assert exitcode == 0, stderr if not stdout else stdout
300+
stderr_lines = stderr.splitlines()
301+
assert len(stderr_lines) > 5, stderr_lines
302+
assert (stderr_lines[0] ==
303+
"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))
304+
assert stdout.find('TranslationUnitDecl') != -1, stdout
305+
assert stdout.find(str(test_file)) != -1, stdout

0 commit comments

Comments
 (0)