Пример #1
0
class Processor(object):

    def __init__(self, client, number, head, target_path):
        self._client = client
        self._number = number
        self._head = head
        self._target_path = target_path
        self._changes = None
        self._problems = Problems(target_path)
        self._review = Review(client, number)

    def load_changes(self):
        log.info('Loading pull request patches from github.')
        files = self._client.pull_requests.list_files(self._number)
        pull_request_patches = files.all()
        self._changes = DiffCollection(pull_request_patches)
        self._problems.set_changes(self._changes)

    def run_tools(self, review_config):
        if self._changes is None:
            raise RuntimeError('No loaded changes, cannot run tools. '
                               'Try calling load_changes first.')
        files_to_check = self._changes.get_files(
            append_base=self._target_path,
            ignore_patterns=review_config.ignore_patterns())
        tools.run(
            review_config,
            self._problems,
            files_to_check,
            self._target_path)

    def publish(self, wait_time=0):
        self._problems.limit_to_changes()
        self._review.publish(self._problems, self._head, wait_time)
Пример #2
0
    def test_publish_summary(self):
        problems = Problems()

        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            IssueComment('Terrible things'),
            Comment(filename_1, 117, 117, 'Something bad'),
            Comment(filename_1, 119, 119, 'Something bad'),
        )
        problems.add_many(errors)
        problems.set_changes([1])

        review = Review(self.repo, self.pr, self.config)
        review.publish_summary(problems)

        assert self.pr.create_comment.called
        self.assertEqual(1, self.pr.create_comment.call_count)

        msg = """There are 3 errors:

* Terrible things
* Console/Command/Task/AssetBuildTask.php, line 117 - Something bad
* Console/Command/Task/AssetBuildTask.php, line 119 - Something bad
"""
        self.pr.create_comment.assert_called_with(msg)
Пример #3
0
    def test_publish_summary(self):
        gh = Mock()
        problems = Problems()

        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something bad'),
        )
        problems.add_many(errors)
        problems.set_changes([1])
        sha = 'abc123'

        review = Review(gh, 3)
        review.publish_summary(problems)

        assert gh.issues.comments.create.called
        eq_(1, gh.issues.comments.create.call_count)
        calls = gh.issues.comments.create.call_args_list

        msg = """There are 2 errors:

* Console/Command/Task/AssetBuildTask.php, line 117 - Something bad
* Console/Command/Task/AssetBuildTask.php, line 119 - Something bad
"""
        expected = call(3, msg)
        eq_(calls[0], expected)
Пример #4
0
class Processor(object):

    _client = None
    _number = None
    _head = None
    _target_path = None
    _changes = None
    _problems = None
    _review = None
    _config = None

    def __init__(self, client, number, head, target_path, config=None):
        self._client = client
        self._number = number
        self._head = head
        self._target_path = target_path
        self._problems = Problems(target_path)
        self._review = Review(client, number)

        if config is None:
            config = {}
        self._config = config

    def load_changes(self):
        log.info('Loading pull request patches from github.')
        files = self._client.pull_requests.list_files(self._number)
        pull_request_patches = files.all()
        self._changes = DiffCollection(pull_request_patches)
        self._problems.set_changes(self._changes)

    def run_tools(self, review_config):
        if self._changes is None:
            raise RuntimeError('No loaded changes, cannot run tools. '
                               'Try calling load_changes first.')
        files_to_check = self._changes.get_files(
            append_base=self._target_path,
            ignore_patterns=review_config.ignore_patterns())
        commits_to_check = self.get_commits(self._number)
        tools.run(
            review_config,
            self._problems,
            files_to_check,
            commits_to_check,
            self._target_path)

    def publish(self):
        self._problems.limit_to_changes()
        self._review.publish(
            self._problems,
            self._head,
            self._config.get('SUMMARY_THRESHOLD'))

    def get_commits(self, number):
        return self._client.pull_requests.list_commits(number).all()
Пример #5
0
class Processor(object):

    _repository = None
    _pull_request = None
    _target_path = None
    _changes = None
    _problems = None
    _review = None
    _config = None

    def __init__(self, repository, pull_request, target_path, config=None):
        config = config if config else {}
        self._config = config
        self._repository = repository
        self._pull_request = pull_request
        self._target_path = target_path
        self._problems = Problems(target_path)
        self._review = Review(repository, pull_request, config)

    def load_changes(self):
        log.info('Loading pull request patches from github.')
        files = self._pull_request.files()
        self._changes = DiffCollection(files)
        self._problems.set_changes(self._changes)

    def run_tools(self, review_config):
        if self._changes is None:
            raise RuntimeError('No loaded changes, cannot run tools. '
                               'Try calling load_changes first.')
        files_to_check = self._changes.get_files(
            append_base=self._target_path,
            ignore_patterns=review_config.ignore_patterns())
        commits_to_check = self._pull_request.commits()
        log.debug("_problems before tools: %s" % len(self._problems))
        self._problems = tools.run(
            review_config,
            self._problems,
            files_to_check,
            commits_to_check,
            self._target_path)
        log.debug("_problems after tools: %s" % len(self._problems))


    def publish(self):
        self._problems.limit_to_changes()
        self._review.publish(
            self._problems,
            self._pull_request.head,
            self._config.get('SUMMARY_THRESHOLD'))
Пример #6
0
    def test_publish_comment_threshold_checks(self):
        fixture = load_fixture('comments_current.json')
        self.pr.review_comments.return_value = [GhIssueComment(f) for f in json.loads(fixture)]

        problems = Problems()

        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something bad'),
        )
        problems.add_many(errors)
        problems.set_changes([1])
        sha = 'abc123'

        review = Review(self.gh, 3)
        review.publish_summary = Mock()
        review.publish(problems, sha, 1)

        assert review.publish_summary.called, 'Should have been called.'
