# -*- coding: utf-8 -*- # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. # See https://llvm.org/LICENSE.txt for license information. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception import json import libear import libscanbuild.report as sut import unittest import os import os.path def run_bug_parse(content): with libear.TemporaryDirectory() as tmpdir: file_name = os.path.join(tmpdir, "test.html") with open(file_name, "w") as handle: handle.writelines(content) for bug in sut.parse_bug_html(file_name): return bug def run_crash_parse(content, preproc): with libear.TemporaryDirectory() as tmpdir: file_name = os.path.join(tmpdir, preproc + ".info.txt") with open(file_name, "w") as handle: handle.writelines(content) return sut.parse_crash(file_name) class ParseFileTest(unittest.TestCase): def test_parse_bug(self): content = [ "some header\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "some tails\n", ] result = run_bug_parse(content) self.assertEqual(result["bug_category"], "Logic error") self.assertEqual(result["bug_path_length"], 4) self.assertEqual(result["bug_line"], 5) self.assertEqual(result["bug_description"], "Division by zero") self.assertEqual(result["bug_type"], "Division by zero") self.assertEqual(result["bug_file"], "xx") def test_parse_bug_empty(self): content = [] result = run_bug_parse(content) self.assertEqual(result["bug_category"], "Other") self.assertEqual(result["bug_path_length"], 1) self.assertEqual(result["bug_line"], 0) def test_parse_crash(self): content = [ "/some/path/file.c\n", "Some very serious Error\n", "bla\n", "bla-bla\n", ] result = run_crash_parse(content, "file.i") self.assertEqual(result["source"], content[0].rstrip()) self.assertEqual(result["problem"], content[1].rstrip()) self.assertEqual(os.path.basename(result["file"]), "file.i") self.assertEqual(os.path.basename(result["info"]), "file.i.info.txt") self.assertEqual(os.path.basename(result["stderr"]), "file.i.stderr.txt") def test_parse_real_crash(self): import libscanbuild.analyze as sut2 import re with libear.TemporaryDirectory() as tmpdir: filename = os.path.join(tmpdir, "test.c") with open(filename, "w") as handle: handle.write("int main() { return 0") # produce failure report opts = { "clang": "clang", "directory": os.getcwd(), "flags": [], "file": filename, "output_dir": tmpdir, "language": "c", "error_type": "other_error", "error_output": "some output", "exit_code": 13, } sut2.report_failure(opts) # find the info file pp_file = None for root, _, files in os.walk(tmpdir): keys = [os.path.join(root, name) for name in files] for key in keys: if re.match(r"^(.*/)+clang(.*)\.i$", key): pp_file = key self.assertIsNot(pp_file, None) # read the failure report back result = sut.parse_crash(pp_file + ".info.txt") self.assertEqual(result["source"], filename) self.assertEqual(result["problem"], "Other Error") self.assertEqual(result["file"], pp_file) self.assertEqual(result["info"], pp_file + ".info.txt") self.assertEqual(result["stderr"], pp_file + ".stderr.txt") class ReportMethodTest(unittest.TestCase): def test_chop(self): self.assertEqual("file", sut.chop("/prefix", "/prefix/file")) self.assertEqual("file", sut.chop("/prefix/", "/prefix/file")) self.assertEqual("lib/file", sut.chop("/prefix/", "/prefix/lib/file")) self.assertEqual("/prefix/file", sut.chop("", "/prefix/file")) def test_chop_when_cwd(self): self.assertEqual("../src/file", sut.chop("/cwd", "/src/file")) self.assertEqual("../src/file", sut.chop("/prefix/cwd", "/prefix/src/file")) class GetPrefixFromCompilationDatabaseTest(unittest.TestCase): def test_with_different_filenames(self): self.assertEqual(sut.commonprefix(["/tmp/a.c", "/tmp/b.c"]), "/tmp") def test_with_different_dirnames(self): self.assertEqual(sut.commonprefix(["/tmp/abs/a.c", "/tmp/ack/b.c"]), "/tmp") def test_no_common_prefix(self): self.assertEqual(sut.commonprefix(["/tmp/abs/a.c", "/usr/ack/b.c"]), "/") def test_with_single_file(self): self.assertEqual(sut.commonprefix(["/tmp/a.c"]), "/tmp") def test_empty(self): self.assertEqual(sut.commonprefix([]), "") class MergeSarifTest(unittest.TestCase): def test_merging_sarif(self): sarif1 = { "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", "runs": [ { "artifacts": [ { "length": 100, "location": { "uri": "//clang/tools/scan-build-py/tests/unit/test_report.py" }, "mimeType": "text/plain", "roles": ["resultFile"], } ], "columnKind": "unicodeCodePoints", "results": [ { "codeFlows": [ { "threadFlows": [ { "locations": [ { "importance": "important", "location": { "message": { "text": "test message 1" }, "physicalLocation": { "artifactLocation": { "index": 0, "uri": "//clang/tools/scan-build-py/tests/unit/test_report.py", }, "region": { "endColumn": 5, "startColumn": 1, "startLine": 2, }, }, }, } ] } ] } ] }, { "codeFlows": [ { "threadFlows": [ { "locations": [ { "importance": "important", "location": { "message": { "text": "test message 2" }, "physicalLocation": { "artifactLocation": { "index": 0, "uri": "//clang/tools/scan-build-py/tests/unit/test_report.py", }, "region": { "endColumn": 23, "startColumn": 9, "startLine": 10, }, }, }, } ] } ] } ] }, ], "tool": { "driver": { "fullName": "clang static analyzer", "language": "en-US", "name": "clang", "rules": [ { "fullDescription": { "text": "test rule for merge sarif test" }, "helpUrl": "//clang/tools/scan-build-py/tests/unit/test_report.py", "id": "testId", "name": "testName", } ], "version": "test clang", } }, } ], "version": "2.1.0", } sarif2 = { "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json", "runs": [ { "artifacts": [ { "length": 1523, "location": { "uri": "//clang/tools/scan-build-py/tests/unit/test_report.py" }, "mimeType": "text/plain", "roles": ["resultFile"], } ], "columnKind": "unicodeCodePoints", "results": [ { "codeFlows": [ { "threadFlows": [ { "locations": [ { "importance": "important", "location": { "message": { "text": "test message 3" }, "physicalLocation": { "artifactLocation": { "index": 0, "uri": "//clang/tools/scan-build-py/tests/unit/test_report.py", }, "region": { "endColumn": 99, "startColumn": 99, "startLine": 17, }, }, }, } ] } ] } ] }, { "codeFlows": [ { "threadFlows": [ { "locations": [ { "importance": "important", "location": { "message": { "text": "test message 4" }, "physicalLocation": { "artifactLocation": { "index": 0, "uri": "//clang/tools/scan-build-py/tests/unit/test_report.py", }, "region": { "endColumn": 305, "startColumn": 304, "startLine": 1, }, }, }, } ] } ] } ] }, ], "tool": { "driver": { "fullName": "clang static analyzer", "language": "en-US", "name": "clang", "rules": [ { "fullDescription": { "text": "test rule for merge sarif test" }, "helpUrl": "//clang/tools/scan-build-py/tests/unit/test_report.py", "id": "testId", "name": "testName", } ], "version": "test clang", } }, } ], "version": "2.1.0", } contents = [sarif1, sarif2] with libear.TemporaryDirectory() as tmpdir: for idx, content in enumerate(contents): file_name = os.path.join(tmpdir, "results-{}.sarif".format(idx)) with open(file_name, "w") as handle: json.dump(content, handle) sut.merge_sarif_files(tmpdir, sort_files=True) self.assertIn("results-merged.sarif", os.listdir(tmpdir)) with open(os.path.join(tmpdir, "results-merged.sarif")) as f: merged = json.load(f) self.assertEqual(len(merged["runs"]), 2) self.assertEqual(len(merged["runs"][0]["results"]), 2) self.assertEqual(len(merged["runs"][1]["results"]), 2) expected = sarif1 for run in sarif2["runs"]: expected["runs"].append(run) self.assertEqual(merged, expected) def test_merge_updates_embedded_link(self): sarif1 = { "runs": [ { "results": [ { "codeFlows": [ { "message": { "text": "test message 1-1 [link](sarif:/runs/1/results/0) [link2](sarif:/runs/1/results/0)" }, "threadFlows": [ { "message": { "text": "test message 1-2 [link](sarif:/runs/1/results/0)" } } ], } ] } ] }, { "results": [ { "codeFlows": [ { "message": { "text": "test message 2-1 [link](sarif:/runs/0/results/0)" }, "threadFlows": [ { "message": { "text": "test message 2-2 [link](sarif:/runs/0/results/0)" } } ], } ] } ] }, ] } sarif2 = { "runs": [ { "results": [ { "codeFlows": [ { "message": { "text": "test message 3-1 [link](sarif:/runs/1/results/0) [link2](sarif:/runs/1/results/0)" }, "threadFlows": [ { "message": { "text": "test message 3-2 [link](sarif:/runs/1/results/0)" } } ], } ] } ], }, { "results": [ { "codeFlows": [ { "message": { "text": "test message 4-1 [link](sarif:/runs/0/results/0)" }, "threadFlows": [ { "message": { "text": "test message 4-2 [link](sarif:/runs/0/results/0)" } } ], } ] } ] }, ] } sarif3 = { "runs": [ { "results": [ { "codeFlows": [ { "message": { "text": "test message 5-1 [link](sarif:/runs/1/results/0) [link2](sarif:/runs/1/results/0)" }, "threadFlows": [ { "message": { "text": "test message 5-2 [link](sarif:/runs/1/results/0)" } } ], } ] } ], }, { "results": [ { "codeFlows": [ { "message": { "text": "test message 6-1 [link](sarif:/runs/0/results/0)" }, "threadFlows": [ { "message": { "text": "test message 6-2 [link](sarif:/runs/0/results/0)" } } ], } ] } ] }, ] } contents = [sarif1, sarif2, sarif3] with libear.TemporaryDirectory() as tmpdir: for idx, content in enumerate(contents): file_name = os.path.join(tmpdir, "results-{}.sarif".format(idx)) with open(file_name, "w") as handle: json.dump(content, handle) sut.merge_sarif_files(tmpdir, sort_files=True) self.assertIn("results-merged.sarif", os.listdir(tmpdir)) with open(os.path.join(tmpdir, "results-merged.sarif")) as f: merged = json.load(f) self.assertEqual(len(merged["runs"]), 6) code_flows = [ merged["runs"][x]["results"][0]["codeFlows"][0]["message"]["text"] for x in range(6) ] thread_flows = [ merged["runs"][x]["results"][0]["codeFlows"][0]["threadFlows"][0][ "message" ]["text"] for x in range(6) ] # The run index should be updated for the second and third sets of runs self.assertEqual( code_flows, [ "test message 1-1 [link](sarif:/runs/1/results/0) [link2](sarif:/runs/1/results/0)", "test message 2-1 [link](sarif:/runs/0/results/0)", "test message 3-1 [link](sarif:/runs/3/results/0) [link2](sarif:/runs/3/results/0)", "test message 4-1 [link](sarif:/runs/2/results/0)", "test message 5-1 [link](sarif:/runs/5/results/0) [link2](sarif:/runs/5/results/0)", "test message 6-1 [link](sarif:/runs/4/results/0)", ], ) self.assertEqual( thread_flows, [ "test message 1-2 [link](sarif:/runs/1/results/0)", "test message 2-2 [link](sarif:/runs/0/results/0)", "test message 3-2 [link](sarif:/runs/3/results/0)", "test message 4-2 [link](sarif:/runs/2/results/0)", "test message 5-2 [link](sarif:/runs/5/results/0)", "test message 6-2 [link](sarif:/runs/4/results/0)", ], ) def test_overflow_run_count(self): sarif1 = { "runs": [ { "results": [ {"message": {"text": "run 1-0 [link](sarif:/runs/1/results/0)"}} ] }, { "results": [ {"message": {"text": "run 1-1 [link](sarif:/runs/2/results/0)"}} ] }, { "results": [ {"message": {"text": "run 1-2 [link](sarif:/runs/3/results/0)"}} ] }, { "results": [ {"message": {"text": "run 1-3 [link](sarif:/runs/4/results/0)"}} ] }, { "results": [ {"message": {"text": "run 1-4 [link](sarif:/runs/5/results/0)"}} ] }, { "results": [ {"message": {"text": "run 1-5 [link](sarif:/runs/6/results/0)"}} ] }, { "results": [ {"message": {"text": "run 1-6 [link](sarif:/runs/7/results/0)"}} ] }, { "results": [ {"message": {"text": "run 1-7 [link](sarif:/runs/8/results/0)"}} ] }, { "results": [ {"message": {"text": "run 1-8 [link](sarif:/runs/9/results/0)"}} ] }, { "results": [ {"message": {"text": "run 1-9 [link](sarif:/runs/0/results/0)"}} ] }, ] } sarif2 = { "runs": [ { "results": [ { "message": { "text": "run 2-0 [link](sarif:/runs/1/results/0) [link2](sarif:/runs/2/results/0)" } } ] }, { "results": [ {"message": {"text": "run 2-1 [link](sarif:/runs/2/results/0)"}} ] }, { "results": [ {"message": {"text": "run 2-2 [link](sarif:/runs/3/results/0)"}} ] }, { "results": [ {"message": {"text": "run 2-3 [link](sarif:/runs/4/results/0)"}} ] }, { "results": [ {"message": {"text": "run 2-4 [link](sarif:/runs/5/results/0)"}} ] }, { "results": [ {"message": {"text": "run 2-5 [link](sarif:/runs/6/results/0)"}} ] }, { "results": [ {"message": {"text": "run 2-6 [link](sarif:/runs/7/results/0)"}} ] }, { "results": [ {"message": {"text": "run 2-7 [link](sarif:/runs/8/results/0)"}} ] }, { "results": [ {"message": {"text": "run 2-8 [link](sarif:/runs/9/results/0)"}} ] }, { "results": [ {"message": {"text": "run 2-9 [link](sarif:/runs/0/results/0)"}} ] }, ] } contents = [sarif1, sarif2] with libear.TemporaryDirectory() as tmpdir: for idx, content in enumerate(contents): file_name = os.path.join(tmpdir, "results-{}.sarif".format(idx)) with open(file_name, "w") as handle: json.dump(content, handle) sut.merge_sarif_files(tmpdir, sort_files=True) self.assertIn("results-merged.sarif", os.listdir(tmpdir)) with open(os.path.join(tmpdir, "results-merged.sarif")) as f: merged = json.load(f) self.assertEqual(len(merged["runs"]), 20) messages = [ merged["runs"][x]["results"][0]["message"]["text"] for x in range(20) ] self.assertEqual( messages, [ "run 1-0 [link](sarif:/runs/1/results/0)", "run 1-1 [link](sarif:/runs/2/results/0)", "run 1-2 [link](sarif:/runs/3/results/0)", "run 1-3 [link](sarif:/runs/4/results/0)", "run 1-4 [link](sarif:/runs/5/results/0)", "run 1-5 [link](sarif:/runs/6/results/0)", "run 1-6 [link](sarif:/runs/7/results/0)", "run 1-7 [link](sarif:/runs/8/results/0)", "run 1-8 [link](sarif:/runs/9/results/0)", "run 1-9 [link](sarif:/runs/0/results/0)", "run 2-0 [link](sarif:/runs/11/results/0) [link2](sarif:/runs/12/results/0)", "run 2-1 [link](sarif:/runs/12/results/0)", "run 2-2 [link](sarif:/runs/13/results/0)", "run 2-3 [link](sarif:/runs/14/results/0)", "run 2-4 [link](sarif:/runs/15/results/0)", "run 2-5 [link](sarif:/runs/16/results/0)", "run 2-6 [link](sarif:/runs/17/results/0)", "run 2-7 [link](sarif:/runs/18/results/0)", "run 2-8 [link](sarif:/runs/19/results/0)", "run 2-9 [link](sarif:/runs/10/results/0)", ], )