710 lines · python
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-exception5 6import json7import libear8import libscanbuild.report as sut9import unittest10import os11import os.path12 13 14def run_bug_parse(content):15 with libear.TemporaryDirectory() as tmpdir:16 file_name = os.path.join(tmpdir, "test.html")17 with open(file_name, "w") as handle:18 handle.writelines(content)19 for bug in sut.parse_bug_html(file_name):20 return bug21 22 23def run_crash_parse(content, preproc):24 with libear.TemporaryDirectory() as tmpdir:25 file_name = os.path.join(tmpdir, preproc + ".info.txt")26 with open(file_name, "w") as handle:27 handle.writelines(content)28 return sut.parse_crash(file_name)29 30 31class ParseFileTest(unittest.TestCase):32 def test_parse_bug(self):33 content = [34 "some header\n",35 "<!-- BUGDESC Division by zero -->\n",36 "<!-- BUGTYPE Division by zero -->\n",37 "<!-- BUGCATEGORY Logic error -->\n",38 "<!-- BUGFILE xx -->\n",39 "<!-- BUGLINE 5 -->\n",40 "<!-- BUGCOLUMN 22 -->\n",41 "<!-- BUGPATHLENGTH 4 -->\n",42 "<!-- BUGMETAEND -->\n",43 "<!-- REPORTHEADER -->\n",44 "some tails\n",45 ]46 result = run_bug_parse(content)47 self.assertEqual(result["bug_category"], "Logic error")48 self.assertEqual(result["bug_path_length"], 4)49 self.assertEqual(result["bug_line"], 5)50 self.assertEqual(result["bug_description"], "Division by zero")51 self.assertEqual(result["bug_type"], "Division by zero")52 self.assertEqual(result["bug_file"], "xx")53 54 def test_parse_bug_empty(self):55 content = []56 result = run_bug_parse(content)57 self.assertEqual(result["bug_category"], "Other")58 self.assertEqual(result["bug_path_length"], 1)59 self.assertEqual(result["bug_line"], 0)60 61 def test_parse_crash(self):62 content = [63 "/some/path/file.c\n",64 "Some very serious Error\n",65 "bla\n",66 "bla-bla\n",67 ]68 result = run_crash_parse(content, "file.i")69 self.assertEqual(result["source"], content[0].rstrip())70 self.assertEqual(result["problem"], content[1].rstrip())71 self.assertEqual(os.path.basename(result["file"]), "file.i")72 self.assertEqual(os.path.basename(result["info"]), "file.i.info.txt")73 self.assertEqual(os.path.basename(result["stderr"]), "file.i.stderr.txt")74 75 def test_parse_real_crash(self):76 import libscanbuild.analyze as sut277 import re78 79 with libear.TemporaryDirectory() as tmpdir:80 filename = os.path.join(tmpdir, "test.c")81 with open(filename, "w") as handle:82 handle.write("int main() { return 0")83 # produce failure report84 opts = {85 "clang": "clang",86 "directory": os.getcwd(),87 "flags": [],88 "file": filename,89 "output_dir": tmpdir,90 "language": "c",91 "error_type": "other_error",92 "error_output": "some output",93 "exit_code": 13,94 }95 sut2.report_failure(opts)96 # find the info file97 pp_file = None98 for root, _, files in os.walk(tmpdir):99 keys = [os.path.join(root, name) for name in files]100 for key in keys:101 if re.match(r"^(.*/)+clang(.*)\.i$", key):102 pp_file = key103 self.assertIsNot(pp_file, None)104 # read the failure report back105 result = sut.parse_crash(pp_file + ".info.txt")106 self.assertEqual(result["source"], filename)107 self.assertEqual(result["problem"], "Other Error")108 self.assertEqual(result["file"], pp_file)109 self.assertEqual(result["info"], pp_file + ".info.txt")110 self.assertEqual(result["stderr"], pp_file + ".stderr.txt")111 112 113class ReportMethodTest(unittest.TestCase):114 def test_chop(self):115 self.assertEqual("file", sut.chop("/prefix", "/prefix/file"))116 self.assertEqual("file", sut.chop("/prefix/", "/prefix/file"))117 self.assertEqual("lib/file", sut.chop("/prefix/", "/prefix/lib/file"))118 self.assertEqual("/prefix/file", sut.chop("", "/prefix/file"))119 120 def test_chop_when_cwd(self):121 self.assertEqual("../src/file", sut.chop("/cwd", "/src/file"))122 self.assertEqual("../src/file", sut.chop("/prefix/cwd", "/prefix/src/file"))123 124 125class GetPrefixFromCompilationDatabaseTest(unittest.TestCase):126 def test_with_different_filenames(self):127 self.assertEqual(sut.commonprefix(["/tmp/a.c", "/tmp/b.c"]), "/tmp")128 129 def test_with_different_dirnames(self):130 self.assertEqual(sut.commonprefix(["/tmp/abs/a.c", "/tmp/ack/b.c"]), "/tmp")131 132 def test_no_common_prefix(self):133 self.assertEqual(sut.commonprefix(["/tmp/abs/a.c", "/usr/ack/b.c"]), "/")134 135 def test_with_single_file(self):136 self.assertEqual(sut.commonprefix(["/tmp/a.c"]), "/tmp")137 138 def test_empty(self):139 self.assertEqual(sut.commonprefix([]), "")140 141 142class MergeSarifTest(unittest.TestCase):143 def test_merging_sarif(self):144 sarif1 = {145 "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",146 "runs": [147 {148 "artifacts": [149 {150 "length": 100,151 "location": {152 "uri": "//clang/tools/scan-build-py/tests/unit/test_report.py"153 },154 "mimeType": "text/plain",155 "roles": ["resultFile"],156 }157 ],158 "columnKind": "unicodeCodePoints",159 "results": [160 {161 "codeFlows": [162 {163 "threadFlows": [164 {165 "locations": [166 {167 "importance": "important",168 "location": {169 "message": {170 "text": "test message 1"171 },172 "physicalLocation": {173 "artifactLocation": {174 "index": 0,175 "uri": "//clang/tools/scan-build-py/tests/unit/test_report.py",176 },177 "region": {178 "endColumn": 5,179 "startColumn": 1,180 "startLine": 2,181 },182 },183 },184 }185 ]186 }187 ]188 }189 ]190 },191 {192 "codeFlows": [193 {194 "threadFlows": [195 {196 "locations": [197 {198 "importance": "important",199 "location": {200 "message": {201 "text": "test message 2"202 },203 "physicalLocation": {204 "artifactLocation": {205 "index": 0,206 "uri": "//clang/tools/scan-build-py/tests/unit/test_report.py",207 },208 "region": {209 "endColumn": 23,210 "startColumn": 9,211 "startLine": 10,212 },213 },214 },215 }216 ]217 }218 ]219 }220 ]221 },222 ],223 "tool": {224 "driver": {225 "fullName": "clang static analyzer",226 "language": "en-US",227 "name": "clang",228 "rules": [229 {230 "fullDescription": {231 "text": "test rule for merge sarif test"232 },233 "helpUrl": "//clang/tools/scan-build-py/tests/unit/test_report.py",234 "id": "testId",235 "name": "testName",236 }237 ],238 "version": "test clang",239 }240 },241 }242 ],243 "version": "2.1.0",244 }245 sarif2 = {246 "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",247 "runs": [248 {249 "artifacts": [250 {251 "length": 1523,252 "location": {253 "uri": "//clang/tools/scan-build-py/tests/unit/test_report.py"254 },255 "mimeType": "text/plain",256 "roles": ["resultFile"],257 }258 ],259 "columnKind": "unicodeCodePoints",260 "results": [261 {262 "codeFlows": [263 {264 "threadFlows": [265 {266 "locations": [267 {268 "importance": "important",269 "location": {270 "message": {271 "text": "test message 3"272 },273 "physicalLocation": {274 "artifactLocation": {275 "index": 0,276 "uri": "//clang/tools/scan-build-py/tests/unit/test_report.py",277 },278 "region": {279 "endColumn": 99,280 "startColumn": 99,281 "startLine": 17,282 },283 },284 },285 }286 ]287 }288 ]289 }290 ]291 },292 {293 "codeFlows": [294 {295 "threadFlows": [296 {297 "locations": [298 {299 "importance": "important",300 "location": {301 "message": {302 "text": "test message 4"303 },304 "physicalLocation": {305 "artifactLocation": {306 "index": 0,307 "uri": "//clang/tools/scan-build-py/tests/unit/test_report.py",308 },309 "region": {310 "endColumn": 305,311 "startColumn": 304,312 "startLine": 1,313 },314 },315 },316 }317 ]318 }319 ]320 }321 ]322 },323 ],324 "tool": {325 "driver": {326 "fullName": "clang static analyzer",327 "language": "en-US",328 "name": "clang",329 "rules": [330 {331 "fullDescription": {332 "text": "test rule for merge sarif test"333 },334 "helpUrl": "//clang/tools/scan-build-py/tests/unit/test_report.py",335 "id": "testId",336 "name": "testName",337 }338 ],339 "version": "test clang",340 }341 },342 }343 ],344 "version": "2.1.0",345 }346 347 contents = [sarif1, sarif2]348 with libear.TemporaryDirectory() as tmpdir:349 for idx, content in enumerate(contents):350 file_name = os.path.join(tmpdir, "results-{}.sarif".format(idx))351 with open(file_name, "w") as handle:352 json.dump(content, handle)353 354 sut.merge_sarif_files(tmpdir, sort_files=True)355 356 self.assertIn("results-merged.sarif", os.listdir(tmpdir))357 with open(os.path.join(tmpdir, "results-merged.sarif")) as f:358 merged = json.load(f)359 self.assertEqual(len(merged["runs"]), 2)360 self.assertEqual(len(merged["runs"][0]["results"]), 2)361 self.assertEqual(len(merged["runs"][1]["results"]), 2)362 363 expected = sarif1364 for run in sarif2["runs"]:365 expected["runs"].append(run)366 367 self.assertEqual(merged, expected)368 369 def test_merge_updates_embedded_link(self):370 sarif1 = {371 "runs": [372 {373 "results": [374 {375 "codeFlows": [376 {377 "message": {378 "text": "test message 1-1 [link](sarif:/runs/1/results/0) [link2](sarif:/runs/1/results/0)"379 },380 "threadFlows": [381 {382 "message": {383 "text": "test message 1-2 [link](sarif:/runs/1/results/0)"384 }385 }386 ],387 }388 ]389 }390 ]391 },392 {393 "results": [394 {395 "codeFlows": [396 {397 "message": {398 "text": "test message 2-1 [link](sarif:/runs/0/results/0)"399 },400 "threadFlows": [401 {402 "message": {403 "text": "test message 2-2 [link](sarif:/runs/0/results/0)"404 }405 }406 ],407 }408 ]409 }410 ]411 },412 ]413 }414 sarif2 = {415 "runs": [416 {417 "results": [418 {419 "codeFlows": [420 {421 "message": {422 "text": "test message 3-1 [link](sarif:/runs/1/results/0) [link2](sarif:/runs/1/results/0)"423 },424 "threadFlows": [425 {426 "message": {427 "text": "test message 3-2 [link](sarif:/runs/1/results/0)"428 }429 }430 ],431 }432 ]433 }434 ],435 },436 {437 "results": [438 {439 "codeFlows": [440 {441 "message": {442 "text": "test message 4-1 [link](sarif:/runs/0/results/0)"443 },444 "threadFlows": [445 {446 "message": {447 "text": "test message 4-2 [link](sarif:/runs/0/results/0)"448 }449 }450 ],451 }452 ]453 }454 ]455 },456 ]457 }458 sarif3 = {459 "runs": [460 {461 "results": [462 {463 "codeFlows": [464 {465 "message": {466 "text": "test message 5-1 [link](sarif:/runs/1/results/0) [link2](sarif:/runs/1/results/0)"467 },468 "threadFlows": [469 {470 "message": {471 "text": "test message 5-2 [link](sarif:/runs/1/results/0)"472 }473 }474 ],475 }476 ]477 }478 ],479 },480 {481 "results": [482 {483 "codeFlows": [484 {485 "message": {486 "text": "test message 6-1 [link](sarif:/runs/0/results/0)"487 },488 "threadFlows": [489 {490 "message": {491 "text": "test message 6-2 [link](sarif:/runs/0/results/0)"492 }493 }494 ],495 }496 ]497 }498 ]499 },500 ]501 }502 503 contents = [sarif1, sarif2, sarif3]504 505 with libear.TemporaryDirectory() as tmpdir:506 for idx, content in enumerate(contents):507 file_name = os.path.join(tmpdir, "results-{}.sarif".format(idx))508 with open(file_name, "w") as handle:509 json.dump(content, handle)510 511 sut.merge_sarif_files(tmpdir, sort_files=True)512 513 self.assertIn("results-merged.sarif", os.listdir(tmpdir))514 with open(os.path.join(tmpdir, "results-merged.sarif")) as f:515 merged = json.load(f)516 self.assertEqual(len(merged["runs"]), 6)517 518 code_flows = [519 merged["runs"][x]["results"][0]["codeFlows"][0]["message"]["text"]520 for x in range(6)521 ]522 thread_flows = [523 merged["runs"][x]["results"][0]["codeFlows"][0]["threadFlows"][0][524 "message"525 ]["text"]526 for x in range(6)527 ]528 529 # The run index should be updated for the second and third sets of runs530 self.assertEqual(531 code_flows,532 [533 "test message 1-1 [link](sarif:/runs/1/results/0) [link2](sarif:/runs/1/results/0)",534 "test message 2-1 [link](sarif:/runs/0/results/0)",535 "test message 3-1 [link](sarif:/runs/3/results/0) [link2](sarif:/runs/3/results/0)",536 "test message 4-1 [link](sarif:/runs/2/results/0)",537 "test message 5-1 [link](sarif:/runs/5/results/0) [link2](sarif:/runs/5/results/0)",538 "test message 6-1 [link](sarif:/runs/4/results/0)",539 ],540 )541 self.assertEqual(542 thread_flows,543 [544 "test message 1-2 [link](sarif:/runs/1/results/0)",545 "test message 2-2 [link](sarif:/runs/0/results/0)",546 "test message 3-2 [link](sarif:/runs/3/results/0)",547 "test message 4-2 [link](sarif:/runs/2/results/0)",548 "test message 5-2 [link](sarif:/runs/5/results/0)",549 "test message 6-2 [link](sarif:/runs/4/results/0)",550 ],551 )552 553 def test_overflow_run_count(self):554 sarif1 = {555 "runs": [556 {557 "results": [558 {"message": {"text": "run 1-0 [link](sarif:/runs/1/results/0)"}}559 ]560 },561 {562 "results": [563 {"message": {"text": "run 1-1 [link](sarif:/runs/2/results/0)"}}564 ]565 },566 {567 "results": [568 {"message": {"text": "run 1-2 [link](sarif:/runs/3/results/0)"}}569 ]570 },571 {572 "results": [573 {"message": {"text": "run 1-3 [link](sarif:/runs/4/results/0)"}}574 ]575 },576 {577 "results": [578 {"message": {"text": "run 1-4 [link](sarif:/runs/5/results/0)"}}579 ]580 },581 {582 "results": [583 {"message": {"text": "run 1-5 [link](sarif:/runs/6/results/0)"}}584 ]585 },586 {587 "results": [588 {"message": {"text": "run 1-6 [link](sarif:/runs/7/results/0)"}}589 ]590 },591 {592 "results": [593 {"message": {"text": "run 1-7 [link](sarif:/runs/8/results/0)"}}594 ]595 },596 {597 "results": [598 {"message": {"text": "run 1-8 [link](sarif:/runs/9/results/0)"}}599 ]600 },601 {602 "results": [603 {"message": {"text": "run 1-9 [link](sarif:/runs/0/results/0)"}}604 ]605 },606 ]607 }608 sarif2 = {609 "runs": [610 {611 "results": [612 {613 "message": {614 "text": "run 2-0 [link](sarif:/runs/1/results/0) [link2](sarif:/runs/2/results/0)"615 }616 }617 ]618 },619 {620 "results": [621 {"message": {"text": "run 2-1 [link](sarif:/runs/2/results/0)"}}622 ]623 },624 {625 "results": [626 {"message": {"text": "run 2-2 [link](sarif:/runs/3/results/0)"}}627 ]628 },629 {630 "results": [631 {"message": {"text": "run 2-3 [link](sarif:/runs/4/results/0)"}}632 ]633 },634 {635 "results": [636 {"message": {"text": "run 2-4 [link](sarif:/runs/5/results/0)"}}637 ]638 },639 {640 "results": [641 {"message": {"text": "run 2-5 [link](sarif:/runs/6/results/0)"}}642 ]643 },644 {645 "results": [646 {"message": {"text": "run 2-6 [link](sarif:/runs/7/results/0)"}}647 ]648 },649 {650 "results": [651 {"message": {"text": "run 2-7 [link](sarif:/runs/8/results/0)"}}652 ]653 },654 {655 "results": [656 {"message": {"text": "run 2-8 [link](sarif:/runs/9/results/0)"}}657 ]658 },659 {660 "results": [661 {"message": {"text": "run 2-9 [link](sarif:/runs/0/results/0)"}}662 ]663 },664 ]665 }666 667 contents = [sarif1, sarif2]668 with libear.TemporaryDirectory() as tmpdir:669 for idx, content in enumerate(contents):670 file_name = os.path.join(tmpdir, "results-{}.sarif".format(idx))671 with open(file_name, "w") as handle:672 json.dump(content, handle)673 674 sut.merge_sarif_files(tmpdir, sort_files=True)675 676 self.assertIn("results-merged.sarif", os.listdir(tmpdir))677 with open(os.path.join(tmpdir, "results-merged.sarif")) as f:678 merged = json.load(f)679 self.assertEqual(len(merged["runs"]), 20)680 681 messages = [682 merged["runs"][x]["results"][0]["message"]["text"]683 for x in range(20)684 ]685 self.assertEqual(686 messages,687 [688 "run 1-0 [link](sarif:/runs/1/results/0)",689 "run 1-1 [link](sarif:/runs/2/results/0)",690 "run 1-2 [link](sarif:/runs/3/results/0)",691 "run 1-3 [link](sarif:/runs/4/results/0)",692 "run 1-4 [link](sarif:/runs/5/results/0)",693 "run 1-5 [link](sarif:/runs/6/results/0)",694 "run 1-6 [link](sarif:/runs/7/results/0)",695 "run 1-7 [link](sarif:/runs/8/results/0)",696 "run 1-8 [link](sarif:/runs/9/results/0)",697 "run 1-9 [link](sarif:/runs/0/results/0)",698 "run 2-0 [link](sarif:/runs/11/results/0) [link2](sarif:/runs/12/results/0)",699 "run 2-1 [link](sarif:/runs/12/results/0)",700 "run 2-2 [link](sarif:/runs/13/results/0)",701 "run 2-3 [link](sarif:/runs/14/results/0)",702 "run 2-4 [link](sarif:/runs/15/results/0)",703 "run 2-5 [link](sarif:/runs/16/results/0)",704 "run 2-6 [link](sarif:/runs/17/results/0)",705 "run 2-7 [link](sarif:/runs/18/results/0)",706 "run 2-8 [link](sarif:/runs/19/results/0)",707 "run 2-9 [link](sarif:/runs/10/results/0)",708 ],709 )710