Пример #7
0
    def test_publish_review_no_count_change(self, pub_status_mock, _):
        fixture = load_fixture('comments_current.json')
        self.pr.review_comments.return_value = [
            GhIssueComment(f, self.session) for f in json.loads(fixture)]
        problems = Problems()

        # Match the line/positions in comments_current.json
        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            Comment(filename_1, 40, 40, '2. Something bad'),
            Comment(filename_1, 87, 87, '1. Something bad'),
            Comment(filename_1, 89, 89, '2. Something bad'),
        )
        problems.add_many(errors)
        problems.set_changes([1])
        sha = 'abc123'

        tst_config = build_review_config(fixer_ini, {'SUMMARY_THRESHOLD': 1})
        review = Review(self.repo, self.pr, tst_config)

        review.publish_review(problems, sha)
        # Ensure publish_status(True) means the status=failed
        pub_status_mock.assert_called_with(True)
Пример #8
0
    def test_publish_comment_threshold_checks(self, http):
        fixture_data = load_fixture('comments_current.json')
        response = Response()
        response._content = fixture_data
        http.return_value = response

        gh = Github()
        problems = Problems()

        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something bad'),
        )
        problems.add_many(errors)
        problems.set_changes([1])
        sha = 'abc123'

        review = Review(gh, 3)
        review.publish_summary = Mock()
        review.publish(problems, sha, 1)

        assert review.publish_summary.called, 'Should have been called.'
Пример #9
0
    def test_publish_review_no_count_change(self, pub_status_mock, _):
        fixture = load_fixture('comments_current.json')
        self.pr.review_comments.return_value = [
            GhIssueComment(f, self.session) for f in json.loads(fixture)]
        problems = Problems()

        # Match the line/positions in comments_current.json
        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            Comment(filename_1, 40, 40, '2. Something bad'),
            Comment(filename_1, 87, 87, '1. Something bad'),
            Comment(filename_1, 89, 89, '2. Something bad'),
        )
        problems.add_many(errors)
        problems.set_changes([1])
        sha = 'abc123'

        tst_config = build_review_config(fixer_ini, {'SUMMARY_THRESHOLD': 1})
        review = Review(self.repo, self.pr, tst_config)

        review.publish_review(problems, sha)
        # Ensure publish_status(True) means the status=failed
        pub_status_mock.assert_called_with(True)
Пример #10
0
    def test_publish_review_comment_threshold_checks(self):
        fixture = load_fixture('comments_current.json')
        self.pr.review_comments.return_value = [
            GhIssueComment(f, self.session) for f in json.loads(fixture)
        ]

        problems = Problems()

        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            Comment(filename_1, 117, 117, 'Something bad'),
            Comment(filename_1, 119, 119, 'Something bad'),
        )
        problems.add_many(errors)
        problems.set_changes([1])
        sha = 'abc123'

        tst_config = build_review_config(fixer_ini, {'SUMMARY_THRESHOLD': 1})
        review = Review(self.repo, self.pr, tst_config)
        with patch('lintreview.review.Review.publish_summary') as pub_sum_mock:
            review.publish_review(problems, sha)

            self.assertTrue(pub_sum_mock.called)
Пример #11
0
    def test_publish_comment_threshold_checks(self, http):
        fixture_data = load_fixture('comments_current.json')
        response = Response()
        response._content = fixture_data
        http.return_value = response

        gh = Github()
        problems = Problems()

        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something bad'),
        )
        problems.add_many(errors)
        problems.set_changes([1])
        sha = 'abc123'

        review = Review(gh, 3)
        review.publish_summary = Mock()
        review.publish(problems, sha, 1)

        assert review.publish_summary.called, 'Should have been called.'
Пример #12
0
    def test_publish_comment_threshold_checks(self):
        fixture = load_fixture('comments_current.json')
        self.pr.review_comments.return_value = [
            GhIssueComment(f) for f in json.loads(fixture)
        ]

        problems = Problems()

        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            Comment(filename_1, 117, 117, 'Something bad'),
            Comment(filename_1, 119, 119, 'Something bad'),
        )
        problems.add_many(errors)
        problems.set_changes([1])
        sha = 'abc123'

        config = build_review_config(fixer_ini, {'SUMMARY_THRESHOLD': 1})
        review = Review(self.repo, self.pr, config)
        review.publish_summary = Mock()
        review.publish(problems, sha)

        assert review.publish_summary.called, 'Should have been called.'
Пример #13
0
    def test_publish_review_comment_threshold_checks(self):
        fixture = load_fixture('comments_current.json')
        self.pr.review_comments.return_value = [
            GhIssueComment(f, self.session) for f in json.loads(fixture)
        ]

        problems = Problems()

        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            Comment(filename_1, 117, 117, 'Something bad'),
            Comment(filename_1, 119, 119, 'Something bad'),
        )
        problems.add_many(errors)
        problems.set_changes([1])
        sha = 'abc123'

        tst_config = build_review_config(fixer_ini, {'SUMMARY_THRESHOLD': 1})
        review = Review(self.repo, self.pr, tst_config)
        with patch('lintreview.review.Review.publish_summary') as pub_sum_mock:
            review.publish_review(problems, sha)

            self.assertTrue(pub_sum_mock.called)
Пример #14
0
    def test_publish_summary(self):
        problems = Problems()

        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something bad'),
        )
        problems.add_many(errors)
        problems.set_changes([1])

        review = Review(self.repo, self.pr)
        review.publish_summary(problems)

        assert self.pr.create_comment.called
        eq_(1, self.pr.create_comment.call_count)

        msg = """There are 2 errors:

* Console/Command/Task/AssetBuildTask.php, line 117 - Something bad
* Console/Command/Task/AssetBuildTask.php, line 119 - Something bad
"""
        self.pr.create_comment.assert_called_with(msg)
Пример #15
0
    def test_publish_summary(self):
        problems = Problems()

        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something bad'),
        )
        problems.add_many(errors)
        problems.set_changes([1])

        review = Review(self.gh, 3)
        review.publish_summary(problems)

        assert self.issue.create_comment.called
        eq_(1, self.issue.create_comment.call_count)

        msg = """There are 2 errors:

* Console/Command/Task/AssetBuildTask.php, line 117 - Something bad
* Console/Command/Task/AssetBuildTask.php, line 119 - Something bad
"""
        self.issue.create_comment.assert_called_with(msg)
