def test_as_text(mock_revision): ''' Test text export for ClangTidyIssue ''' from static_analysis_bot.clang.tidy import ClangTidyIssue issue = ClangTidyIssue(mock_revision, 'test.cpp', '42', '51', 'dummy-check', 'dummy message withUppercaseChars', 'error') issue.body = 'Dummy body withUppercaseChars' assert issue.as_text() == 'Error: Dummy message withUppercaseChars [clang-tidy: dummy-check]\n```\nDummy body withUppercaseChars\n```'
def test_as_markdown(mock_revision): ''' Test markdown generation for ClangTidyIssue ''' from static_analysis_bot.clang.tidy import ClangTidyIssue parts = ('test.cpp', '42', '51', 'error', 'dummy message', 'dummy-check') issue = ClangTidyIssue(parts, mock_revision) issue.body = 'Dummy body' assert issue.as_markdown() == '''
def test_expanded_macros(mock_stats, test_cpp, mock_revision): ''' Test expanded macros are detected by clang issue ''' from static_analysis_bot.clang.tidy import ClangTidyIssue parts = ('test.cpp', '42', '51', 'error', 'dummy message', 'dummy-check') issue = ClangTidyIssue(parts, mock_revision) assert issue.is_problem() assert issue.line == 42 assert issue.char == 51 assert issue.notes == [] assert issue.is_expanded_macro() is False # Add a note starting with "expanded from macro..." parts = ('test.cpp', '42', '51', 'note', 'expanded from macro Blah dummy.cpp', 'dummy-check-note') issue.notes.append(ClangTidyIssue(parts, mock_revision)) assert issue.is_expanded_macro() is True # Add another note does not change it parts = ('test.cpp', '42', '51', 'note', 'This is not an expanded macro', 'dummy-check-note') issue.notes.append(ClangTidyIssue(parts, mock_revision)) assert issue.is_expanded_macro() is True # But if we swap them, it does not work anymore issue.notes.reverse() assert issue.is_expanded_macro() is False
def test_phabricator_clang_tidy(mock_repository, mock_phabricator): ''' Test Phabricator reporter publication on a mock clang-tidy issue ''' from static_analysis_bot.report.phabricator import PhabricatorReporter from static_analysis_bot.revisions import PhabricatorRevision from static_analysis_bot.clang.tidy import ClangTidyIssue def _check_comment(request): # Check the Phabricator main comment is well formed payload = urllib.parse.parse_qs(request.body) assert payload['output'] == ['json'] assert len(payload['params']) == 1 details = json.loads(payload['params'][0]) assert details == { 'revision_id': 51, 'message': VALID_CLANG_TIDY_MESSAGE, 'attach_inlines': 1, '__conduit__': {'token': 'deadbeef'}, } # Outputs dummy empty response resp = { 'error_code': None, 'result': None, } return 201, {'Content-Type': 'application/json', 'unittest': 'clang-tidy'}, json.dumps(resp) responses.add_callback( responses.POST, 'http://phabricator.test/api/differential.createcomment', callback=_check_comment, ) with mock_phabricator as api: revision = PhabricatorRevision('PHID-DIFF-abcdef', api) revision.lines = { # Add dummy lines diff 'test.cpp': [41, 42, 43], } reporter = PhabricatorReporter({'analyzers': ['clang-tidy']}, api=api) issue_parts = ('test.cpp', '42', '51', 'error', 'dummy message', 'modernize-use-nullptr') issue = ClangTidyIssue(issue_parts, revision) assert issue.is_publishable() issues, patches = reporter.publish([issue, ], revision) assert len(issues) == 1 assert len(patches) == 0 # Check the callback has been used assert len(responses.calls) > 0 call = responses.calls[-1] assert call.request.url == 'http://phabricator.test/api/differential.createcomment' assert call.response.headers.get('unittest') == 'clang-tidy'
def test_phabricator_clang_tidy(mock_repository, mock_phabricator): ''' Test Phabricator reporter publication on a mock clang-tidy issue ''' from static_analysis_bot.report.phabricator import PhabricatorReporter from static_analysis_bot.revisions import PhabricatorRevision from static_analysis_bot.clang.tidy import ClangTidyIssue def _check_comment(request): # Check the Phabricator main comment is well formed payload = urllib.parse.parse_qs(request.body) assert payload['output'] == ['json'] assert len(payload['params']) == 1 details = json.loads(payload['params'][0]) assert details == { 'revision_id': 51, 'message': VALID_CLANG_TIDY_MESSAGE, 'attach_inlines': 1, '__conduit__': {'token': 'deadbeef'}, } # Outputs dummy empty response resp = { 'error_code': None, 'result': None, } return 201, {'Content-Type': 'application/json', 'unittest': 'clang-tidy'}, json.dumps(resp) responses.add_callback( responses.POST, 'http://phabricator.test/api/differential.createcomment', callback=_check_comment, ) with mock_phabricator as api: revision = PhabricatorRevision(api, 'PHID-DIFF-abcdef') revision.lines = { # Add dummy lines diff 'test.cpp': [41, 42, 43], } reporter = PhabricatorReporter({'analyzers': ['clang-tidy'], 'modes': ('comment')}, api=api) issue = ClangTidyIssue(revision, 'test.cpp', '42', '51', 'modernize-use-nullptr', 'dummy message', 'error') assert issue.is_publishable() issues, patches = reporter.publish([issue, ], revision) assert len(issues) == 1 assert len(patches) == 0 # Check the callback has been used assert len(responses.calls) > 0 call = responses.calls[-1] assert call.request.url == 'http://phabricator.test/api/differential.createcomment' assert call.response.headers.get('unittest') == 'clang-tidy'
def _test_reporter(api, analyzers): # Always use the same setup, only varies the analyzers revision = PhabricatorRevision('PHID-DIFF-abcdef', api) revision.lines = { 'test.cpp': [41, 42, 43], } reporter = PhabricatorReporter({'analyzers': analyzers}, api=api) issues = [ ClangFormatIssue('test.cpp', 42, 1, revision), ClangTidyIssue(('test.cpp', '42', '51', 'error', 'dummy message', 'modernize-use-nullptr'), revision), InferIssue({ 'file': 'test.cpp', 'line': 42, 'column': 1, 'bug_type': 'dummy', 'kind': 'whatever', 'qualifier': 'dummy message.', }, revision), MozLintIssue('test.cpp', 1, 'danger', 42, 'flake8', 'Python error', 'EXXX', revision), ] assert all(i.is_publishable() for i in issues) revision.improvement_patches = { 'dummy': 'not gonna work', 'clang-format': 'https://diff.url/clang-format', 'clang-tidy': 'https://diff.url/clang-tidy', 'mozlint': 'https://diff.url/mozlint', 'infer': 'https://diff.url/infer', } return reporter.publish(issues, revision)
def test_as_markdown(mock_revision): ''' Test markdown generation for ClangTidyIssue ''' from static_analysis_bot.clang.tidy import ClangTidyIssue issue = ClangTidyIssue(mock_revision, 'test.cpp', '42', '51', 'dummy-check', 'dummy message', 'error') issue.body = 'Dummy body' assert issue.as_markdown() == ''' ## clang-tidy error - **Message**: dummy message - **Location**: test.cpp:42:51 - **In patch**: no - **Clang check**: dummy-check - **Publishable check**: no - **Third Party**: no - **Expanded Macro**: no - **Publishable **: no - **Is new**: no ``` Dummy body ``` ''' assert issue.as_phabricator_lint() == { 'char': 51, 'code': 'clang-tidy.dummy-check', 'line': 42, 'name': 'Clang-Tidy - dummy-check', 'description': 'dummy message\n\n > Dummy body', 'path': 'test.cpp', 'severity': 'warning', }
def _test_reporter(api, analyzers): # Always use the same setup, only varies the analyzers revision = PhabricatorRevision(api, 'PHID-DIFF-abcdef') revision.lines = { 'test.cpp': [0, 41, 42, 43], 'dom/test.cpp': [ 42, ], } reporter = PhabricatorReporter({'analyzers': analyzers}, api=api) issues = [ ClangFormatIssue('dom/test.cpp', 42, 1, revision), ClangTidyIssue(('test.cpp', '42', '51', 'error', 'dummy message', 'modernize-use-nullptr'), revision), InferIssue( { 'file': 'test.cpp', 'line': 42, 'column': 1, 'bug_type': 'dummy', 'kind': 'whatever', 'qualifier': 'dummy message.', }, revision), MozLintIssue('test.cpp', 1, 'danger', 42, 'flake8', 'Python error', 'EXXX', revision), CoverageIssue('test.cpp', 0, 'This file is uncovered', revision), ] assert all(i.is_publishable() for i in issues) revision.improvement_patches = [ ImprovementPatch('dummy', repr(revision), 'Whatever'), ImprovementPatch('clang-tidy', repr(revision), 'Some C fixes'), ImprovementPatch('clang-format', repr(revision), 'Some lint fixes'), ImprovementPatch('infer', repr(revision), 'Some java fixes'), ImprovementPatch('mozlint', repr(revision), 'Some js fixes'), ] list(map(lambda p: p.write(), revision.improvement_patches)) # trigger local write return reporter.publish(issues, revision)
def test_expanded_macros(mock_stats, test_cpp, mock_revision): ''' Test expanded macros are detected by clang issue ''' from static_analysis_bot.clang.tidy import ClangTidyIssue issue = ClangTidyIssue(mock_revision, 'test.cpp', '42', '51', 'dummy message', 'dummy-check', 'error') assert issue.is_problem() assert issue.line == 42 assert issue.char == 51 assert issue.notes == [] assert issue.is_expanded_macro() is False # Add a note starting with "expanded from macro..." issue.notes.append(ClangTidyIssue(mock_revision, 'test.cpp', '42', '51', 'dummy-check-note', 'expanded from macro Blah dummy.cpp', 'note')) assert issue.is_expanded_macro() is True # Add another note does not change it issue.notes.append(ClangTidyIssue(mock_revision, 'test.cpp', '42', '51', 'dummy-check-note', 'This is not an expanded macro', 'note')) assert issue.is_expanded_macro() is True # But if we swap them, it does not work anymore issue.notes.reverse() assert issue.is_expanded_macro() is False
def test_phabricator_harbormaster(mock_repository, mock_phabricator): ''' Test Phabricator reporter publication on a mock clang-tidy issue using harbormaster ''' from static_analysis_bot.report.phabricator import PhabricatorReporter from static_analysis_bot.revisions import PhabricatorRevision from static_analysis_bot.clang.tidy import ClangTidyIssue def _check_message(request): # Check the Phabricator main comment is well formed payload = urllib.parse.parse_qs(request.body) assert payload['output'] == ['json'] assert len(payload['params']) == 1 details = json.loads(payload['params'][0]) assert details == { 'buildTargetPHID': 'PHID-HMBD-deadbeef12456', 'lint': [ { 'char': 51, 'code': 'clang-tidy.modernize-use-nullptr', 'name': 'Clang-Tidy - modernize-use-nullptr', 'line': 42, 'path': 'test.cpp', 'severity': 'warning', 'description': 'dummy message' } ], 'unit': [], 'type': 'work', '__conduit__': {'token': 'deadbeef'}, } # Outputs dummy empty response resp = { 'error_code': None, 'result': None, } return 201, {'Content-Type': 'application/json', 'unittest': 'clang-tidy'}, json.dumps(resp) responses.add_callback( responses.POST, 'http://phabricator.test/api/harbormaster.sendmessage', callback=_check_message, ) with mock_phabricator as api: revision = PhabricatorRevision(api, 'PHID-DIFF-abcdef') revision.lines = { # Add dummy lines diff 'test.cpp': [41, 42, 43], } revision.build_target_phid = 'PHID-HMBD-deadbeef12456' reporter = PhabricatorReporter({'analyzers': ['clang-tidy'], 'mode': 'harbormaster'}, api=api) issue = ClangTidyIssue(revision, 'test.cpp', '42', '51', 'modernize-use-nullptr', 'dummy message', 'error') assert issue.is_publishable() issues, patches = reporter.publish([issue, ], revision) assert len(issues) == 1 assert len(patches) == 0 # Check the callback has been used assert len(responses.calls) > 0 call = responses.calls[-1] assert call.request.url == 'http://phabricator.test/api/harbormaster.sendmessage' assert call.response.headers.get('unittest') == 'clang-tidy'
def test_comment(mock_mozreview, test_cpp, mock_revision): ''' Test comment creation for specific issues ''' from static_analysis_bot.clang.tidy import ClangTidyIssue from static_analysis_bot.clang.format import ClangFormatIssue from static_analysis_bot.lint import MozLintIssue from static_analysis_bot.report.base import Reporter # Init dummy reporter class TestReporter(Reporter): def __init__(self): pass reporter = TestReporter() # Build clang tidy fake issue, while forcing publication status header = ('test.cpp', 1, 1, 'error', 'Dummy message', 'test-check') clang_tidy_publishable = ClangTidyIssue(header, mock_revision) clang_tidy_publishable.is_publishable = lambda: True assert clang_tidy_publishable.is_publishable() issues = [ clang_tidy_publishable, ] assert reporter.build_comment(issues, 'https://report.example.com') == ''' Code analysis found 1 defect in this patch: - 1 defect found by clang-tidy You can run this analysis locally with: - `./mach static-analysis check path/to/file.cpp` (C/C++) If you see a problem in this automated review, please report it here: https://report.example.com ''' # Now add a clang-format issue clang_format_publishable = ClangFormatIssue('test.cpp', '', '', ('delete', 1, 2, 3, 4), mock_revision) clang_format_publishable.is_publishable = lambda: True assert clang_tidy_publishable.is_publishable() issues.append(clang_format_publishable) assert reporter.build_comment(issues, 'https://report.example.com') == ''' Code analysis found 2 defects in this patch: - 1 defect found by clang-format - 1 defect found by clang-tidy You can run this analysis locally with: - `./mach clang-format -p path/to/file.cpp` (C/C++) - `./mach static-analysis check path/to/file.cpp` (C/C++) If you see a problem in this automated review, please report it here: https://report.example.com ''' # Now add a mozlint issue mozlint_publishable = MozLintIssue('test.cpp', 1, 'error', 1, 'test', 'Dummy test', 'dummy rule', mock_revision) mozlint_publishable.is_publishable = lambda: True assert mozlint_publishable.is_publishable() issues.append(mozlint_publishable) assert reporter.build_comment(issues, 'https://report.example.com') == '''
def test_phabricator_harbormaster(mock_repository, mock_phabricator): ''' Test Phabricator reporter publication on a mock clang-tidy issue using harbormaster ''' from static_analysis_bot.report.phabricator import PhabricatorReporter from static_analysis_bot.revisions import PhabricatorRevision from static_analysis_bot.clang.tidy import ClangTidyIssue def _check_message(request): # Check the Phabricator main comment is well formed payload = urllib.parse.parse_qs(request.body) assert payload['output'] == ['json'] assert len(payload['params']) == 1 details = json.loads(payload['params'][0]) assert details == { 'buildTargetPHID': 'PHID-HMBD-deadbeef12456', 'lint': [{ 'char': 51, 'code': 'clang-tidy.modernize-use-nullptr', 'name': 'Clang-Tidy - modernize-use-nullptr', 'line': 42, 'path': 'test.cpp', 'severity': 'warning', 'description': 'dummy message' }], 'unit': [], 'type': 'work', '__conduit__': { 'token': 'deadbeef' }, } # Outputs dummy empty response resp = { 'error_code': None, 'result': None, } return 201, { 'Content-Type': 'application/json', 'unittest': 'clang-tidy' }, json.dumps(resp) responses.add_callback( responses.POST, 'http://phabricator.test/api/harbormaster.sendmessage', callback=_check_message, ) with mock_phabricator as api: revision = PhabricatorRevision(api, 'PHID-DIFF-abcdef') revision.lines = { # Add dummy lines diff 'test.cpp': [41, 42, 43], } revision.build_target_phid = 'PHID-HMBD-deadbeef12456' reporter = PhabricatorReporter( { 'analyzers': ['clang-tidy'], 'mode': 'harbormaster' }, api=api) issue = ClangTidyIssue(revision, 'test.cpp', '42', '51', 'modernize-use-nullptr', 'dummy message', 'error') assert issue.is_publishable() issues, patches = reporter.publish([ issue, ], revision) assert len(issues) == 1 assert len(patches) == 0 # Check the callback has been used assert len(responses.calls) > 0 call = responses.calls[-1] assert call.request.url == 'http://phabricator.test/api/harbormaster.sendmessage' assert call.response.headers.get('unittest') == 'clang-tidy'