1 # -*- coding: utf-8 -*-
2 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
3 # See https://llvm.org/LICENSE.txt for license information.
4 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
11 import libscanbuild
.analyze
as sut
14 class ReportDirectoryTest(unittest
.TestCase
):
16 # Test that successive report directory names ascend in lexicographic
17 # order. This is required so that report directories from two runs of
18 # scan-build can be easily matched up to compare results.
19 def test_directory_name_comparison(self
):
20 with libear
.TemporaryDirectory() as tmpdir
, sut
.report_directory(
22 ) as report_dir1
, sut
.report_directory(
24 ) as report_dir2
, sut
.report_directory(
27 self
.assertLess(report_dir1
, report_dir2
)
28 self
.assertLess(report_dir2
, report_dir3
)
31 class FilteringFlagsTest(unittest
.TestCase
):
32 def test_language_captured(self
):
34 cmd
= ["clang", "-c", "source.c"] + flags
35 opts
= sut
.classify_parameters(cmd
)
36 return opts
["language"]
38 self
.assertEqual(None, test([]))
39 self
.assertEqual("c", test(["-x", "c"]))
40 self
.assertEqual("cpp", test(["-x", "cpp"]))
44 cmd
= ["clang", "-c", "source.c"] + flags
45 opts
= sut
.classify_parameters(cmd
)
46 return opts
["arch_list"]
48 self
.assertEqual([], test([]))
49 self
.assertEqual(["mips"], test(["-arch", "mips"]))
50 self
.assertEqual(["mips", "i386"], test(["-arch", "mips", "-arch", "i386"]))
52 def assertFlagsChanged(self
, expected
, flags
):
53 cmd
= ["clang", "-c", "source.c"] + flags
54 opts
= sut
.classify_parameters(cmd
)
55 self
.assertEqual(expected
, opts
["flags"])
57 def assertFlagsUnchanged(self
, flags
):
58 self
.assertFlagsChanged(flags
, flags
)
60 def assertFlagsFiltered(self
, flags
):
61 self
.assertFlagsChanged([], flags
)
63 def test_optimalizations_pass(self
):
64 self
.assertFlagsUnchanged(["-O"])
65 self
.assertFlagsUnchanged(["-O1"])
66 self
.assertFlagsUnchanged(["-Os"])
67 self
.assertFlagsUnchanged(["-O2"])
68 self
.assertFlagsUnchanged(["-O3"])
70 def test_include_pass(self
):
71 self
.assertFlagsUnchanged([])
72 self
.assertFlagsUnchanged(["-include", "/usr/local/include"])
73 self
.assertFlagsUnchanged(["-I."])
74 self
.assertFlagsUnchanged(["-I", "."])
75 self
.assertFlagsUnchanged(["-I/usr/local/include"])
76 self
.assertFlagsUnchanged(["-I", "/usr/local/include"])
77 self
.assertFlagsUnchanged(["-I/opt", "-I", "/opt/otp/include"])
78 self
.assertFlagsUnchanged(["-isystem", "/path"])
79 self
.assertFlagsUnchanged(["-isystem=/path"])
81 def test_define_pass(self
):
82 self
.assertFlagsUnchanged(["-DNDEBUG"])
83 self
.assertFlagsUnchanged(["-UNDEBUG"])
84 self
.assertFlagsUnchanged(["-Dvar1=val1", "-Dvar2=val2"])
85 self
.assertFlagsUnchanged(['-Dvar="val ues"'])
87 def test_output_filtered(self
):
88 self
.assertFlagsFiltered(["-o", "source.o"])
90 def test_some_warning_filtered(self
):
91 self
.assertFlagsFiltered(["-Wall"])
92 self
.assertFlagsFiltered(["-Wnoexcept"])
93 self
.assertFlagsFiltered(["-Wreorder", "-Wunused", "-Wundef"])
94 self
.assertFlagsUnchanged(["-Wno-reorder", "-Wno-unused"])
96 def test_compile_only_flags_pass(self
):
97 self
.assertFlagsUnchanged(["-std=C99"])
98 self
.assertFlagsUnchanged(["-nostdinc"])
99 self
.assertFlagsUnchanged(["-isystem", "/image/debian"])
100 self
.assertFlagsUnchanged(["-iprefix", "/usr/local"])
101 self
.assertFlagsUnchanged(["-iquote=me"])
102 self
.assertFlagsUnchanged(["-iquote", "me"])
104 def test_compile_and_link_flags_pass(self
):
105 self
.assertFlagsUnchanged(["-fsinged-char"])
106 self
.assertFlagsUnchanged(["-fPIC"])
107 self
.assertFlagsUnchanged(["-stdlib=libc++"])
108 self
.assertFlagsUnchanged(["--sysroot", "/"])
109 self
.assertFlagsUnchanged(["-isysroot", "/"])
111 def test_some_flags_filtered(self
):
112 self
.assertFlagsFiltered(["-g"])
113 self
.assertFlagsFiltered(["-fsyntax-only"])
114 self
.assertFlagsFiltered(["-save-temps"])
115 self
.assertFlagsFiltered(["-init", "my_init"])
116 self
.assertFlagsFiltered(["-sectorder", "a", "b", "c"])
124 def call(self
, params
):
129 class RunAnalyzerTest(unittest
.TestCase
):
131 def run_analyzer(content
, failures_report
, output_format
="plist"):
132 with libear
.TemporaryDirectory() as tmpdir
:
133 filename
= os
.path
.join(tmpdir
, "test.cpp")
134 with
open(filename
, "w") as handle
:
135 handle
.write(content
)
139 "directory": os
.getcwd(),
143 "output_dir": tmpdir
,
144 "output_format": output_format
,
145 "output_failures": failures_report
,
148 result
= sut
.run_analyzer(opts
, spy
.call
)
150 for entry
in os
.listdir(tmpdir
):
151 output_files
.append(entry
)
152 return (result
, spy
.arg
, output_files
)
154 def test_run_analyzer(self
):
155 content
= "int div(int n, int d) { return n / d; }"
156 (result
, fwds
, _
) = RunAnalyzerTest
.run_analyzer(content
, False)
157 self
.assertEqual(None, fwds
)
158 self
.assertEqual(0, result
["exit_code"])
160 def test_run_analyzer_crash(self
):
161 content
= "int div(int n, int d) { return n / d }"
162 (result
, fwds
, _
) = RunAnalyzerTest
.run_analyzer(content
, False)
163 self
.assertEqual(None, fwds
)
164 self
.assertEqual(1, result
["exit_code"])
166 def test_run_analyzer_crash_and_forwarded(self
):
167 content
= "int div(int n, int d) { return n / d }"
168 (_
, fwds
, _
) = RunAnalyzerTest
.run_analyzer(content
, True)
169 self
.assertEqual(1, fwds
["exit_code"])
170 self
.assertTrue(len(fwds
["error_output"]) > 0)
172 def test_run_analyzer_with_sarif(self
):
173 content
= "int div(int n, int d) { return n / d; }"
174 (result
, fwds
, output_files
) = RunAnalyzerTest
.run_analyzer(
175 content
, False, output_format
="sarif"
177 self
.assertEqual(None, fwds
)
178 self
.assertEqual(0, result
["exit_code"])
180 pattern
= re
.compile(r
"^result-.+\.sarif$")
181 for f
in output_files
:
182 if re
.match(pattern
, f
):
184 self
.fail("no result sarif files found in output")
187 class ReportFailureTest(unittest
.TestCase
):
188 def assertUnderFailures(self
, path
):
189 self
.assertEqual("failures", os
.path
.basename(os
.path
.dirname(path
)))
191 def test_report_failure_create_files(self
):
192 with libear
.TemporaryDirectory() as tmpdir
:
194 filename
= os
.path
.join(tmpdir
, "test.c")
195 with
open(filename
, "w") as handle
:
196 handle
.write("int main() { return 0")
197 uname_msg
= " ".join(os
.uname()) + os
.linesep
198 error_msg
= "this is my error output"
202 "directory": os
.getcwd(),
205 "output_dir": tmpdir
,
207 "error_type": "other_error",
208 "error_output": error_msg
,
211 sut
.report_failure(opts
)
215 for root
, _
, files
in os
.walk(tmpdir
):
216 keys
= [os
.path
.join(root
, name
) for name
in files
]
218 with
open(key
, "r") as handle
:
219 result
[key
] = handle
.readlines()
220 if re
.match(r
"^(.*/)+clang(.*)\.i$", key
):
223 # prepocessor file generated
224 self
.assertUnderFailures(pp_file
)
225 # info file generated and content dumped
226 info_file
= pp_file
+ ".info.txt"
227 self
.assertTrue(info_file
in result
)
228 self
.assertEqual("Other Error\n", result
[info_file
][1])
229 self
.assertEqual(uname_msg
, result
[info_file
][3])
230 # error file generated and content dumped
231 error_file
= pp_file
+ ".stderr.txt"
232 self
.assertTrue(error_file
in result
)
233 self
.assertEqual([error_msg
], result
[error_file
])
236 class AnalyzerTest(unittest
.TestCase
):
237 def test_nodebug_macros_appended(self
):
240 opts
= {"flags": flags
, "force_debug": True}
241 self
.assertEqual(spy
.success
, sut
.filter_debug_flags(opts
, spy
.call
))
242 return spy
.arg
["flags"]
244 self
.assertEqual(["-UNDEBUG"], test([]))
245 self
.assertEqual(["-DNDEBUG", "-UNDEBUG"], test(["-DNDEBUG"]))
246 self
.assertEqual(["-DSomething", "-UNDEBUG"], test(["-DSomething"]))
248 def test_set_language_fall_through(self
):
249 def language(expected
, input):
251 input.update({"compiler": "c", "file": "test.c"})
252 self
.assertEqual(spy
.success
, sut
.language_check(input, spy
.call
))
253 self
.assertEqual(expected
, spy
.arg
["language"])
255 language("c", {"language": "c", "flags": []})
256 language("c++", {"language": "c++", "flags": []})
258 def test_set_language_stops_on_not_supported(self
):
260 input = {"compiler": "c", "flags": [], "file": "test.java", "language": "java"}
261 self
.assertIsNone(sut
.language_check(input, spy
.call
))
262 self
.assertIsNone(spy
.arg
)
264 def test_set_language_sets_flags(self
):
265 def flags(expected
, input):
267 input.update({"compiler": "c", "file": "test.c"})
268 self
.assertEqual(spy
.success
, sut
.language_check(input, spy
.call
))
269 self
.assertEqual(expected
, spy
.arg
["flags"])
271 flags(["-x", "c"], {"language": "c", "flags": []})
272 flags(["-x", "c++"], {"language": "c++", "flags": []})
274 def test_set_language_from_filename(self
):
275 def language(expected
, input):
277 input.update({"language": None, "flags": []})
278 self
.assertEqual(spy
.success
, sut
.language_check(input, spy
.call
))
279 self
.assertEqual(expected
, spy
.arg
["language"])
281 language("c", {"file": "file.c", "compiler": "c"})
282 language("c++", {"file": "file.c", "compiler": "c++"})
283 language("c++", {"file": "file.cxx", "compiler": "c"})
284 language("c++", {"file": "file.cxx", "compiler": "c++"})
285 language("c++", {"file": "file.cpp", "compiler": "c++"})
286 language("c-cpp-output", {"file": "file.i", "compiler": "c"})
287 language("c++-cpp-output", {"file": "file.i", "compiler": "c++"})
289 def test_arch_loop_sets_flags(self
):
292 input = {"flags": [], "arch_list": archs
}
293 sut
.arch_check(input, spy
.call
)
294 return spy
.arg
["flags"]
296 self
.assertEqual([], flags([]))
297 self
.assertEqual(["-arch", "i386"], flags(["i386"]))
298 self
.assertEqual(["-arch", "i386"], flags(["i386", "ppc"]))
299 self
.assertEqual(["-arch", "sparc"], flags(["i386", "sparc"]))
301 def test_arch_loop_stops_on_not_supported(self
):
304 input = {"flags": [], "arch_list": archs
}
305 self
.assertIsNone(sut
.arch_check(input, spy
.call
))
306 self
.assertIsNone(spy
.arg
)
313 def method_without_expecteds(opts
):
317 @sut.require(["this", "that"])
318 def method_with_expecteds(opts
):
323 def method_exception_from_inside(opts
):
324 raise Exception("here is one")
327 class RequireDecoratorTest(unittest
.TestCase
):
328 def test_method_without_expecteds(self
):
329 self
.assertEqual(method_without_expecteds(dict()), 0)
330 self
.assertEqual(method_without_expecteds({}), 0)
331 self
.assertEqual(method_without_expecteds({"this": 2}), 0)
332 self
.assertEqual(method_without_expecteds({"that": 3}), 0)
334 def test_method_with_expecteds(self
):
335 self
.assertRaises(KeyError, method_with_expecteds
, dict())
336 self
.assertRaises(KeyError, method_with_expecteds
, {})
337 self
.assertRaises(KeyError, method_with_expecteds
, {"this": 2})
338 self
.assertRaises(KeyError, method_with_expecteds
, {"that": 3})
339 self
.assertEqual(method_with_expecteds({"this": 0, "that": 3}), 0)
341 def test_method_exception_not_caught(self
):
342 self
.assertRaises(Exception, method_exception_from_inside
, dict())
345 class PrefixWithTest(unittest
.TestCase
):
346 def test_gives_empty_on_empty(self
):
347 res
= sut
.prefix_with(0, [])
348 self
.assertFalse(res
)
350 def test_interleaves_prefix(self
):
351 res
= sut
.prefix_with(0, [1, 2, 3])
352 self
.assertListEqual([0, 1, 0, 2, 0, 3], res
)
355 class MergeCtuMapTest(unittest
.TestCase
):
356 def test_no_map_gives_empty(self
):
357 pairs
= sut
.create_global_ctu_extdef_map([])
358 self
.assertFalse(pairs
)
360 def test_multiple_maps_merged(self
):
362 "c:@F@fun1#I# ast/fun1.c.ast",
363 "c:@F@fun2#I# ast/fun2.c.ast",
364 "c:@F@fun3#I# ast/fun3.c.ast",
366 pairs
= sut
.create_global_ctu_extdef_map(concat_map
)
367 self
.assertTrue(("c:@F@fun1#I#", "ast/fun1.c.ast") in pairs
)
368 self
.assertTrue(("c:@F@fun2#I#", "ast/fun2.c.ast") in pairs
)
369 self
.assertTrue(("c:@F@fun3#I#", "ast/fun3.c.ast") in pairs
)
370 self
.assertEqual(3, len(pairs
))
372 def test_not_unique_func_left_out(self
):
374 "c:@F@fun1#I# ast/fun1.c.ast",
375 "c:@F@fun2#I# ast/fun2.c.ast",
376 "c:@F@fun1#I# ast/fun7.c.ast",
378 pairs
= sut
.create_global_ctu_extdef_map(concat_map
)
379 self
.assertFalse(("c:@F@fun1#I#", "ast/fun1.c.ast") in pairs
)
380 self
.assertFalse(("c:@F@fun1#I#", "ast/fun7.c.ast") in pairs
)
381 self
.assertTrue(("c:@F@fun2#I#", "ast/fun2.c.ast") in pairs
)
382 self
.assertEqual(1, len(pairs
))
384 def test_duplicates_are_kept(self
):
386 "c:@F@fun1#I# ast/fun1.c.ast",
387 "c:@F@fun2#I# ast/fun2.c.ast",
388 "c:@F@fun1#I# ast/fun1.c.ast",
390 pairs
= sut
.create_global_ctu_extdef_map(concat_map
)
391 self
.assertTrue(("c:@F@fun1#I#", "ast/fun1.c.ast") in pairs
)
392 self
.assertTrue(("c:@F@fun2#I#", "ast/fun2.c.ast") in pairs
)
393 self
.assertEqual(2, len(pairs
))
395 def test_space_handled_in_source(self
):
396 concat_map
= ["c:@F@fun1#I# ast/f un.c.ast"]
397 pairs
= sut
.create_global_ctu_extdef_map(concat_map
)
398 self
.assertTrue(("c:@F@fun1#I#", "ast/f un.c.ast") in pairs
)
399 self
.assertEqual(1, len(pairs
))
402 class ExtdefMapSrcToAstTest(unittest
.TestCase
):
403 def test_empty_gives_empty(self
):
404 fun_ast_lst
= sut
.extdef_map_list_src_to_ast([])
405 self
.assertFalse(fun_ast_lst
)
407 def test_sources_to_asts(self
):
409 "c:@F@f1#I# " + os
.path
.join(os
.sep
+ "path", "f1.c"),
410 "c:@F@f2#I# " + os
.path
.join(os
.sep
+ "path", "f2.c"),
412 fun_ast_lst
= sut
.extdef_map_list_src_to_ast(fun_src_lst
)
414 "c:@F@f1#I# " + os
.path
.join("ast", "path", "f1.c.ast") in fun_ast_lst
417 "c:@F@f2#I# " + os
.path
.join("ast", "path", "f2.c.ast") in fun_ast_lst
419 self
.assertEqual(2, len(fun_ast_lst
))
421 def test_spaces_handled(self
):
422 fun_src_lst
= ["c:@F@f1#I# " + os
.path
.join(os
.sep
+ "path", "f 1.c")]
423 fun_ast_lst
= sut
.extdef_map_list_src_to_ast(fun_src_lst
)
425 "c:@F@f1#I# " + os
.path
.join("ast", "path", "f 1.c.ast") in fun_ast_lst
427 self
.assertEqual(1, len(fun_ast_lst
))