Пример #16
0
class TestProblems(TestCase):

    two_files_json = load_fixture('two_file_pull_request.json')

    # Block offset so lines don't match offsets
    block_offset = load_fixture('pull_request_line_offset.json')

    def setUp(self):
        self.problems = Problems()
        self.session = GitHubSession()

    def test_add(self):
        self.problems.add('file.py', 10, 'Not good')
        self.assertEqual(1, len(self.problems))

        self.problems.add('file.py', 11, 'Not good')
        self.assertEqual(2, len(self.problems))
        self.assertEqual(2, len(self.problems.all()))
        self.assertEqual(2, len(self.problems.all('file.py')))
        self.assertEqual(0, len(self.problems.all('not there')))

    def test_add__duplicate_is_ignored(self):
        self.problems.add('file.py', 10, 'Not good')
        self.assertEqual(1, len(self.problems))

        self.problems.add('file.py', 10, 'Not good')
        self.assertEqual(1, len(self.problems))

    def test_add__same_line_combines(self):
        self.problems.add('file.py', 10, 'Tabs bad')
        self.problems.add('file.py', 10, 'Spaces are good')
        self.assertEqual(1, len(self.problems))

        result = self.problems.all()
        expected = 'Tabs bad\nSpaces are good'
        self.assertEqual(expected, result[0].body)

    def test_add__same_line_ignores_duplicates(self):
        self.problems.add('file.py', 10, 'Tabs bad')
        self.problems.add('file.py', 10, 'Tabs bad')
        self.assertEqual(1, len(self.problems))

        result = self.problems.all()
        expected = 'Tabs bad'
        self.assertEqual(expected, result[0].body)

    def test_add__with_diff_containing_block_offset(self):
        res = [
            PullFile(f, self.session) for f in json.loads(self.block_offset)
        ]
        changes = DiffCollection(res)

        problems = Problems(changes=changes)
        line_num = 32
        problems.add('somefile.py', line_num, 'Not good')
        self.assertEqual(1, len(problems))

        result = problems.all('somefile.py')
        first_result = result[0]
        self.assertIsInstance(first_result, Comment)
        self.assertEqual(
            changes.line_position('somefile.py', line_num),
            first_result.position,
            'Offset should be transformed to match value in changes'
        )

    def test_add_many(self):
        errors = [
            Comment('some/file.py', 10, 10, 'Thing is wrong'),
            Comment('some/file.py', 12, 12, 'Not good'),
        ]
        self.problems.add_many(errors)
        result = self.problems.all('some/file.py')
        self.assertEqual(2, len(result))
        self.assertEqual(errors, result)

    def test_error_count(self):
        errors = [
            Comment('some/file.py', 10, 10, 'Thing is wrong'),
            Comment('some/file.py', 12, 12, 'Not good'),
        ]
        self.problems.add_many(errors)
        assert 2 == len(self.problems)
        assert 2 == self.problems.error_count()

    def test_error_count_exclude_info(self):
        errors = [
            Comment('some/file.py', 10, 10, 'Thing is wrong'),
            InfoComment('some content'),
        ]
        self.problems.add_many(errors)
        assert 1 == self.problems.error_count()
        assert 2 == len(self.problems)

    def test_limit_to_changes__remove_problems(self):
        res = [
            PullFile(f, self.session) for f in json.loads(self.two_files_json)
        ]
        changes = DiffCollection(res)

        # Setup some fake problems.
        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            Comment(None, None, None, 'This is a general comment'),
            Comment(filename_1, 117, 117, 'Something bad'),
            Comment(filename_1, 119, 119, 'Something else bad'),
            Comment(filename_1, 130, 130, 'Filtered out, line is not changed'),
        )
        self.problems.add_many(errors)
        filename_2 = 'Test/test_files/View/Parse/single.ctp'
        errors = (
            Comment(filename_2, 2, 2, 'Filtered out'),
            Comment(filename_2, 3, 3, 'Something bad'),
            Comment(filename_2, 7, 7, 'Filtered out'),
        )
        self.problems.add_many(errors)
        self.problems.set_changes(changes)
        self.problems.limit_to_changes()

        result = self.problems.all(filename_1)
        self.assertEqual(2, len(result))
        expected = [
            Comment(filename_1, 117, 117, 'Something bad'),
            Comment(filename_1, 119, 119, 'Something else bad')]
        self.assertEqual(len(result), len(expected))
        self.assertEqual(result, expected)

        result = self.problems.all(filename_2)
        self.assertEqual(1, len(result))
        expected = [
            Comment(filename_2, 3, 3, 'Something bad')
        ]
        self.assertEqual(result, expected)

    def test_has_changes(self):
        problems = Problems(changes=None)
        self.assertFalse(problems.has_changes())

        problems = Problems(changes=[1])
        assert problems.has_changes()
Пример #17
0
class Processor(object):

    _repository = None
    _pull_request = None
    _target_path = None
    _changes = None
    _review = None
    _config = None
    problems = None

    def __init__(self, repository, pull_request, target_path, config):
        self._config = config
        self._repository = repository
        self._pull_request = pull_request
        self._target_path = target_path
        self.problems = Problems()
        self._review = Review(repository, pull_request, config)

    def load_changes(self):
        log.info('Loading pull request patches from github.')
        files = self._pull_request.files()
        self._changes = DiffCollection(files)
        self.problems.set_changes(self._changes)

    def run_tools(self):
        if self._changes is None:
            raise RuntimeError('No loaded changes, cannot run tools. '
                               'Try calling load_changes first.')
        config = self._config

        files_to_check = self._changes.get_files(
            ignore_patterns=config.ignore_patterns())
        commits_to_check = self._pull_request.commits()

        tool_list = tools.factory(config, self.problems, self._target_path)

        if config.fixers_enabled():
            self.apply_fixers(tool_list, files_to_check)

        tools.run(tool_list, files_to_check, commits_to_check)

    def apply_fixers(self, tool_list, files_to_check):
        try:
            fixer_context = fixers.create_context(
                self._config,
                self._target_path,
                self._repository,
                self._pull_request,
            )
            fixer_diff = fixers.run_fixers(tool_list, self._target_path,
                                           files_to_check)
            fixers.apply_fixer_diff(self._changes, fixer_diff, fixer_context)
        except (ConfigurationError, WorkflowError) as e:
            log.info('Fixer application failed. Got %s', e)
            message = u'Unable to apply fixers. {}'.format(e)
            self.problems.add(InfoComment(message))
            fixers.rollback_changes(self._target_path, self._pull_request.head)
        except Exception as e:
            log.info(
                'Fixer application failed, '
                'rolling back working tree. Got %s', e)
            fixers.rollback_changes(self._target_path, self._pull_request.head)

    def publish(self, check_run_id=None):
        self.problems.limit_to_changes()
        if check_run_id:
            self._review.publish_checkrun(self.problems, check_run_id)
        else:
            self._review.publish_review(self.problems, self._pull_request.head)
