def run(self, revision): ''' Build analysis workflows and directly run them ''' issues = [] # Index ASAP Taskcluster task for this revision self.index(revision, state='started') # Set the Phabricator build as running revision.update_status(state=BuildState.Work) # Use remote when we are on try if settings.source == SOURCE_TRY: remote = RemoteWorkflow(self.queue_service) issues += remote.run(revision) # Always use local workflow # until we have all analyzers in-tree local = LocalWorkflow(self, self.analyzers, self.index_service) issues += local.run(revision) if not issues: logger.info('No issues, stopping there.') self.index(revision, state='done', issues=0) revision.update_status(BuildState.Pass) return # Publish all issues from both workflows at once self.publish(revision, issues)
def test_unsupported_analyzer(mock_try_config, mock_revision): ''' Test a remote workflow with an unsupported analyzer (not mozlint) ''' from static_analysis_bot.workflows.remote import RemoteWorkflow tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', 'GECKO_HEAD_REV': 'deadbeef1234', } }, 'remoteTryTask': { 'dependencies': ['analyzer-A', 'analyzer-B'] }, 'analyzer-A': {}, 'analyzer-B': { 'name': 'custom-analyzer-from-vendor', 'state': 'failed', 'artifacts': { 'issue.log': 'TEST-UNEXPECTED-ERROR | test.cpp:12:1 | clearly an issue (checker XXX)', } }, 'extra-task': {}, } workflow = RemoteWorkflow(MockQueue(tasks)) with pytest.raises(Exception) as e: workflow.run(mock_revision) assert str(e.value) == 'Unsupported task custom-analyzer-from-vendor'
def test_no_issues(mock_try_config, mock_revision): ''' Test a remote workflow without any issues in its artifacts ''' from static_analysis_bot.workflows.remote import RemoteWorkflow tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', 'GECKO_HEAD_REV': 'deadbeef1234', } }, 'remoteTryTask': { 'dependencies': ['analyzer-A', 'analyzer-B'] }, 'analyzer-A': {}, 'analyzer-B': { 'name': 'source-test-mozlint-flake8', 'state': 'failed', 'artifacts': { 'nope.log': 'No issues here !', 'still-nope.txt': 'xxxxx', 'public/code-review/mozlint.json': {}, } }, 'extra-task': {}, } workflow = RemoteWorkflow(MockQueue(tasks)) issues = workflow.run(mock_revision) assert len(issues) == 0
def test_clang_format_task(mock_try_config, mock_revision): ''' Test a remote workflow with a clang-format analyzer ''' from static_analysis_bot.workflows.remote import RemoteWorkflow from static_analysis_bot.clang.format import ClangFormatIssue tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', 'GECKO_HEAD_REV': 'deadbeef1234', } }, 'remoteTryTask': { 'dependencies': ['clang-format'] }, 'clang-format': { 'name': 'source-test-clang-format', 'state': 'completed', 'artifacts': { 'public/code-review/clang-format.json': { 'test.cpp': [ { 'line_offset': 11, 'char_offset': 44616, 'char_length': 7, 'lines_modified': 2, 'line': 1386, 'replacement': 'Multi\nlines', } ] } } } } workflow = RemoteWorkflow(MockQueue(tasks)) issues = workflow.run(mock_revision) assert len(issues) == 1 issue = issues[0] assert isinstance(issue, ClangFormatIssue) assert issue.path == 'test.cpp' assert issue.line == 1386 assert issue.nb_lines == 2 assert issue.patch == 'Multi\nlines' assert issue.column == 11 assert issue.as_dict() == { 'analyzer': 'clang-format', 'column': 11, 'in_patch': False, 'is_new': True, 'line': 1386, 'nb_lines': 2, 'patch': 'Multi\nlines', 'path': 'test.cpp', 'publishable': False, 'validates': False, 'validation': {} }
def test_clang_format_task(mock_try_config, mock_revision): ''' Test a remote workflow with a clang-format analyzer ''' from static_analysis_bot.workflows.remote import RemoteWorkflow from static_analysis_bot.clang.format import ClangFormatIssue tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', 'GECKO_HEAD_REV': 'deadbeef1234', } }, 'remoteTryTask': { 'dependencies': ['clang-format'] }, 'clang-format': { 'name': 'source-test-clang-format', 'state': 'completed', 'artifacts': { 'public/code-review/clang-format.json': { 'test.cpp': [{ 'line_offset': 11, 'char_offset': 44616, 'char_length': 7, 'lines_modified': 2, 'line': 1386, 'replacement': 'Multi\nlines', }] } } } } workflow = RemoteWorkflow(MockQueue(tasks)) issues = workflow.run(mock_revision) assert len(issues) == 1 issue = issues[0] assert isinstance(issue, ClangFormatIssue) assert issue.path == 'test.cpp' assert issue.line == 1386 assert issue.nb_lines == 2 assert issue.patch == 'Multi\nlines' assert issue.column == 11 assert issue.as_dict() == { 'analyzer': 'clang-format', 'column': 11, 'in_patch': False, 'is_new': True, 'line': 1386, 'nb_lines': 2, 'patch': 'Multi\nlines', 'path': 'test.cpp', 'publishable': False, 'validates': False, 'validation': {} }
def test_baseline(mock_try_config, mock_revision): ''' Test a normal remote workflow (aka Try mode) - current task with analyzer deps - an analyzer in failed status - with some issues in its log ''' from static_analysis_bot.workflows.remote import RemoteWorkflow from static_analysis_bot.lint import MozLintIssue # We run on a mock TC, with a try source assert mock_try_config.taskcluster.task_id == 'local instance' assert mock_try_config.source == 'try' assert mock_try_config.try_task_id == 'remoteTryTask' # We do not want to check local files with this worfklow mock_try_config.has_local_clone = False tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', 'GECKO_HEAD_REV': 'deadbeef1234', } }, 'remoteTryTask': { 'dependencies': ['analyzer-A', 'analyzer-B'] }, 'analyzer-A': { 'name': 'source-test-mozlint-flake8', 'state': 'failed', 'artifacts': { 'failures.log': '\n'.join([ 'something else', 'xx123 TEST-UNEXPECTED-ERROR | test.cpp:12:1 | strange issue (checker XXX)', ]) } }, 'analyzer-B': {}, 'extra-task': {}, } workflow = RemoteWorkflow(MockQueue(tasks)) issues = workflow.run(mock_revision) assert len(issues) == 1 issue = issues[0] assert isinstance(issue, MozLintIssue) assert issue.path == 'test.cpp' assert issue.line == 12 assert issue.column == 1 assert issue.message == 'strange issue' assert issue.rule == 'checker XXX' assert issue.revision is mock_revision assert issue.validates()
def test_clang_tidy_task(mock_try_config, mock_revision): ''' Test a remote workflow with a clang-tidy analyzer ''' from static_analysis_bot.workflows.remote import RemoteWorkflow from static_analysis_bot.clang.tidy import ClangTidyIssue tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', 'GECKO_HEAD_REV': 'deadbeef1234', } }, 'remoteTryTask': { 'dependencies': ['clang-tidy'] }, 'clang-tidy': { 'name': 'source-test-clang-tidy', 'state': 'completed', 'artifacts': { 'public/code-review/clang-tidy.json': { 'files': { 'test.cpp': { 'hash': 'e409f05a10574adb8d47dcb631f8e3bb', 'warnings': [ { 'column': 12, 'line': 123, 'flag': 'checker.XXX', 'message': 'some hard issue with c++', 'filename': 'test.cpp', } ] } } }, } } } workflow = RemoteWorkflow(MockQueue(tasks)) issues = workflow.run(mock_revision) assert len(issues) == 1 issue = issues[0] assert isinstance(issue, ClangTidyIssue) assert issue.path == 'test.cpp' assert issue.line == 123 assert issue.char == 12 assert issue.check == 'checker.XXX' assert issue.message == 'some hard issue with c++'
def test_clang_tidy_task(mock_try_config, mock_revision): ''' Test a remote workflow with a clang-tidy analyzer ''' from static_analysis_bot.workflows.remote import RemoteWorkflow from static_analysis_bot.clang.tidy import ClangTidyIssue tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', 'GECKO_HEAD_REV': 'deadbeef1234', } }, 'remoteTryTask': { 'dependencies': ['clang-tidy'] }, 'clang-tidy': { 'name': 'source-test-clang-tidy', 'state': 'completed', 'artifacts': { 'public/code-review/clang-tidy.json': { 'files': { 'test.cpp': { 'hash': 'e409f05a10574adb8d47dcb631f8e3bb', 'warnings': [{ 'column': 12, 'line': 123, 'flag': 'checker.XXX', 'message': 'some hard issue with c++', 'filename': 'test.cpp', }] } } }, } } } workflow = RemoteWorkflow(MockQueue(tasks)) issues = workflow.run(mock_revision) assert len(issues) == 1 issue = issues[0] assert isinstance(issue, ClangTidyIssue) assert issue.path == 'test.cpp' assert issue.line == 123 assert issue.char == 12 assert issue.check == 'checker.XXX' assert issue.message == 'some hard issue with c++'
def test_mozlint_task(mock_try_config, mock_revision): ''' Test a remote workflow with a mozlint analyzer ''' from static_analysis_bot.workflows.remote import RemoteWorkflow from static_analysis_bot.lint import MozLintIssue tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', 'GECKO_HEAD_REV': 'deadbeef1234', } }, 'remoteTryTask': { 'dependencies': ['mozlint'] }, 'mozlint': { 'name': 'source-test-mozlint-dummy', 'state': 'failed', 'artifacts': { 'public/code-review/mozlint.json': { 'test.cpp': [ { 'path': 'test.cpp', 'lineno': 42, 'column': 51, 'level': 'error', 'linter': 'flake8', 'rule': 'E001', 'message': 'dummy issue', } ] }, } } } workflow = RemoteWorkflow(MockQueue(tasks)) issues = workflow.run(mock_revision) assert len(issues) == 1 issue = issues[0] assert isinstance(issue, MozLintIssue) assert issue.path == 'test.cpp' assert issue.line == 42 assert issue.column == 51 assert issue.linter == 'flake8' assert issue.message == 'dummy issue'
def test_mozlint_task(mock_try_config, mock_revision): ''' Test a remote workflow with a mozlint analyzer ''' from static_analysis_bot.workflows.remote import RemoteWorkflow from static_analysis_bot.lint import MozLintIssue tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', 'GECKO_HEAD_REV': 'deadbeef1234', } }, 'remoteTryTask': { 'dependencies': ['mozlint'] }, 'mozlint': { 'name': 'source-test-mozlint-dummy', 'state': 'failed', 'artifacts': { 'public/code-review/mozlint.json': { 'test.cpp': [{ 'path': 'test.cpp', 'lineno': 42, 'column': 51, 'level': 'error', 'linter': 'flake8', 'rule': 'E001', 'message': 'dummy issue', }] }, } } } workflow = RemoteWorkflow(MockQueue(tasks)) issues = workflow.run(mock_revision) assert len(issues) == 1 issue = issues[0] assert isinstance(issue, MozLintIssue) assert issue.path == 'test.cpp' assert issue.line == 42 assert issue.column == 51 assert issue.linter == 'flake8' assert issue.message == 'dummy issue'
def test_no_deps(mock_try_config, mock_revision): ''' Test an error occurs when no dependencies are found on root task ''' from static_analysis_bot.workflows.remote import RemoteWorkflow tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', 'GECKO_HEAD_REV': 'deadbeef1234', } }, 'remoteTryTask': {}, 'extra-task': {}, } workflow = RemoteWorkflow(MockQueue(tasks)) with pytest.raises(AssertionError) as e: workflow.run(mock_revision) assert str(e.value) == 'No task dependencies to analyze'
def test_no_failed(mock_try_config, mock_revision): ''' Test a remote workflow without any failed tasks ''' from static_analysis_bot.workflows.remote import RemoteWorkflow tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', 'GECKO_HEAD_REV': 'deadbeef1234', } }, 'remoteTryTask': { 'dependencies': ['analyzer-A', 'analyzer-B'] }, 'analyzer-A': {}, 'analyzer-B': {}, 'extra-task': {}, } workflow = RemoteWorkflow(MockQueue(tasks)) issues = workflow.run(mock_revision) assert len(issues) == 0
def test_decision_task(mock_try_config, mock_revision): ''' Test a remote workflow with different decision task setup ''' from static_analysis_bot.workflows.remote import RemoteWorkflow tasks = { 'decision': {}, 'remoteTryTask': {}, } workflow = RemoteWorkflow(MockQueue(tasks)) with pytest.raises(AssertionError) as e: workflow.run(mock_revision) assert str(e.value) == 'Missing decision task' tasks = { 'decision': { 'image': 'anotherImage', }, 'remoteTryTask': {}, } workflow = RemoteWorkflow(MockQueue(tasks)) with pytest.raises(AssertionError) as e: workflow.run(mock_revision) assert str(e.value) == 'Missing decision task' tasks = { 'decision': { 'image': { 'from': 'taskcluster/decision', 'tag': 'unsupported', } }, 'remoteTryTask': {}, } workflow = RemoteWorkflow(MockQueue(tasks)) with pytest.raises(AssertionError) as e: workflow.run(mock_revision) assert str(e.value) == 'Missing decision task' tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', }, 'remoteTryTask': {}, } workflow = RemoteWorkflow(MockQueue(tasks)) with pytest.raises(AssertionError) as e: workflow.run(mock_revision) assert str(e.value) == 'Not the try repo in GECKO_HEAD_REPOSITORY' tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', } }, 'remoteTryTask': {}, } workflow = RemoteWorkflow(MockQueue(tasks)) with pytest.raises(AssertionError) as e: workflow.run(mock_revision) assert str(e.value) == 'Missing try revision' assert mock_revision.mercurial_revision is None tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', 'GECKO_HEAD_REV': 'someRevision' } }, 'remoteTryTask': {}, } workflow = RemoteWorkflow(MockQueue(tasks)) with pytest.raises(AssertionError) as e: workflow.run(mock_revision) assert str(e.value) == 'No task dependencies to analyze' assert mock_revision.mercurial_revision is not None assert mock_revision.mercurial_revision == 'someRevision'
def test_decision_task(mock_try_config, mock_revision): ''' Test a remote workflow with different decision task setup ''' from static_analysis_bot.workflows.remote import RemoteWorkflow tasks = { 'decision': { }, 'remoteTryTask': { }, } workflow = RemoteWorkflow(MockQueue(tasks)) with pytest.raises(AssertionError) as e: workflow.run(mock_revision) assert str(e.value) == 'Missing decision task' tasks = { 'decision': { 'image': 'anotherImage', }, 'remoteTryTask': { }, } workflow = RemoteWorkflow(MockQueue(tasks)) with pytest.raises(AssertionError) as e: workflow.run(mock_revision) assert str(e.value) == 'Missing decision task' tasks = { 'decision': { 'image': { 'from': 'taskcluster/decision', 'tag': 'unsupported', } }, 'remoteTryTask': { }, } workflow = RemoteWorkflow(MockQueue(tasks)) with pytest.raises(AssertionError) as e: workflow.run(mock_revision) assert str(e.value) == 'Missing decision task' tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', }, 'remoteTryTask': { }, } workflow = RemoteWorkflow(MockQueue(tasks)) with pytest.raises(AssertionError) as e: workflow.run(mock_revision) assert str(e.value) == 'Not the try repo in GECKO_HEAD_REPOSITORY' tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', } }, 'remoteTryTask': { }, } workflow = RemoteWorkflow(MockQueue(tasks)) with pytest.raises(AssertionError) as e: workflow.run(mock_revision) assert str(e.value) == 'Missing try revision' assert mock_revision.mercurial_revision is None tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', 'GECKO_HEAD_REV': 'someRevision' } }, 'remoteTryTask': { }, } workflow = RemoteWorkflow(MockQueue(tasks)) with pytest.raises(AssertionError) as e: workflow.run(mock_revision) assert str(e.value) == 'No task dependencies to analyze' assert mock_revision.mercurial_revision is not None assert mock_revision.mercurial_revision == 'someRevision'
def test_baseline(mock_try_config, mock_revision): ''' Test a normal remote workflow (aka Try mode) - current task with analyzer deps - an analyzer in failed status - with some issues in its log ''' from static_analysis_bot.workflows.remote import RemoteWorkflow from static_analysis_bot.lint import MozLintIssue # We run on a mock TC, with a try source assert mock_try_config.taskcluster.task_id == 'local instance' assert mock_try_config.source == 'try' assert mock_try_config.try_task_id == 'remoteTryTask' # We do not want to check local files with this worfklow mock_try_config.has_local_clone = False tasks = { 'decision': { 'image': 'taskcluster/decision:XXX', 'env': { 'GECKO_HEAD_REPOSITORY': 'https://hg.mozilla.org/try', 'GECKO_HEAD_REV': 'deadbeef1234', } }, 'remoteTryTask': { 'dependencies': ['analyzer-A', 'analyzer-B'] }, 'analyzer-A': { 'name': 'source-test-mozlint-flake8', 'state': 'failed', 'artifacts': { 'public/code-review/mozlint.json': { 'test.cpp': [ { 'path': 'test.cpp', 'lineno': 12, 'column': 1, 'level': 'error', 'linter': 'flake8', 'rule': 'checker XXX', 'message': 'strange issue', } ] }, } }, 'analyzer-B': {}, 'extra-task': {}, } workflow = RemoteWorkflow(MockQueue(tasks)) issues = workflow.run(mock_revision) assert len(issues) == 1 issue = issues[0] assert isinstance(issue, MozLintIssue) assert issue.path == 'test.cpp' assert issue.line == 12 assert issue.column == 1 assert issue.message == 'strange issue' assert issue.rule == 'checker XXX' assert issue.revision is mock_revision assert issue.validates()