# -*- 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.assertEquals(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)' ])