Пример #18
0
class TestProblems(TestCase):

    two_files_json = load_fixture('two_file_pull_request.json')

    # Block offset so lines don't match offsets
    block_offset = load_fixture('pull_request_line_offset.json')

    def setUp(self):
        self.problems = Problems()

    def test_add(self):
        self.problems.add('file.py', 10, 'Not good')
        eq_(1, len(self.problems))

        self.problems.add('file.py', 11, 'Not good')
        eq_(2, len(self.problems))
        eq_(2, len(self.problems.all()))
        eq_(2, len(self.problems.all('file.py')))
        eq_(0, len(self.problems.all('not there')))

    def test_add__duplicate_is_ignored(self):
        self.problems.add('file.py', 10, 'Not good')
        eq_(1, len(self.problems))

        self.problems.add('file.py', 10, 'Not good')
        eq_(1, len(self.problems))

    def test_add__with_base_path(self):
        problems = Problems('/some/path/')
        problems.add('/some/path/file.py', 10, 'Not good')
        eq_([], problems.all('/some/path/file.py'))
        eq_(1, len(problems.all('file.py')))
        eq_(1, len(problems))

    def test_add__with_base_path_no_trailing_slash(self):
        problems = Problems('/some/path')
        problems.add('/some/path/file.py', 10, 'Not good')
        eq_([], problems.all('/some/path/file.py'))
        eq_(1, len(problems.all('file.py')))
        eq_(1, len(problems))

    def test_add__with_diff_containing_block_offset(self):
        res = Resource.loads(self.block_offset)
        changes = DiffCollection(res)

        problems = Problems(changes=changes)
        line_num = 32
        problems.add('somefile.py', line_num, 'Not good')
        eq_(1, len(problems))

        result = problems.all('somefile.py')
        eq_(changes.line_position('somefile.py', line_num), result[0].position,
            'Offset should be transformed to match value in changes')

    def test_add_many(self):
        errors = [
            ('some/file.py', 10, 'Thing is wrong'),
            ('some/file.py', 12, 'Not good'),
        ]
        self.problems.add_many(errors)
        result = self.problems.all('some/file.py')
        eq_(2, len(result))
        expected = [
            Comment(errors[0][0], errors[0][1], errors[0][1], errors[0][2]),
            Comment(errors[1][0], errors[1][1], errors[1][1], errors[1][2]),
        ]
        eq_(expected, result)

    def test_limit_to_changes__remove_problems(self):
        res = Resource.loads(self.two_files_json)
        changes = DiffCollection(res)

        # Setup some fake problems.
        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something else bad'),
            (filename_1, 130, 'Filtered out, as line is not changed'),
        )
        self.problems.add_many(errors)
        filename_2 = 'Test/test_files/View/Parse/single.ctp'
        errors = (
            (filename_2, 2, 'Filtered out'),
            (filename_2, 3, 'Something bad'),
            (filename_2, 7, 'Filtered out'),
        )
        self.problems.add_many(errors)
        self.problems.set_changes(changes)
        self.problems.limit_to_changes()

        result = self.problems.all(filename_1)
        eq_(2, len(result))
        expected = [
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something else bad')]
        eq_(result.sort(), expected.sort())

        result = self.problems.all(filename_2)
        eq_(1, len(result))
        expected = [Comment(filename_2, 3, 3, 'Something bad')]
        eq_(result, expected)

    def test_publish_problems(self):
        gh = Mock()
        problems = Problems()

        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something bad'),
        )
        problems.add_many(errors)
        sha = 'abc123'

        review = Review(gh, 3)
        review.publish_problems(problems, sha)

        assert gh.pull_requests.comments.create.called
        eq_(2, gh.pull_requests.comments.create.call_count)
        calls = gh.pull_requests.comments.create.call_args_list

        expected = call(3, {
            'commit_id': sha,
            'path': errors[0][0],
            'position': errors[0][1],
            'body': errors[0][2]
        })
        eq_(calls[0], expected)

        expected = call(3, {
            'commit_id': sha,
            'path': errors[1][0],
            'position': errors[1][1],
            'body': errors[1][2]
        })
        eq_(calls[1], expected)

    @patch('lintreview.review.time')
    def test_publish_with__wait_time(self, time):
        gh = Mock()
        problems = Problems()
        review = Review(gh, 3)

        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something bad'),
        )
        problems.add_many(errors)
        sha = 'abc123'

        review.publish_problems(problems, sha, 1)
        assert time.sleep.called
        eq_(2, time.sleep.call_count)
        calls = time.sleep.call_args_list

        expected = call(1)
        eq_(calls[0], expected)
        eq_(calls[1], expected)

    def test_publish_ok_comment(self):
        gh = Mock()
        problems = Problems()
        review = Review(gh, 3)

        sha = 'abc123'
        review.publish(problems, sha)

        assert not(gh.pull_requests.comments.create.called)
        assert gh.issues.comments.create.called

        calls = gh.issues.comments.create.call_args_list

        expected = call(3, ':+1: No lint errors found.')
        eq_(calls[0], expected)
