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_phabricator_clang_format(mock_repository, mock_phabricator): ''' Test Phabricator reporter publication on a mock clang-format issue ''' from static_analysis_bot.report.phabricator import PhabricatorReporter from static_analysis_bot.revisions import PhabricatorRevision from static_analysis_bot.clang.format import ClangFormatIssue 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['message'] == VALID_CLANG_FORMAT_MESSAGE # Outputs dummy empty response resp = { 'error_code': None, 'result': None, } return 201, { 'Content-Type': 'application/json', 'unittest': 'clang-format' }, 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], 'dom/test.cpp': [ 42, ], } reporter = PhabricatorReporter({'analyzers': ['clang-format']}, api=api) issue = ClangFormatIssue('dom/test.cpp', 42, 1, revision) assert issue.is_publishable() revision.improvement_patches = {'clang-format': 'https://diff.url'} issues, patches = reporter.publish([ issue, ], revision) assert len(issues) == 1 assert len(patches) == 1 # 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-format'
def test_phabricator_coverage(mock_config, mock_repository, mock_phabricator): ''' Test Phabricator reporter publication on a mock coverage issue ''' from static_analysis_bot.report.phabricator import PhabricatorReporter from static_analysis_bot.revisions import PhabricatorRevision from static_analysis_bot.coverage import CoverageIssue 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['message'] == VALID_COVERAGE_MESSAGE.format( results=mock_config.taskcluster.results_dir) # Outputs dummy empty response resp = { 'error_code': None, 'result': None, } return 201, { 'Content-Type': 'application/json', 'unittest': 'coverage' }, 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.txt': [0], 'test.cpp': [0], 'dom/test.cpp': [ 42, ], } reporter = PhabricatorReporter({'analyzers': ['coverage']}, api=api) issue = CoverageIssue('test.cpp', 0, 'This file is uncovered', 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') == 'coverage'
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_format(mock_config, mock_repository, mock_phabricator): ''' Test Phabricator reporter publication on a mock clang-format issue ''' from static_analysis_bot.report.phabricator import PhabricatorReporter from static_analysis_bot.revisions import PhabricatorRevision, ImprovementPatch from static_analysis_bot.clang.format import ClangFormatIssue 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['message'] == VALID_CLANG_FORMAT_MESSAGE.format(results=mock_config.taskcluster.results_dir) # Outputs dummy empty response resp = { 'error_code': None, 'result': None, } return 201, {'Content-Type': 'application/json', 'unittest': 'clang-format'}, 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], 'dom/test.cpp': [42, ], } reporter = PhabricatorReporter({'analyzers': ['clang-format']}, api=api) issue = ClangFormatIssue('dom/test.cpp', 42, 1, revision) assert issue.is_publishable() revision.improvement_patches = [ ImprovementPatch('clang-format', repr(revision), 'Some lint fixes'), ] list(map(lambda p: p.write(), revision.improvement_patches)) # trigger local write issues, patches = reporter.publish([issue, ], revision) assert len(issues) == 1 assert len(patches) == 1 # 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-format'
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_mail(mock_config, mock_issues, mock_phabricator): ''' Test mail sending through Taskcluster ''' from static_analysis_bot.report.mail import MailReporter from static_analysis_bot.revisions import PhabricatorRevision, ImprovementPatch def _check_email(request): payload = json.loads(request.body) assert payload['subject'] in ( '[test] New Static Analysis Phabricator #42 - PHID-DIFF-test', ) assert payload['address'] == '*****@*****.**' assert payload['template'] == 'fullscreen' assert payload['content'] == MAIL_CONTENT.format( results=mock_config.taskcluster.results_dir) return (200, {}, '') # ack # Add mock taskcluster email to check output responses.add_callback( responses.POST, 'https://notify.taskcluster.net/v1/email', callback=_check_email, ) # Publish email conf = { 'emails': [ '*****@*****.**', ], } r = MailReporter(conf, 'test_tc', 'token_tc') with mock_phabricator as api: prev = PhabricatorRevision(api, 'PHID-DIFF-test') prev.improvement_patches = [ ImprovementPatch('clang-tidy', repr(prev), 'Some code fixes'), ImprovementPatch('clang-format', repr(prev), 'Some lint fixes'), ] list(map(lambda p: p.write(), prev.improvement_patches)) # trigger local write r.publish(mock_issues, prev) # Check stats mock_cls = mock_issues[0].__class__ assert r.calc_stats(mock_issues) == { mock_cls: { 'total': 5, 'publishable': 3, 'publishable_paths': ['/path/to/file'] } }
def test_phabricator_coverage(mock_config, mock_repository, mock_phabricator): ''' Test Phabricator reporter publication on a mock coverage issue ''' from static_analysis_bot.report.phabricator import PhabricatorReporter from static_analysis_bot.revisions import PhabricatorRevision from static_analysis_bot.coverage import CoverageIssue 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['message'] == VALID_COVERAGE_MESSAGE.format(results=mock_config.taskcluster.results_dir) # Outputs dummy empty response resp = { 'error_code': None, 'result': None, } return 201, {'Content-Type': 'application/json', 'unittest': 'coverage'}, 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.txt': [0], 'test.cpp': [0], 'dom/test.cpp': [42, ], } reporter = PhabricatorReporter({'analyzers': ['coverage']}, api=api) issue = CoverageIssue('test.cpp', 0, 'This file is uncovered', 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') == 'coverage'
def test_mail(mock_issues, mock_phabricator): ''' Test mail sending through Taskcluster ''' from static_analysis_bot.report.mail import MailReporter from static_analysis_bot.revisions import PhabricatorRevision def _check_email(request): payload = json.loads(request.body) assert payload['subject'] in ( '[test] New Static Analysis Phabricator #42 - PHID-DIFF-test', ) assert payload['address'] == '*****@*****.**' assert payload['template'] == 'fullscreen' assert payload['content'] == MAIL_CONTENT return (200, {}, '') # ack # Add mock taskcluster email to check output responses.add_callback( responses.POST, 'https://notify.taskcluster.net/v1/email', callback=_check_email, ) # Publish email conf = { 'emails': [ '*****@*****.**', ], } r = MailReporter(conf, 'test_tc', 'token_tc') with mock_phabricator as api: prev = PhabricatorRevision('PHID-DIFF-test', api) prev.improvement_patches = { 'clang-tidy': 'https://my.patch/clang-tidy.diff', 'clang-format': 'https://another.patch/clang-format.diff', } r.publish(mock_issues, prev) # Check stats mock_cls = mock_issues[0].__class__ assert r.calc_stats(mock_issues) == { mock_cls: { 'total': 5, 'publishable': 3, } }
def mock_revision(mock_phabricator): ''' Mock a mercurial revision ''' from static_analysis_bot.revisions import PhabricatorRevision with mock_phabricator as api: return PhabricatorRevision(api, diff_phid='PHID-DIFF-XXX')
def test_phabricator(mock_phabricator, mock_config): ''' Test a phabricator revision ''' from static_analysis_bot.revisions import PhabricatorRevision with mock_phabricator as api: r = PhabricatorRevision(api, 'PHID-DIFF-testABcd12') assert not hasattr(r, 'mercurial') assert r.diff_id == 42 assert r.diff_phid == 'PHID-DIFF-testABcd12' assert r.url == 'https://phabricator.test/D51' assert repr(r) == 'PHID-DIFF-testABcd12' assert r.id == 51 # revision # Patch is automatically loaded from Phabricator assert r.patch is not None assert isinstance(r.patch, str) assert len(r.patch.split('\n')) == 14 patch = Patch.parse_patch(r.patch) assert patch == { 'test.txt': { 'touched': [], 'deleted': [], 'added': [2], 'new': False }, 'test.cpp': { 'touched': [], 'deleted': [], 'added': [2], 'new': False } }
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_mail(mock_issues, mock_phabricator): ''' Test mail sending through Taskcluster ''' from static_analysis_bot.report.mail import MailReporter from static_analysis_bot.revisions import MozReviewRevision, PhabricatorRevision def _check_email(request): payload = json.loads(request.body) assert payload['subject'] in ( '[test] New Static Analysis MozReview #12345 - 1', '[test] New Static Analysis Phabricator #42 - PHID-DIFF-test', ) assert payload['address'] == '*****@*****.**' assert payload['template'] == 'fullscreen' assert payload['content'].startswith( '\n# Found 3 publishable issues (5 total)') return (200, {}, '') # ack # Add mock taskcluster email to check output responses.add_callback( responses.POST, 'https://notify.taskcluster.net/v1/email', callback=_check_email, ) # Publish email conf = { 'emails': [ '*****@*****.**', ], } r = MailReporter(conf, 'test_tc', 'token_tc') # Publish for mozreview mrev = MozReviewRevision('12345', 'abcdef', '1') r.publish(mock_issues, mrev) with mock_phabricator as api: prev = PhabricatorRevision('PHID-DIFF-test', api) r.publish(mock_issues, prev) # Check stats mock_cls = mock_issues[0].__class__ assert r.calc_stats(mock_issues) == { mock_cls: { 'total': 5, 'publishable': 3, } }
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(revision, 'test.cpp', '42', '51', 'modernize-use-nullptr', 'dummy message', 'error'), 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_publication(tmpdir, mock_issues, mock_phabricator): ''' Test debug publication and report analysis ''' from static_analysis_bot.report.debug import DebugReporter from static_analysis_bot.revisions import PhabricatorRevision report_dir = str(tmpdir.mkdir('public').realpath()) report_path = os.path.join(report_dir, 'report.json') assert not os.path.exists(report_path) with mock_phabricator as api: prev = PhabricatorRevision(api, 'PHID-DIFF-abcdef') r = DebugReporter(report_dir) r.publish(mock_issues, prev) assert os.path.exists(report_path) with open(report_path) as f: report = json.load(f) assert 'issues' in report assert report['issues'] == [{ 'nb': 0 }, { 'nb': 1 }, { 'nb': 2 }, { 'nb': 3 }, { 'nb': 4 }] assert 'revision' in report assert report['revision'] == { 'id': 51, 'diff_id': 42, 'url': 'https://phabricator.test/D51', 'bugzilla_id': '', 'diff_phid': 'PHID-DIFF-abcdef', 'phid': 'PHID-DREV-zzzzz', 'title': 'Static Analysis tests', 'has_clang_files': False, } assert 'time' in report assert isinstance(report['time'], float)
def test_phabricator(mock_phabricator, mock_repository, mock_config): ''' Test a phabricator revision ''' from static_analysis_bot.revisions import PhabricatorRevision with mock_phabricator as api: r = PhabricatorRevision(api, 'PHID-DIFF-testABcd12') assert not hasattr(r, 'mercurial') assert r.diff_id == 42 assert r.diff_phid == 'PHID-DIFF-testABcd12' assert r.url == 'https://phabricator.test/D51' assert repr(r) == 'PHID-DIFF-testABcd12' assert r.id == 51 # revision # Check test.txt content test_txt = os.path.join(mock_config.repo_dir, 'test.txt') assert open(test_txt).read() == 'Hello World\n' # Cleanup the repo mock_repository.update(clean=True) # Load full patch # Mock the mercurial repo update as we use a dummy revision assert r.patch is None __update = mock_repository.update mock_repository.update = MagicMock(return_value=True) r.load(mock_repository) mock_repository.update = __update assert r.patch is not None assert isinstance(r.patch, str) assert len(r.patch.split('\n')) == 14 patch = Patch.parse_patch(r.patch) assert patch == { 'test.txt': { 'touched': [], 'deleted': [], 'added': [2], 'new': False }, 'test.cpp': { 'touched': [], 'deleted': [], 'added': [2], 'new': False } } # Check file is untouched after load assert open(test_txt).read() == 'Hello World\n' # Check file is updated after apply r.apply(mock_repository) assert open(test_txt).read() == 'Hello World\nSecond line\n'
def main( id, work_dir, taskcluster_secret, taskcluster_client_id, taskcluster_access_token, ): secrets = get_secrets( taskcluster_secret, config.PROJECT_NAME, required=( 'APP_CHANNEL', 'REPORTERS', 'ANALYZERS', 'PHABRICATOR', 'ALLOWED_PATHS', 'MAX_CLONE_RUNTIME', ), existing={ 'APP_CHANNEL': 'development', 'REPORTERS': [], 'ANALYZERS': [ 'clang-tidy', ], 'PUBLICATION': 'IN_PATCH', 'ALLOWED_PATHS': [ '*', ], 'MAX_CLONE_RUNTIME': 15 * 60, }, taskcluster_client_id=taskcluster_client_id, taskcluster_access_token=taskcluster_access_token, ) init_logger( config.PROJECT_NAME, PAPERTRAIL_HOST=secrets.get('PAPERTRAIL_HOST'), PAPERTRAIL_PORT=secrets.get('PAPERTRAIL_PORT'), SENTRY_DSN=secrets.get('SENTRY_DSN'), MOZDEF=secrets.get('MOZDEF'), timestamp=True, ) # Setup settings before stats settings.setup( secrets['APP_CHANNEL'], work_dir, secrets['PUBLICATION'], secrets['ALLOWED_PATHS'], secrets.get('COVERITY_CONFIG'), secrets['MAX_CLONE_RUNTIME'], ) # Setup statistics datadog_api_key = secrets.get('DATADOG_API_KEY') if datadog_api_key: stats.auth(datadog_api_key) # Load reporters reporters = get_reporters( secrets['REPORTERS'], taskcluster_client_id, taskcluster_access_token, ) # Load index service index_service = get_service( 'index', taskcluster_client_id, taskcluster_access_token, ) # Load queue service queue_service = get_service( 'queue', taskcluster_client_id, taskcluster_access_token, ) # Load Phabricator API phabricator_api = PhabricatorAPI(**secrets['PHABRICATOR']) if 'phabricator' in reporters: reporters['phabricator'].setup_api(phabricator_api) # Load unique revision if settings.source == SOURCE_PHABRICATOR: revision = PhabricatorRevision(phabricator_api, diff_phid=id) elif settings.source == SOURCE_TRY: revision = PhabricatorRevision(phabricator_api, try_task=queue_service.task( settings.try_task_id)) else: raise Exception('Unsupported source {}'.format(settings.source)) # Run workflow according to source w = Workflow(reporters, secrets['ANALYZERS'], index_service, queue_service, phabricator_api) try: w.run(revision) except Exception as e: # Log errors to papertrail logger.error( 'Static analysis failure', revision=revision, error=e, ) # Index analysis state extras = {} if isinstance(e, AnalysisException): extras['error_code'] = e.code extras['error_message'] = str(e) w.index(revision, state='error', **extras) # Then raise to mark task as erroneous raise
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 main( taskcluster_secret, taskcluster_client_id, taskcluster_access_token, ): secrets = get_secrets( taskcluster_secret, config.PROJECT_NAME, required=( 'APP_CHANNEL', 'REPORTERS', 'PHABRICATOR', 'ALLOWED_PATHS', ), existing={ 'APP_CHANNEL': 'development', 'REPORTERS': [], 'PUBLICATION': 'IN_PATCH', 'ALLOWED_PATHS': [ '*', ], }, taskcluster_client_id=taskcluster_client_id, taskcluster_access_token=taskcluster_access_token, ) init_logger( config.PROJECT_NAME, PAPERTRAIL_HOST=secrets.get('PAPERTRAIL_HOST'), PAPERTRAIL_PORT=secrets.get('PAPERTRAIL_PORT'), SENTRY_DSN=secrets.get('SENTRY_DSN'), MOZDEF=secrets.get('MOZDEF'), timestamp=True, ) # Setup settings before stats phabricator = secrets['PHABRICATOR'] settings.setup( secrets['APP_CHANNEL'], secrets['PUBLICATION'], secrets['ALLOWED_PATHS'], secrets.get('COVERITY_CONFIG'), ) # Setup statistics datadog_api_key = secrets.get('DATADOG_API_KEY') if datadog_api_key: stats.auth(datadog_api_key) # Load reporters reporters = get_reporters( secrets['REPORTERS'], taskcluster_client_id, taskcluster_access_token, ) # Load index service index_service = get_service( 'index', taskcluster_client_id, taskcluster_access_token, ) # Load queue service queue_service = get_service( 'queue', taskcluster_client_id, taskcluster_access_token, ) # Load Phabricator API phabricator_reporting_enabled = 'phabricator' in reporters phabricator_api = PhabricatorAPI(phabricator['api_key'], phabricator['url']) if phabricator_reporting_enabled: reporters['phabricator'].setup_api(phabricator_api) # Load unique revision revision = PhabricatorRevision( phabricator_api, try_task=queue_service.task(settings.try_task_id), # Update build status only when phabricator reporting is enabled update_build=phabricator_reporting_enabled, ) # Run workflow according to source w = Workflow(reporters, index_service, queue_service, phabricator_api) try: w.run(revision) except Exception as e: # Log errors to papertrail logger.error( 'Static analysis failure', revision=revision, error=e, ) # Index analysis state extras = {} if isinstance(e, AnalysisException): extras['error_code'] = e.code extras['error_message'] = str(e) w.index(revision, state='error', **extras) # Update Harbormaster status revision.update_status(state=BuildState.Fail) # Then raise to mark task as erroneous raise
def main(source, id, cache_root, mozreview_diffset, mozreview_revision, taskcluster_secret, taskcluster_client_id, taskcluster_access_token, ): secrets = get_secrets(taskcluster_secret, config.PROJECT_NAME, required=( 'APP_CHANNEL', 'REPORTERS', 'ANALYZERS', 'PHABRICATOR', ), existing={ 'APP_CHANNEL': 'development', 'REPORTERS': [], 'ANALYZERS': ['clang-tidy', ], 'PUBLICATION': 'IN_PATCH', }, taskcluster_client_id=taskcluster_client_id, taskcluster_access_token=taskcluster_access_token, ) init_logger(config.PROJECT_NAME, PAPERTRAIL_HOST=secrets.get('PAPERTRAIL_HOST'), PAPERTRAIL_PORT=secrets.get('PAPERTRAIL_PORT'), SENTRY_DSN=secrets.get('SENTRY_DSN'), MOZDEF=secrets.get('MOZDEF'), ) # Setup settings before stats settings.setup(secrets['APP_CHANNEL'], cache_root, secrets['PUBLICATION']) # Setup statistics datadog_api_key = secrets.get('DATADOG_API_KEY') if datadog_api_key: stats.auth(datadog_api_key) # Load reporters reporters = get_reporters( secrets['REPORTERS'], taskcluster_client_id, taskcluster_access_token, ) # Load index service index_service = get_service( 'index', taskcluster_client_id, taskcluster_access_token, ) # Load Phabricator API phabricator_api = PhabricatorAPI(**secrets['PHABRICATOR']) if 'phabricator' in reporters: reporters['phabricator'].setup_api(phabricator_api) # Load unique revision if source == 'phabricator': revision = PhabricatorRevision(id, phabricator_api) elif source == 'mozreview': revision = MozReviewRevision(id, mozreview_revision, mozreview_diffset) else: raise Exception('Unsupported analysis source: {}'.format(source)) w = Workflow(reporters, secrets['ANALYZERS'], index_service, phabricator_api) try: w.run(revision) except Exception as e: # Log errors to papertrail logger.error( 'Static analysis failure', revision=revision, error=e, ) # Index analysis state w.index(revision, state='error') # Then raise to mark task as erroneous raise
def test_phabricator_clang_tidy_and_coverage(mock_config, mock_phabricator): ''' Test Phabricator reporter publication on a mock coverage issue ''' from static_analysis_bot.report.phabricator import PhabricatorReporter from static_analysis_bot.revisions import PhabricatorRevision from static_analysis_bot.tasks.coverage import CoverageIssue from static_analysis_bot.tasks.clang_tidy import ClangTidyIssue def _check_comment_sa(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) def _check_comment_ccov(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['message'] == VALID_COVERAGE_MESSAGE.format(results=mock_config.taskcluster.results_dir) # Outputs dummy empty response resp = { 'error_code': None, 'result': None, } return 201, {'Content-Type': 'application/json', 'unittest': 'coverage'}, json.dumps(resp) responses.add_callback( responses.POST, 'http://phabricator.test/api/differential.createcomment', callback=_check_comment_sa, ) responses.add_callback( responses.POST, 'http://phabricator.test/api/differential.createcomment', callback=_check_comment_ccov, ) with mock_phabricator as api: revision = PhabricatorRevision(api, 'PHID-DIFF-abcdef') revision.lines = { # Add dummy lines diff 'test.txt': [0], 'test.cpp': [0], 'another_test.cpp': [41, 42, 43], } revision.files = ['test.txt', 'test.cpp', 'another_test.cpp'] reporter = PhabricatorReporter({'analyzers': ['coverage', 'clang-tidy']}, api=api) issue_clang_tidy = ClangTidyIssue(revision, 'another_test.cpp', '42', '51', 'modernize-use-nullptr', 'dummy message', 'error') assert issue_clang_tidy.is_publishable() issue_coverage = CoverageIssue('test.cpp', 0, 'This file is uncovered', revision) assert issue_coverage.is_publishable() issues, patches = reporter.publish([issue_clang_tidy, issue_coverage, ], revision) assert len(issues) == 2 assert len(patches) == 0 # Check the callback has been used assert len(responses.calls) > 0 call = responses.calls[-2] assert call.request.url == 'http://phabricator.test/api/differential.createcomment' assert call.response.headers.get('unittest') == 'clang-tidy' # 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') == 'coverage'
def test_phabricator_harbormaster(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.tasks.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'