Пример #19
0
class TestProblems(TestCase):

    two_files_json = load_fixture('two_file_pull_request.json')

    # Block offset so lines don't match offsets
    block_offset = load_fixture('pull_request_line_offset.json')

    def setUp(self):
        self.problems = Problems()

    def test_add(self):
        self.problems.add('file.py', 10, 'Not good')
        eq_(1, len(self.problems))

        self.problems.add('file.py', 11, 'Not good')
        eq_(2, len(self.problems))
        eq_(2, len(self.problems.all()))
        eq_(2, len(self.problems.all('file.py')))
        eq_(0, len(self.problems.all('not there')))

    def test_add__duplicate_is_ignored(self):
        self.problems.add('file.py', 10, 'Not good')
        eq_(1, len(self.problems))

        self.problems.add('file.py', 10, 'Not good')
        eq_(1, len(self.problems))

    def test_add__with_base_path(self):
        problems = Problems('/some/path/')
        problems.add('/some/path/file.py', 10, 'Not good')
        eq_([], problems.all('/some/path/file.py'))
        eq_(1, len(problems.all('file.py')))
        eq_(1, len(problems))

    def test_add__with_base_path_no_trailing_slash(self):
        problems = Problems('/some/path')
        problems.add('/some/path/file.py', 10, 'Not good')
        eq_([], problems.all('/some/path/file.py'))
        eq_(1, len(problems.all('file.py')))
        eq_(1, len(problems))

    def test_add__with_diff_containing_block_offset(self):
        res = Resource.loads(self.block_offset)
        changes = DiffCollection(res)

        problems = Problems(changes=changes)
        line_num = 32
        problems.add('somefile.py', line_num, 'Not good')
        eq_(1, len(problems))

        result = problems.all('somefile.py')
        eq_(changes.line_position('somefile.py', line_num), result[0].position,
            'Offset should be transformed to match value in changes')

    def test_add_many(self):
        errors = [
            ('some/file.py', 10, 'Thing is wrong'),
            ('some/file.py', 12, 'Not good'),
        ]
        self.problems.add_many(errors)
        result = self.problems.all('some/file.py')
        eq_(2, len(result))
        expected = [
            Comment(errors[0][0], errors[0][1], errors[0][1], errors[0][2]),
            Comment(errors[1][0], errors[1][1], errors[1][1], errors[1][2]),
        ]
        eq_(expected, result)

    def test_limit_to_changes__remove_problems(self):
        res = Resource.loads(self.two_files_json)
        changes = DiffCollection(res)

        # Setup some fake problems.
        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something else bad'),
            (filename_1, 130, 'Filtered out, as line is not changed'),
        )
        self.problems.add_many(errors)
        filename_2 = 'Test/test_files/View/Parse/single.ctp'
        errors = (
            (filename_2, 2, 'Filtered out'),
            (filename_2, 3, 'Something bad'),
            (filename_2, 7, 'Filtered out'),
        )
        self.problems.add_many(errors)
        self.problems.set_changes(changes)
        self.problems.limit_to_changes()

        result = self.problems.all(filename_1)
        eq_(2, len(result))
        expected = [(filename_1, 117, 'Something bad'),
                    (filename_1, 119, 'Something else bad')]
        eq_(result.sort(), expected.sort())

        result = self.problems.all(filename_2)
        eq_(1, len(result))
        expected = [Comment(filename_2, 3, 3, 'Something bad')]
        eq_(result, expected)

    def test_has_changes(self):
        problems = Problems(changes=None)
        self.assertFalse(problems.has_changes())

        problems = Problems(changes=[1])
        assert problems.has_changes()
Пример #20
0
class TestProblems(TestCase):

    two_files_json = load_fixture('two_file_pull_request.json')

    # Block offset so lines don't match offsets
    block_offset = load_fixture('pull_request_line_offset.json')

    def setUp(self):
        self.problems = Problems()

    def test_add(self):
        self.problems.add('file.py', 10, 'Not good')
        eq_(1, len(self.problems))

        self.problems.add('file.py', 11, 'Not good')
        eq_(2, len(self.problems))
        eq_(2, len(self.problems.all()))
        eq_(2, len(self.problems.all('file.py')))
        eq_(0, len(self.problems.all('not there')))

    def test_add__duplicate_is_ignored(self):
        self.problems.add('file.py', 10, 'Not good')
        eq_(1, len(self.problems))

        self.problems.add('file.py', 10, 'Not good')
        eq_(1, len(self.problems))

    def test_add__with_base_path(self):
        problems = Problems('/some/path/')
        problems.add('/some/path/file.py', 10, 'Not good')
        eq_([], problems.all('/some/path/file.py'))
        eq_(1, len(problems.all('file.py')))
        eq_(1, len(problems))

    def test_add__with_base_path_no_trailing_slash(self):
        problems = Problems('/some/path')
        problems.add('/some/path/file.py', 10, 'Not good')
        eq_([], problems.all('/some/path/file.py'))
        eq_(1, len(problems.all('file.py')))
        eq_(1, len(problems))

    def test_add__with_diff_containing_block_offset(self):
        res = Resource.loads(self.block_offset)
        changes = DiffCollection(res)

        problems = Problems(changes=changes)
        line_num = 32
        problems.add('somefile.py', line_num, 'Not good')
        eq_(1, len(problems))

        result = problems.all('somefile.py')
        eq_(changes.line_position('somefile.py', line_num), result[0].position,
            'Offset should be transformed to match value in changes')

    def test_add_many(self):
        errors = [
            ('some/file.py', 10, 'Thing is wrong'),
            ('some/file.py', 12, 'Not good'),
        ]
        self.problems.add_many(errors)
        result = self.problems.all('some/file.py')
        eq_(2, len(result))
        expected = [
            Comment(errors[0][0], errors[0][1], errors[0][1], errors[0][2]),
            Comment(errors[1][0], errors[1][1], errors[1][1], errors[1][2]),
        ]
        eq_(expected, result)

    def test_limit_to_changes__remove_problems(self):
        res = Resource.loads(self.two_files_json)
        changes = DiffCollection(res)

        # Setup some fake problems.
        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something else bad'),
            (filename_1, 130, 'Filtered out, as line is not changed'),
        )
        self.problems.add_many(errors)
        filename_2 = 'Test/test_files/View/Parse/single.ctp'
        errors = (
            (filename_2, 2, 'Filtered out'),
            (filename_2, 3, 'Something bad'),
            (filename_2, 7, 'Filtered out'),
        )
        self.problems.add_many(errors)
        self.problems.set_changes(changes)
        self.problems.limit_to_changes()

        result = self.problems.all(filename_1)
        eq_(2, len(result))
        expected = [(filename_1, 117, 'Something bad'),
                    (filename_1, 119, 'Something else bad')]
        eq_(result.sort(), expected.sort())

        result = self.problems.all(filename_2)
        eq_(1, len(result))
        expected = [Comment(filename_2, 3, 3, 'Something bad')]
        eq_(result, expected)

    def test_publish_problems(self):
        gh = Mock()
        problems = Problems()

        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something bad'),
        )
        problems.add_many(errors)
        sha = 'abc123'

        review = Review(gh, 3)
        review.publish_problems(problems, sha)

        assert gh.pull_requests.comments.create.called
        eq_(2, gh.pull_requests.comments.create.call_count)
        calls = gh.pull_requests.comments.create.call_args_list

        expected = call(
            3, {
                'commit_id': sha,
                'path': errors[0][0],
                'position': errors[0][1],
                'body': errors[0][2]
            })
        eq_(calls[0], expected)

        expected = call(
            3, {
                'commit_id': sha,
                'path': errors[1][0],
                'position': errors[1][1],
                'body': errors[1][2]
            })
        eq_(calls[1], expected)

    @patch('lintreview.review.time')
    def test_publish_with__wait_time(self, time):
        gh = Mock()
        problems = Problems()
        review = Review(gh, 3)

        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something bad'),
        )
        problems.add_many(errors)
        sha = 'abc123'

        review.publish_problems(problems, sha, 1)
        assert time.sleep.called
        eq_(2, time.sleep.call_count)
        calls = time.sleep.call_args_list

        expected = call(1)
        eq_(calls[0], expected)
        eq_(calls[1], expected)

    def test_publish_ok_comment(self):
        gh = Mock()
        problems = Problems()
        review = Review(gh, 3)

        sha = 'abc123'
        review.publish(problems, sha)

        assert not (gh.pull_requests.comments.create.called)
        assert gh.issues.comments.create.called

        calls = gh.issues.comments.create.call_args_list

        expected = call(3, ':+1: No lint errors found.')
        eq_(calls[0], expected)
Пример #21
0
class TestProblems(TestCase):

    two_files_json = load_fixture('two_file_pull_request.json')

    # Block offset so lines don't match offsets
    block_offset = load_fixture('pull_request_line_offset.json')

    def setUp(self):
        self.problems = Problems()

    def test_add(self):
        self.problems.add('file.py', 10, 'Not good')
        eq_(1, len(self.problems))

        self.problems.add('file.py', 11, 'Not good')
        eq_(2, len(self.problems))
        eq_(2, len(self.problems.all()))
        eq_(2, len(self.problems.all('file.py')))
        eq_(0, len(self.problems.all('not there')))

    def test_add__duplicate_is_ignored(self):
        self.problems.add('file.py', 10, 'Not good')
        eq_(1, len(self.problems))

        self.problems.add('file.py', 10, 'Not good')
        eq_(1, len(self.problems))

    def test_add__with_base_path(self):
        problems = Problems('/some/path/')
        problems.add('/some/path/file.py', 10, 'Not good')
        eq_([], problems.all('/some/path/file.py'))
        eq_(1, len(problems.all('file.py')))
        eq_(1, len(problems))

    def test_add__with_base_path_no_trailing_slash(self):
        problems = Problems('/some/path')
        problems.add('/some/path/file.py', 10, 'Not good')
        eq_([], problems.all('/some/path/file.py'))
        eq_(1, len(problems.all('file.py')))
        eq_(1, len(problems))

    def test_add__with_diff_containing_block_offset(self):
        res = map(lambda f: PullFile(f),
                  json.loads(self.block_offset))
        changes = DiffCollection(res)

        problems = Problems(changes=changes)
        line_num = 32
        problems.add('somefile.py', line_num, 'Not good')
        eq_(1, len(problems))

        result = problems.all('somefile.py')
        eq_(changes.line_position('somefile.py', line_num), result[0].position,
            'Offset should be transformed to match value in changes')

    def test_add_many(self):
        errors = [
            ('some/file.py', 10, 'Thing is wrong'),
            ('some/file.py', 12, 'Not good'),
        ]
        self.problems.add_many(errors)
        result = self.problems.all('some/file.py')
        eq_(2, len(result))
        expected = [
            Comment(errors[0][0], errors[0][1], errors[0][1], errors[0][2]),
            Comment(errors[1][0], errors[1][1], errors[1][1], errors[1][2]),
        ]
        eq_(expected, result)

    def test_limit_to_changes__remove_problems(self):
        res = map(lambda f: PullFile(f),
                  json.loads(self.two_files_json))
        changes = DiffCollection(res)

        # Setup some fake problems.
        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            (None, None, 'This is a general comment'),
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something else bad'),
            (filename_1, 130, 'Filtered out, as line is not changed'),
        )
        self.problems.add_many(errors)
        filename_2 = 'Test/test_files/View/Parse/single.ctp'
        errors = (
            (filename_2, 2, 'Filtered out'),
            (filename_2, 3, 'Something bad'),
            (filename_2, 7, 'Filtered out'),
        )
        self.problems.add_many(errors)
        self.problems.set_changes(changes)
        self.problems.limit_to_changes()

        result = self.problems.all(filename_1)
        eq_(2, len(result))
        expected = [
            (None, None, 'This is a general comment'),
            (filename_1, 117, 'Something bad'),
            (filename_1, 119, 'Something else bad')]
        eq_(result.sort(), expected.sort())

        result = self.problems.all(filename_2)
        eq_(1, len(result))
        expected = [
            Comment(filename_2, 3, 3, 'Something bad')
        ]
        eq_(result, expected)

    def test_has_changes(self):
        problems = Problems(changes=None)
        self.assertFalse(problems.has_changes())

        problems = Problems(changes=[1])
        assert problems.has_changes()
Пример #22
0
class TestProblems(TestCase):

    two_files_json = load_fixture('two_file_pull_request.json')

    # Block offset so lines don't match offsets
    block_offset = load_fixture('pull_request_line_offset.json')

    def setUp(self):
        self.problems = Problems()
        self.session = GitHubSession()

    def test_add(self):
        self.problems.add('file.py', 10, 'Not good')
        self.assertEqual(1, len(self.problems))

        self.problems.add('file.py', 11, 'Not good')
        self.assertEqual(2, len(self.problems))
        self.assertEqual(2, len(self.problems.all()))
        self.assertEqual(2, len(self.problems.all('file.py')))
        self.assertEqual(0, len(self.problems.all('not there')))

    def test_add__duplicate_is_ignored(self):
        self.problems.add('file.py', 10, 'Not good')
        self.assertEqual(1, len(self.problems))

        self.problems.add('file.py', 10, 'Not good')
        self.assertEqual(1, len(self.problems))

    def test_add__same_line_combines(self):
        self.problems.add('file.py', 10, 'Tabs bad')
        self.problems.add('file.py', 10, 'Spaces are good')
        self.assertEqual(1, len(self.problems))

        result = self.problems.all()
        expected = 'Tabs bad\nSpaces are good'
        self.assertEqual(expected, result[0].body)

    def test_add__same_line_ignores_duplicates(self):
        self.problems.add('file.py', 10, 'Tabs bad')
        self.problems.add('file.py', 10, 'Tabs bad')
        self.assertEqual(1, len(self.problems))

        result = self.problems.all()
        expected = 'Tabs bad'
        self.assertEqual(expected, result[0].body)

    def test_add__with_diff_containing_block_offset(self):
        res = [
            PullFile(f, self.session) for f in json.loads(self.block_offset)
        ]
        changes = DiffCollection(res)

        problems = Problems(changes=changes)
        line_num = 32
        problems.add('somefile.py', line_num, 'Not good')
        self.assertEqual(1, len(problems))

        result = problems.all('somefile.py')
        first_result = result[0]
        self.assertIsInstance(first_result, Comment)
        self.assertEqual(
            changes.line_position('somefile.py',
                                  line_num), first_result.position,
            'Offset should be transformed to match value in changes')

    def test_add_zero(self):
        self.problems.add('file.py', 0, 'Not good')
        result = self.problems.all('file.py')
        assert len(result) == 1, problems
        assert result[0].line == Comment.FIRST_LINE_IN_DIFF

    def test_add_many(self):
        errors = [
            Comment('some/file.py', 10, 10, 'Thing is wrong'),
            Comment('some/file.py', 12, 12, 'Not good'),
        ]
        self.problems.add_many(errors)
        result = self.problems.all('some/file.py')
        self.assertEqual(2, len(result))
        self.assertEqual(errors, result)

    def test_error_count(self):
        errors = [
            Comment('some/file.py', 10, 10, 'Thing is wrong'),
            Comment('some/file.py', 12, 12, 'Not good'),
        ]
        self.problems.add_many(errors)
        assert 2 == len(self.problems)
        assert 2 == self.problems.error_count()

    def test_error_count_exclude_info(self):
        errors = [
            Comment('some/file.py', 10, 10, 'Thing is wrong'),
            InfoComment('some content'),
        ]
        self.problems.add_many(errors)
        assert 1 == self.problems.error_count()
        assert 2 == len(self.problems)

    def test_limit_to_changes__remove_problems(self):
        res = [
            PullFile(f, self.session) for f in json.loads(self.two_files_json)
        ]
        changes = DiffCollection(res)

        # Setup some fake problems.
        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            Comment(None, None, None, 'This is a general comment'),
            Comment(filename_1, 117, 117, 'Something bad'),
            Comment(filename_1, 119, 119, 'Something else bad'),
            Comment(filename_1, 130, 130, 'Filtered out, line is not changed'),
        )
        self.problems.add_many(errors)
        filename_2 = 'Test/test_files/View/Parse/single.ctp'
        errors = (
            Comment(filename_2, 2, 2, 'Filtered out'),
            Comment(filename_2, 3, 3, 'Something bad'),
            Comment(filename_2, 7, 7, 'Filtered out'),
        )
        self.problems.add_many(errors)
        self.problems.set_changes(changes)
        self.problems.limit_to_changes()

        result = self.problems.all(filename_1)
        self.assertEqual(2, len(result))
        expected = [
            Comment(filename_1, 117, 117, 'Something bad'),
            Comment(filename_1, 119, 119, 'Something else bad')
        ]
        self.assertEqual(len(result), len(expected))
        self.assertEqual(result, expected)

        result = self.problems.all(filename_2)
        self.assertEqual(1, len(result))
        expected = [Comment(filename_2, 3, 3, 'Something bad')]
        self.assertEqual(result, expected)

    def test_limit_to_changes__first_line_in_diff(self):
        res = [
            PullFile(f, self.session) for f in json.loads(self.two_files_json)
        ]
        changes = DiffCollection(res)

        # Add problems
        filename = 'Test/test_files/View/Parse/single.ctp'
        errors = (
            Comment(filename, 5, 5, 'Something bad'),
            Comment(filename, Comment.FIRST_LINE_IN_DIFF, 0, 'First line!'),
            Comment(filename, 7, 7, 'Filtered out'),
        )
        self.problems.add_many(errors)
        self.problems.set_changes(changes)
        self.problems.limit_to_changes()

        result = self.problems.all(filename)
        self.assertEqual(2, len(result))
        expected = [
            Comment(filename, 5, 5, 'Something bad'),
            Comment(filename, 3, 3, 'First line!'),
        ]
        self.assertEqual(result, expected)

    def test_has_changes(self):
        problems = Problems(changes=None)
        self.assertFalse(problems.has_changes())

        problems = Problems(changes=[1])
        assert problems.has_changes()
Пример #23
0
class TestProblems(TestCase):

    two_files_json = load_fixture('two_file_pull_request.json')

    # Block offset so lines don't match offsets
    block_offset = load_fixture('pull_request_line_offset.json')

    def setUp(self):
        self.problems = Problems()

    def test_add(self):
        self.problems.add('file.py', 10, 'Not good')
        eq_(1, len(self.problems))

        self.problems.add('file.py', 11, 'Not good')
        eq_(2, len(self.problems))
        eq_(2, len(self.problems.all()))
        eq_(2, len(self.problems.all('file.py')))
        eq_(0, len(self.problems.all('not there')))

    def test_add__duplicate_is_ignored(self):
        self.problems.add('file.py', 10, 'Not good')
        eq_(1, len(self.problems))

        self.problems.add('file.py', 10, 'Not good')
        eq_(1, len(self.problems))

    def test_add__same_line_combines(self):
        self.problems.add('file.py', 10, 'Tabs bad')
        self.problems.add('file.py', 10, 'Spaces are good')
        eq_(1, len(self.problems))

        result = self.problems.all()
        expected = 'Tabs bad\nSpaces are good'
        eq_(expected, result[0].body)

    def test_add__same_line_ignores_duplicates(self):
        self.problems.add('file.py', 10, 'Tabs bad')
        self.problems.add('file.py', 10, 'Tabs bad')
        eq_(1, len(self.problems))

        result = self.problems.all()
        expected = 'Tabs bad'
        eq_(expected, result[0].body)

    def test_add__with_diff_containing_block_offset(self):
        res = [PullFile(f) for f in json.loads(self.block_offset)]
        changes = DiffCollection(res)

        problems = Problems(changes=changes)
        line_num = 32
        problems.add('somefile.py', line_num, 'Not good')
        eq_(1, len(problems))

        result = problems.all('somefile.py')
        eq_(changes.line_position('somefile.py', line_num), result[0].position,
            'Offset should be transformed to match value in changes')

    def test_add_many(self):
        errors = [
            Comment('some/file.py', 10, 10, 'Thing is wrong'),
            Comment('some/file.py', 12, 12, 'Not good'),
        ]
        self.problems.add_many(errors)
        result = self.problems.all('some/file.py')
        eq_(2, len(result))
        eq_(errors, result)

    def test_limit_to_changes__remove_problems(self):
        res = [PullFile(f) for f in json.loads(self.two_files_json)]
        changes = DiffCollection(res)

        # Setup some fake problems.
        filename_1 = 'Console/Command/Task/AssetBuildTask.php'
        errors = (
            Comment(None, None, None, 'This is a general comment'),
            Comment(filename_1, 117, 117, 'Something bad'),
            Comment(filename_1, 119, 119, 'Something else bad'),
            Comment(filename_1, 130, 130, 'Filtered out, line is not changed'),
        )
        self.problems.add_many(errors)
        filename_2 = 'Test/test_files/View/Parse/single.ctp'
        errors = (
            Comment(filename_2, 2, 2, 'Filtered out'),
            Comment(filename_2, 3, 3, 'Something bad'),
            Comment(filename_2, 7, 7, 'Filtered out'),
        )
        self.problems.add_many(errors)
        self.problems.set_changes(changes)
        self.problems.limit_to_changes()

        result = self.problems.all(filename_1)
        eq_(2, len(result))
        expected = [
            Comment(filename_1, 117, 117, 'Something bad'),
            Comment(filename_1, 119, 119, 'Something else bad')
        ]
        eq_(len(result), len(expected))
        eq_(result, expected)

        result = self.problems.all(filename_2)
        eq_(1, len(result))
        expected = [Comment(filename_2, 3, 3, 'Something bad')]
        eq_(result, expected)

    def test_has_changes(self):
        problems = Problems(changes=None)
        self.assertFalse(problems.has_changes())

        problems = Problems(changes=[1])
        assert problems.has_changes()
Пример #24
0
class Processor(object):

    _repository = None
    _pull_request = None
    _target_path = None
    _changes = None
    _review = None
    _config = None
    problems = None

    def __init__(self, repository, pull_request, target_path, config):
        self._config = config
        self._repository = repository
        self._pull_request = pull_request
        self._target_path = target_path
        self.problems = Problems()
        self._review = Review(repository, pull_request, config)

    def load_changes(self):
        log.info('Loading pull request patches from github.')
        files = self._pull_request.files()
        self._changes = DiffCollection(files)
        self.problems.set_changes(self._changes)

    def run_tools(self):
        if self._changes is None:
            raise RuntimeError('No loaded changes, cannot run tools. '
                               'Try calling load_changes first.')
        config = self._config

        files_to_check = self._changes.get_files(
            ignore_patterns=config.ignore_patterns()
        )
        commits_to_check = self._pull_request.commits()

        tool_list = tools.factory(
            config,
            self.problems,
            self._target_path)

        if config.fixers_enabled():
            self.apply_fixers(tool_list, files_to_check)

        tools.run(tool_list, files_to_check, commits_to_check)

    def apply_fixers(self, tool_list, files_to_check):
        try:
            fixer_context = fixers.create_context(
                self._config,
                self._target_path,
                self._repository,
                self._pull_request,
            )
            fixer_diff = fixers.run_fixers(
                tool_list,
                self._target_path,
                files_to_check)
            fixers.apply_fixer_diff(
                self._changes,
                fixer_diff,
                fixer_context)
        except (ConfigurationError, WorkflowError) as e:
            log.info('Fixer application failed. Got %s', e)
            message = u'Unable to apply fixers. {}'.format(e)
            self.problems.add(InfoComment(message))
        except Exception as e:
            log.info('Fixer application failed, '
                     'rolling back working tree. Got %s', e)
            fixers.rollback_changes(self._target_path)

    def publish(self, check_run_id=None):
        self.problems.limit_to_changes()
        if check_run_id:
            self._review.publish_checkrun(
                self.problems,
                check_run_id)
        else:
            self._review.publish_review(
                self.problems,
                self._pull_request.head)