mozreviewpulse: absorb needed mozreviewbots libraries and delete the rest (Bug 1287537). r?mars draft
authorSteven MacLeod <smacleod@mozilla.com>
Tue, 29 Nov 2016 17:56:39 -0500
changeset 10159 b99c1cab2f141d00ad296496a87cf7d08133c352
parent 10158 7a4ce6d2bc1d3fe8b6e088e89efd2504805a9ff6
child 10160 4294866cea9df8c55b7a531b5e979648a65efc00
push id1453
push usersmacleod@mozilla.com
push dateSat, 14 Jan 2017 01:14:15 +0000
reviewersmars
bugs1287537
mozreviewpulse: absorb needed mozreviewbots libraries and delete the rest (Bug 1287537). r?mars mozreviewbots is no longer going to be integrated into MozReview. Instead automation will be triggered elsewhere and results will be consumed from pulse. mozreviewbots has some useful code for posting reviews to Review Board in a single request which we'll pull into mozreviewpulse (It could be argued this should be its own stand-alone library, but we'll keep it in mozreviewpulse now and possibly pull it out later). MozReview-Commit-ID: 1qxr8YNzP4h
mozreviewpulse/mozreviewpulse/batchreview.py
mozreviewpulse/requirements.txt
mozreviewpulse/setup.py
pylib/mozreviewbots/eslintbot/__init__.py
pylib/mozreviewbots/eslintbot/__main__.py
pylib/mozreviewbots/mozreviewbotlib/__init__.py
pylib/mozreviewbots/mozreviewbotlib/batchreview.py
pylib/mozreviewbots/pylintbot/__init__.py
pylib/mozreviewbots/pylintbot/__main__.py
pylib/mozreviewbots/sample.ini
pylib/mozreviewbots/setup.py
pylib/mozreviewbots/snarkbot/__init__.py
pylib/mozreviewbots/snarkbot/__main__.py
pylib/mozreviewbots/tests/helpers.sh
pylib/mozreviewbots/tests/hghave
pylib/mozreviewbots/tests/test-eslintbot-noop.t
pylib/mozreviewbots/tests/test-eslintbot.t
pylib/mozreviewbots/tests/test-pylintbot-diff-context.t
pylib/mozreviewbots/tests/test-pylintbot-line-adjustment.t
pylib/mozreviewbots/tests/test-pylintbot-noop.t
pylib/mozreviewbots/tests/test-pylintbot.t
pylib/mozreviewbots/tests/test-snarkbot.t
rename from pylib/mozreviewbots/mozreviewbotlib/batchreview.py
rename to mozreviewpulse/mozreviewpulse/batchreview.py
--- a/mozreviewpulse/requirements.txt
+++ b/mozreviewpulse/requirements.txt
@@ -1,5 +1,6 @@
 docker-py
 kombu
 mock
 pytest
+RBTools
 taskcluster
--- a/mozreviewpulse/setup.py
+++ b/mozreviewpulse/setup.py
@@ -14,10 +14,14 @@ setup(
     license='MPL 2.0',
     classifiers=[
         'Development Status :: 4 - Beta',
         'Intended Audience :: Developers',
         'Topic :: Software Development :: Build Tools',
         'Programming Language :: Python :: 2.7',
     ],
     packages=find_packages(),
-    install_requires=['taskcluster'],
+    install_requires=[
+        'kombu',
+        'RBtools',
+        'taskcluster',
+    ],
 )
deleted file mode 100644
deleted file mode 100644
--- a/pylib/mozreviewbots/eslintbot/__main__.py
+++ /dev/null
@@ -1,157 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-import argparse
-import json
-import logging
-import os
-import random
-import subprocess
-import sys
-import tempfile
-
-
-from mozreviewbotlib import MozReviewBot
-
-
-EXTENSIONS = ['.js', '.jsm', '.jsx']
-
-
-# Any repositories hoping to use ESLintBot should have a ESLINT_CONFIG
-# file in the root repository folder.
-ESLINT_CONFIG = '.eslintrc'
-
-
-class ESLintBot(MozReviewBot):
-    """This bot runs ESLint against JavaScript files under review"""
-
-    def process_commit(self, review, landing_repo_url, repo_url, commit):
-        revision = commit['rev']
-
-        self.logger.info('reviewing revision: %s (review request: %d)' %
-                         (revision[:12], commit['review_request_id']))
-
-        repo_path = self.ensure_hg_repo_exists(landing_repo_url, repo_url,
-                                               revision)
-
-        if not os.path.isfile(os.path.join(repo_path, ESLINT_CONFIG)):
-            self.logger.info('Not reviewing revision: %s no %s file in '
-                             'repository root folder'
-                             % (revision, ESLINT_CONFIG))
-            return
-
-        adds, dels, mods, copies, diff = self.hg_commit_changes(repo_path,
-                                                                revision,
-                                                                diff_context=0)
-
-        rel_adds = set(f for f in adds if os.path.splitext(f)[1] in EXTENSIONS)
-        rel_mods = set(f for f in mods if os.path.splitext(f)[1] in EXTENSIONS)
-        relevant = rel_adds | rel_mods
-
-        if not relevant:
-            self.logger.info('not reviewing revision: %s no relevant '
-                             'Javascript changes in commit' % revision)
-            return
-
-        oldcwd = os.getcwd()
-        try:
-            os.chdir(repo_path)
-
-            # Unfortunately, running ./mach eslint will result in a bunch of
-            # mach logging going into stdout, which we don't care about. We
-            # work around this by outputting the ESLint output to a temporary
-            # file which we'll read in from. Note that this will only work
-            # on UNIX-like systems.
-            output_file = tempfile.NamedTemporaryFile().name
-
-            args = [
-                './mach',
-                'eslint',
-                '--format=json',
-                ('--output-file=%s' % output_file)
-            ] + list(relevant)
-
-            subprocess.check_output(args)
-            with open(output_file, 'r') as f:
-                results = json.load(f)
-                path = os.path.abspath(f.name)
-
-            os.remove(path)
-        finally:
-            os.chdir(oldcwd)
-
-        error_count = 0
-        # The join assures we get a trailing slash
-        base_path = os.path.join(os.path.dirname(repo_path), revision, '')
-
-        for result in results:
-            if not result['errorCount'] and not result['warningCount']:
-                continue
-
-            error_count += result['errorCount'] + result['warningCount']
-            # Do some awful hacks to get a repo-relative file path:
-            file_path = os.path.abspath(result['filePath'])
-            file_path = file_path[len(base_path):]
-
-            for message in result['messages']:
-                if message['severity'] == 1:
-                    severity = "Warning"
-                else:
-                    severity = "Error"
-
-                comment = '%s - %s' % (severity, message['message'])
-
-                if 'column' in message:
-                    comment += " (column %s)" % message['column']
-
-                line = 0
-                if 'line' in message:
-                    line = message['line']
-
-                review.comment(file_path, line, 1, comment)
-
-        commentlines = []
-
-        if error_count:
-            commentlines.extend([
-                'I analyzed your JS changes and found %d errors.' % (
-                    error_count),
-            ])
-        else:
-            commentlines.extend([
-                'Congratulations, there were no JS static analysis '
-                'issues with this patch!',
-            ])
-
-        commentlines.extend([
-            '',
-            'The following files were examined:',
-            '',
-        ])
-        commentlines.extend('  %s' % f for f in sorted(relevant))
-
-        review.publish(body_top='\n'.join(commentlines),
-                       ship_it=error_count == 0)
-
-        self.strip_nonpublic_changesets(repo_path)
-
-
-if __name__ == '__main__':
-
-    parser = argparse.ArgumentParser()
-    parser.add_argument('--config-path',
-                        help='Path to configuration file for bot options')
-    parser.add_argument('--forever', action='store_true',
-                        help='Run the bot in an endless loop')
-    parser.add_argument('--log-level', default='INFO',
-                        help='Log level at which to log events')
-    args = parser.parse_args()
-
-    logging.basicConfig(level=getattr(logging, args.log_level.upper()),
-                        stream=sys.stdout)
-
-    bot = ESLintBot(config_path=args.config_path)
-    if args.forever:
-        bot.listen_forever()
-    else:
-        bot.handle_available_messages()
deleted file mode 100644
--- a/pylib/mozreviewbots/mozreviewbotlib/__init__.py
+++ /dev/null
@@ -1,270 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-import hashlib
-import logging
-import os
-import socket
-import subprocess
-import uuid
-
-from batchreview import BatchReview
-from ConfigParser import ConfigParser, NoSectionError, NoOptionError
-
-from rbtools.api.client import RBClient
-
-from kombu import (
-    Connection,
-    Exchange,
-    Queue,
-)
-
-
-class MozReviewBot(object):
-
-    def __init__(self, config_path=None, reviewboard_url=None,
-                 reviewboard_user=None, reviewboard_password=None,
-                 pulse_host=None, pulse_port=None, pulse_userid=None,
-                 pulse_password=None, exchange=None, queue=None,
-                 routing_key=None, pulse_timeout=None, pulse_ssl=False,
-                 repo_root=None, logger=None):
-
-        if logger is None:
-            self.logger = logging.getLogger('mozreviewbot')
-        else:
-            self.logger = logger
-
-        # We use options passed into __init__ preferentially. If any of these
-        # are not specified, we next check the configuration file, if any.
-        # Finally, we use environment variables.
-        if config_path and not os.path.isfile(config_path):
-            # ConfigParser doesn't seem to throw if it is unable to find the
-            # config file so we'll explicitly check that it exists.
-            self.logger.error('could not locate config file: %s' % (
-                config_path))
-            config_path = None
-        if config_path:
-            try:
-                config = ConfigParser()
-                config.read(config_path)
-                reviewboard_url = (reviewboard_url
-                                   or config.get('reviewboard', 'url'))
-                reviewboard_user = (reviewboard_user
-                                    or config.get('reviewboard', 'user'))
-                reviewboard_password = (reviewboard_password
-                                        or config.get('reviewboard',
-                                                      'password'))
-                pulse_host = pulse_host or config.get('pulse', 'host')
-                pulse_port = pulse_port or config.get('pulse', 'port')
-                pulse_userid = pulse_userid or config.get('pulse', 'userid')
-                pulse_password = pulse_password or config.get('pulse',
-                                                              'password')
-                exchange = exchange or config.get('pulse', 'exchange')
-                queue = queue or config.get('pulse', 'queue')
-                routing_key = routing_key or config.get('pulse',
-                                                        'routing_key')
-                pulse_timeout = pulse_timeout or config.get('pulse',
-                                                            'timeout')
-                if pulse_ssl is None:
-                    pulse_ssl = config.get('pulse', 'ssl')
-            except NoSectionError as e:
-                self.logger.error('configuration file missing section: %s' %
-                                  e.section)
-            try:
-                repo_root = repo_root or config.get('hg', 'repo_root')
-            except (NoOptionError, NoSectionError):
-                # Subclasses do not need to define repo root if they do not
-                # plan on using the hg functionality.
-                pass
-
-            # keep config around in case any subclasses would like to extract
-            # options from it.
-            self.config = config
-        else:
-            self.config = None
-
-        reviewboard_url = reviewboard_url or os.environ.get('REVIEWBOARD_URL')
-        pulse_host = pulse_host or os.environ.get('PULSE_HOST')
-        pulse_port = pulse_port or os.environ.get('PULSE_PORT')
-
-        self.rbclient = RBClient(reviewboard_url, username=reviewboard_user,
-                                 password=reviewboard_password)
-        self.api_root = self.rbclient.get_root()
-
-        self.conn = Connection(hostname=pulse_host, port=pulse_port,
-                               userid=pulse_userid, password=pulse_password,
-                               ssl=pulse_ssl)
-
-        self.exchange = Exchange(exchange, type='topic', durable=True)
-        self.queue = Queue(name=queue, exchange=self.exchange, durable=True,
-                           routing_key=routing_key, exclusive=False,
-                           auto_delete=False)
-
-        self.pulse_timeout = float(pulse_timeout)
-        self.repo_root = repo_root
-
-        self.hg = None
-        for DIR in os.environ['PATH'].split(os.pathsep):
-            p = os.path.join(DIR, 'hg')
-            if os.path.exists(p):
-                self.hg = p
-
-    def _get_available_messages(self):
-        messages = []
-
-        def onmessage(body, message):
-            messages.append((body, message))
-
-        consumer = self.conn.Consumer([self.queue], callbacks=[onmessage],
-                                      auto_declare=True)
-        with consumer:
-            try:
-                self.conn.drain_events(timeout=self.pulse_timeout)
-            except socket.timeout:
-                pass
-
-        return messages
-
-    def _run_hg(self, hg_args):
-        # TODO: Use hgtool.
-
-        args = [self.hg] + hg_args
-
-        env = dict(os.environ)
-        env['HGENCODING'] = 'utf-8'
-
-        null = open(os.devnull, 'w')
-
-        # Execute at / to prevent Mercurial's path traversal logic from
-        # kicking in and picking up unwanted config files.
-        return subprocess.check_output(args, stdin=null, stderr=null,
-                                       env=env, cwd='/')
-
-    def ensure_hg_repo_exists(self, landing_repo_url, repo_url, pull_rev=None):
-        # TODO: Use the root changeset in each repository as an identifier.
-        #       This will enable "forks" to share the same local clone.
-        #       The "share" extension now has support for this.
-        #       Read hg help -e share for details about "pooled storage."
-        #       We should probably deploy that.
-        url = landing_repo_url or repo_url
-
-        sha1 = hashlib.sha1(url).hexdigest()
-        repo_path = os.path.join(self.repo_root, sha1)
-
-        if not os.path.exists(repo_path):
-            args = ['clone', url, repo_path]
-            self.logger.debug('cloning %s' % url)
-            self._run_hg(args)
-            self.logger.debug('finished cloning %s' % url)
-
-        args = ['-R', repo_path, 'pull', repo_url]
-
-        if pull_rev:
-            args.extend(['-r', pull_rev])
-
-        self.logger.debug('pulling %s' % repo_url)
-        self._run_hg(args)
-        self.logger.debug('finished pulling %s' % repo_url)
-
-        return repo_path
-
-    def hg_commit_changes(self, repo_path, node, diff_context=None):
-        """Obtain information about what changed in a Mercurial commit.
-
-        The return value is a tuple of:
-
-          (set(adds), set(dels), set(mods), None, diff)
-
-        The first 4 items list what files changed in the changeset. The last
-        item is a unified diff of the changeset.
-
-        File copies are currently not returned. ``None`` is being used as a
-        placeholder until support is needed.
-        """
-        part_delim = str(uuid.uuid4())
-        item_delim = str(uuid.uuid4())
-
-        parts = [
-            '{join(file_adds, "%s")}' % item_delim,
-            '{join(file_dels, "%s")}' % item_delim,
-            '{join(file_mods, "%s")}' % item_delim,
-            '{join(file_copies, "%s")}' % item_delim,
-        ]
-
-        template = part_delim.join(parts)
-
-        self._run_hg(['-R', repo_path, 'up', '-C', node])
-
-        res = self._run_hg(['-R', repo_path, 'log', '-r', node,
-                            '-T', template])
-
-        diff_args = ['-R', repo_path, 'diff', '-c', node]
-        if diff_context is not None:
-            diff_args.extend(['-U', str(diff_context)])
-        diff = self._run_hg(diff_args)
-
-        adds, dels, mods, copies = res.split(part_delim)
-        adds = set(f for f in adds.split(item_delim) if f)
-        dels = set(f for f in dels.split(item_delim) if f)
-        mods = set(f for f in mods.split(item_delim) if f)
-        # TODO parse the copies.
-
-        return adds, dels, mods, None, diff
-
-    def strip_nonpublic_changesets(self, repo_path):
-        """Strip non-public changesets from a repository.
-
-        Pulling changesets over and over results in many heads in a repository.
-        This makes Mercurial slow. So, we prune non-public changesets/heads
-        to keep repositories fast.
-        """
-
-        self._run_hg(['-R', repo_path, '--config', 'extensions.strip=',
-                      'strip', '--no-backup', '-r', 'not public()'])
-
-    def get_commit_files(self, commit):
-        """Fetches a list of files that were changed by this commit."""
-
-        rrid = commit['review_request_id']
-        diff_revision = commit['diffset_revision']
-
-        start = 0
-        files = []
-        while True:
-            result = self.api_root.get_files(review_request_id=rrid,
-                                             diff_revision=diff_revision,
-                                             start=start)
-            files.extend(result)
-            start += result.num_items
-            if result.num_items == 0 or start >= result.total_results:
-                break
-        return files
-
-    def handle_available_messages(self):
-        for body, message in self._get_available_messages():
-            payload = body['payload']
-            repo_url = payload['repository_url']
-            landing_repo_url = payload['landing_repository_url']
-            commits = payload['commits']
-            # TODO: should we allow process commits to signal that we should
-            #       skip acknowledging the message?
-            try:
-                for commit in commits:
-                    rrid = commit['review_request_id']
-                    diff_revision = commit['diffset_revision']
-
-                    review = BatchReview(self.api_root, rrid, diff_revision)
-                    self.process_commit(review, landing_repo_url, repo_url,
-                                        commit)
-            finally:
-                # This prevents the queue from growing indefinitely but
-                # prevents us from fixing whatever caused the exception
-                # and restarting the bot to handle the message.
-                message.ack()
-
-    def listen_forever(self):
-        while True:
-            self.handle_available_messages()
-
-    def process_commit(self, review, repo_url, commits):
-        pass
deleted file mode 100644
deleted file mode 100644
--- a/pylib/mozreviewbots/pylintbot/__main__.py
+++ /dev/null
@@ -1,200 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-import argparse
-import logging
-import os
-import random
-import sys
-
-from flake8.engine import get_style_guide
-from mozreviewbotlib import MozReviewBot
-from pep8 import (
-    DiffReport,
-    parse_udiff
-)
-
-
-# Maps a flake8 error code to a tuple of (start_line_offset, num_lines)
-LINE_ADJUSTMENTS = {
-    # continuation line under-indented for hanging indent
-    'E121': (-1, 2),
-    # continuation line missing indentation or outdented
-    'E122': (-1, 2),
-    # continuation line over-indented for hanging indent
-    'E126': (-1, 2),
-    # continuation line over-indented for visual indent
-    'E127': (-1, 2),
-    # continuation line under-indented for visual indent
-    'E128': (-1, 2),
-    # continuation line unaligned for hanging indend
-    'E131': (-1, 2),
-    # expected 1 blank line, found 0
-    'E301': (-1, 2),
-    # expected 2 blank lines, found 1
-    'E302': (-2, 3),
-}
-
-NO_ERRORS_QUIPS = [
-    'And now for something completely different.',
-]
-
-ERRORS_QUIPS = [
-    'Always look on the bright side of life.',
-]
-
-
-def _cmp_errors(a, b):
-    """Comparison function for 2 pep8 error tuples.
-
-    We sort first by line number then by error code.
-    """
-    aline, aoffset, acode, atext, adoc = a
-    bline, boffset, bcode, btext, bdoc = b
-
-    if aline < bline:
-        return -1
-    elif aline > bline:
-        return 1
-
-    if aoffset < boffset:
-        return -1
-    elif aoffset > boffset:
-        return 1
-
-    if acode < bcode:
-        return -1
-    elif acode > bcode:
-        return 1
-
-    return 0
-
-
-class CapturingDiffReport(DiffReport):
-    """A custom pep8 report that buffers results to a data structure.
-
-    Existing report classes print output. We are headless. We store the results
-    for later processing.
-    """
-    def __init__(self, options):
-        super(CapturingDiffReport, self).__init__(options)
-
-        self.file_results = {}
-
-    def get_file_results(self):
-        self.file_results[self.filename] = self._deferred_print
-        return self.file_errors
-
-
-class PylintBot(MozReviewBot):
-    """This bot runs flake8 against python files under review"""
-
-    def process_commit(self, review, landing_repo_url, repo_url, commit):
-        revision = commit['rev']
-
-        self.logger.info('reviewing revision: %s (review request: %d)' %
-                         (revision[:12], commit['review_request_id']))
-
-        repo_path = self.ensure_hg_repo_exists(landing_repo_url, repo_url,
-                                               revision)
-
-        self.hg_commit_changes(repo_path, revision, diff_context=0)
-
-        adds, dels, mods, copies, diff = self.hg_commit_changes(repo_path,
-                                                                revision,
-                                                                diff_context=0)
-
-        rel_adds = set(f for f in adds if f.endswith('.py'))
-        rel_mods = set(f for f in mods if f.endswith('.py'))
-        relevant = rel_adds | rel_mods
-
-        if not relevant:
-            self.logger.info('not reviewing revision: %s no relevant '
-                             'python changes in commit' % revision)
-            return
-
-        # flake8's multiprocessing default doesn't work synchronously for
-        # some reason. Probably because our saved state isn't being
-        # transferred across process boundaries. Specify jobs=0 to get
-        # results.
-        style = get_style_guide(parse_argv=False, jobs=0)
-        style.options.selected_lines = {}
-        for k, v in parse_udiff(diff).items():
-            if k.startswith('./'):
-                k = k[2:]
-                style.options.selected_lines[k] = v
-        style.options.report = CapturingDiffReport(style.options)
-
-        oldcwd = os.getcwd()
-        try:
-            os.chdir(repo_path)
-            results = style.check_files(relevant)
-        finally:
-            os.chdir(oldcwd)
-
-        error_count = 0
-        for filename, errors in sorted(results.file_results.items()):
-            if not errors:
-                continue
-
-            errors = sorted(errors, cmp=_cmp_errors)
-
-            for line, offset, code, text, doc in errors:
-                error_count += 1
-                num_lines = 1
-                comment = '%s: %s' % (code, text)
-
-                if code in LINE_ADJUSTMENTS:
-                    line_offset, num_lines = LINE_ADJUSTMENTS[code]
-                    line += line_offset
-
-                review.comment(filename, line, num_lines, comment)
-
-        commentlines = []
-
-        if error_count:
-            commentlines.extend([
-                random.choice(ERRORS_QUIPS),
-                '',
-                'I analyzed your Python changes and found %d errors.' % (
-                    error_count),
-            ])
-        else:
-            commentlines.extend([
-                random.choice(NO_ERRORS_QUIPS),
-                '',
-                'Congratulations, there were no Python static analysis '
-                'issues with this patch!',
-            ])
-
-        commentlines.extend([
-            '',
-            'The following files were examined:',
-            '',
-        ])
-        commentlines.extend('  %s' % f for f in sorted(relevant))
-
-        review.publish(body_top='\n'.join(commentlines),
-                       ship_it=error_count == 0)
-
-        self.strip_nonpublic_changesets(repo_path)
-
-if __name__ == '__main__':
-
-    parser = argparse.ArgumentParser()
-    parser.add_argument('--config-path',
-                        help='Path to configuration file for bot options')
-    parser.add_argument('--forever', action='store_true',
-                        help='Run the bot in an endless loop')
-    parser.add_argument('--log-level', default='INFO',
-                        help='Log level at which to log events')
-    args = parser.parse_args()
-
-    logging.basicConfig(level=getattr(logging, args.log_level.upper()),
-                        stream=sys.stdout)
-
-    bot = PylintBot(config_path=args.config_path)
-    if args.forever:
-        bot.listen_forever()
-    else:
-        bot.handle_available_messages()
deleted file mode 100644
--- a/pylib/mozreviewbots/sample.ini
+++ /dev/null
@@ -1,16 +0,0 @@
-[pulse]
-host = ${PULSE_HOST}
-port = ${PULSE_PORT}
-userid = guest
-password = guest
-exchange = exchange/mozreview/
-queue = all
-ssl = False
-
-[reviewboard]
-url = ${REVIEWBOARD_URL}
-user = default@example.com
-password = password
-
-[hg]
-repo_root = /tmp
deleted file mode 100644
--- a/pylib/mozreviewbots/setup.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from setuptools import setup, find_packages
-
-setup(
-    name='mozreviewbots',
-    version='0.1',
-    description='MozReview static analysis bots',
-    url='https://mozilla-version-control-tools.readthedocs.io/',
-    author='Mozilla',
-    author_email='dev-version-control@lists.mozilla.org',
-    license='MPL 2.0',
-    classifiers=[
-        'Development Status :: 4 - Beta',
-        'Intended Audience :: Developers',
-        'Topic :: Software Development :: Build Tools',
-        'Programming Language :: Python :: 2.7',
-    ],
-    packages=find_packages(),
-    install_requires=['RBTools', 'flake8', 'kombu', 'pep8'],
-)
deleted file mode 100644
deleted file mode 100644
--- a/pylib/mozreviewbots/snarkbot/__main__.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-from mozreviewbotlib import MozReviewBot
-
-
-class SnarkBot(MozReviewBot):
-    """This implements a simple review bot.
-
-       It can be used as a basis for creating more sophisticated reviewers.
-       It is also used to test the MozReviewBot base class.
-    """
-
-    def __init__(self, snark, *args, **kwargs):
-        super(SnarkBot, self).__init__(*args, **kwargs)
-        self.snark = snark
-
-    def process_commit(self, review, landing_repo_url, repo_url, commit):
-        """This is called for each group of commits that is found on Pulse.
-
-           It can also be called directly to facillitate testing. The repo_url
-           is the URL for the underlying review repository. The commit is a
-           dictionary containing commits to review, e.g.
-
-                commit = {'review_request_id': 42,
-                          'diffset_revision': 1,
-                          'commit': 'aaaaaaaaaaaa'}
-        """
-
-        # We fetch the files that were changed in this commit. This list
-        # could be filtered so we only look at certain file types before
-        # we continue.
-        files = self.get_commit_files(commit)
-
-        self.logger.info('reviewing commit: %s for review request: %d '
-                         'diff_revision: %d' % (commit['rev'][:12],
-                                                commit['review_request_id'],
-                                                commit['diffset_revision']))
-        for f in files:
-            self.logger.info('looking at file: %s (%s)' % (f.source_file,
-                                                           f.dest_file))
-
-            changed_lines = review.changed_lines_for_file(f.dest_file)
-
-            # We then fetch the patched file.
-            pf = f.get_patched_file()
-            code = pf.rsp['resource']['data']
-
-            # In this case we just log the text, but this is where the bot
-            # would analyze the patch.
-            self.logger.info(code)
-
-            # We create a comment on one of the changed lines in the file
-            review.comment(f.dest_file, changed_lines.pop(), 1, self.snark,
-                           True)
-
-        review.publish(body_top='This is what I think of your changes:',
-                       ship_it=False)
-
-
-if __name__ == '__main__':
-    import argparse
-    import logging
-    import sys
-
-    parser = argparse.ArgumentParser()
-
-    parser.add_argument('--config-path',
-                        help='Path to configuration file for bot options')
-    parser.add_argument('--log-level', default='INFO',
-                        help='Log level at which to log events')
-    parser.add_argument('--snark', default='seriously?',
-                        help='Snarky comment to use for reviews')
-
-    args = parser.parse_args()
-
-    logging.basicConfig(level=getattr(logging, args.log_level.upper()),
-                        stream=sys.stdout)
-
-    snark = SnarkBot(args.snark, config_path=args.config_path)
-
-    # This will drain all available messages on Pulse and call process_commits
-    # for the commits associated with each message. Use listen_forever() to
-    # process messages in a endless loop.
-    snark.handle_available_messages()
deleted file mode 100644
--- a/pylib/mozreviewbots/tests/helpers.sh
+++ /dev/null
@@ -1,58 +0,0 @@
-pylintconfig() {
-   cat > pylintbot.ini << EOF
-[pulse]
-host = ${PULSE_HOST}
-port = ${PULSE_PORT}
-userid = guest
-password = guest
-exchange = exchange/mozreview/
-queue = all
-ssl = False
-routing_key = #
-timeout = 60.0
-[reviewboard]
-url = ${REVIEWBOARD_URL}
-user = pylintbot@example.com
-password = password
-[hg]
-repo_root = `pwd`/repos
-EOF
-}
-
-pylintsetup() {
-  pylintconfig $1
-
-  mozreview create-user pylintbot@example.com password 'Pylintbot :pylintbot' --bugzilla-group editbugs --uid 2000 --scm-level 3 > /dev/null
-
-  cd client
-  echo foo0 > foo
-  hg commit -A -m 'root commit' > /dev/null
-  hg phase --public -r .
-  hg push --noreview > /dev/null
-}
-
-eslintconfig() {
-   cat > eslintbot.ini << EOF
-[pulse]
-host = ${PULSE_HOST}
-port = ${PULSE_PORT}
-userid = guest
-password = guest
-exchange = exchange/mozreview/
-queue = all
-ssl = False
-routing_key = #
-timeout = 60.0
-[reviewboard]
-url = ${REVIEWBOARD_URL}
-user = eslintbot@example.com
-password = password
-[hg]
-repo_root = `pwd`/repos
-EOF
-}
-
-eslintsetup() {
-  eslintconfig
-  mozreview create-user eslintbot@example.com password 'ESLintBot :eslintbot' --bugzilla-group editbugs --uid 2000 --scm-level 3 > /dev/null
-}
\ No newline at end of file
deleted file mode 100755
--- a/pylib/mozreviewbots/tests/hghave
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env python
-
-import os
-
-HERE = os.path.abspath(os.path.dirname(__file__))
-REPO_ROOT = os.path.join(HERE, '..', '..', '..')
-execfile(os.path.join(REPO_ROOT, 'testing', 'hghave.py'))
deleted file mode 100644
--- a/pylib/mozreviewbots/tests/test-eslintbot-noop.t
+++ /dev/null
@@ -1,87 +0,0 @@
-#require mozreviewdocker
-  $ . $TESTDIR/hgext/reviewboard/tests/helpers.sh
-  $ . $TESTDIR/pylib/mozreviewbots/tests/helpers.sh
-  $ commonenv rb-test-eslintbot
-
-Running setup
-  $ eslintsetup
-
-Enter into the test repo director
-  $ cd client
-
-Create a non-JS file
-  $ echo foo0 > foo
-
-Create mach
-  $ cat >> mach << EOF
-  > #!/usr/bin/env python
-  > import sys
-  > import subprocess
-  > subprocess.call(["eslint"] + sys.argv[1:])
-  > EOF
-
-Create .eslintrc
-  $ touch .eslintrc
-
-Commit the files
-  $ hg commit -A -m 'root commit' > /dev/null
-  $ hg phase --public -r .
-  $ hg push --noreview > /dev/null
-
-Create a review request that doesn't touch any Javascript files
-
-  $ bugzilla create-bug TestProduct TestComponent bug1
-  $ echo irrelevant > foo
-  $ hg commit -m 'Bug 1 - No Javascript changes'
-  $ hg push --config reviewboard.autopublish=false > /dev/null
-  (review requests lack reviewers; visit review url to assign reviewers)
-  $ rbmanage publish 1
-
-  $ python -m eslintbot --config-path ../eslintbot.ini
-  INFO:mozreviewbot:reviewing revision: 27ed10e6e90a (review request: 2)
-  INFO:mozreviewbot:not reviewing revision: 27ed10e6e90a514ad461a5a7147a9134de0ca799 no relevant Javascript changes in commit
-
-  $ rbmanage dumpreview 2
-  id: 2
-  status: pending
-  public: true
-  bugs:
-  - '1'
-  commit: null
-  submitter: default+5
-  summary: Bug 1 - No Javascript changes
-  description:
-  - Bug 1 - No Javascript changes
-  - ''
-  - 'MozReview-Commit-ID: 124Bxg'
-  target_people: []
-  extra_data:
-    calculated_trophies: true
-  commit_extra_data:
-    p2rb: true
-    p2rb.author: test
-    p2rb.commit_id: 27ed10e6e90a514ad461a5a7147a9134de0ca799
-    p2rb.first_public_ancestor: dc42edca6edd9dd5a8346b1a881281263d3a10ad
-    p2rb.identifier: bz://1/mynick
-    p2rb.is_squashed: false
-  diffs:
-  - id: 2
-    revision: 1
-    base_commit_id: dc42edca6edd9dd5a8346b1a881281263d3a10ad
-    name: diff
-    extra: {}
-    patch:
-    - diff --git a/foo b/foo
-    - '--- a/foo'
-    - +++ b/foo
-    - '@@ -1,1 +1,1 @@'
-    - -foo0
-    - +irrelevant
-    - ''
-  approved: false
-  approval_failure: A suitable reviewer has not given a "Ship It!"
-
-Cleanup
-
-  $ mozreview stop
-  stopped 9 containers
deleted file mode 100644
--- a/pylib/mozreviewbots/tests/test-eslintbot.t
+++ /dev/null
@@ -1,131 +0,0 @@
-#require mozreviewdocker eslint
-  $ . $TESTDIR/hgext/reviewboard/tests/helpers.sh
-  $ . $TESTDIR/pylib/mozreviewbots/tests/helpers.sh
-  $ commonenv rb-test-eslintbot
-
-Running setup
-  $ eslintsetup
-
-  $ cd client
-  $ echo foo0 > foo
-  $ hg commit -A -m 'root commit' > /dev/null
-  $ hg phase --public -r .
-  $ hg push --noreview > /dev/null
-
-Create mach
-  $ cat >> mach << EOF
-  > #!/usr/bin/env python
-  > import sys
-  > import subprocess
-  > subprocess.call(["eslint"] + sys.argv[1:])
-  > EOF
-  $ chmod +x mach
-
-Create .eslintrc
-
-  $ touch .eslintrc
-
-Create a review request with some busted Javascript in it
-
-  $ bugzilla create-bug TestProduct TestComponent bug1
-  $ cat >> test.js << EOF
-  > if (foo {
-  >   var bar
-  > }
-  > EOF
-
-  $ hg commit -A -m 'Bug 1 - Some busted Javascript'
-  adding .eslintrc
-  adding mach
-  adding test.js
-  $ hg push --config reviewboard.autopublish=false > /dev/null
-  (review requests lack reviewers; visit review url to assign reviewers)
-  $ rbmanage publish 1
-
-  $ python -m eslintbot --config-path ../eslintbot.ini
-  INFO:mozreviewbot:reviewing revision: 719ed7ed9e3e (review request: 2)
-
-  $ rbmanage dumpreview 2
-  id: 2
-  status: pending
-  public: true
-  bugs:
-  - '1'
-  commit: null
-  submitter: default+5
-  summary: Bug 1 - Some busted Javascript
-  description:
-  - Bug 1 - Some busted Javascript
-  - ''
-  - 'MozReview-Commit-ID: 124Bxg'
-  target_people: []
-  extra_data:
-    calculated_trophies: true
-  commit_extra_data:
-    p2rb: true
-    p2rb.author: test
-    p2rb.commit_id: 719ed7ed9e3e1a340f443981aa91125b68598369
-    p2rb.first_public_ancestor: 7c5bdf0cec4a90edb36300f8f3679857f46db829
-    p2rb.identifier: bz://1/mynick
-    p2rb.is_squashed: false
-  diffs:
-  - id: 2
-    revision: 1
-    base_commit_id: 7c5bdf0cec4a90edb36300f8f3679857f46db829
-    name: diff
-    extra: {}
-    patch:
-    - diff --git a/.eslintrc b/.eslintrc
-    - new file mode 100644
-    - diff --git a/mach b/mach
-    - new file mode 100755
-    - '--- /dev/null'
-    - +++ b/mach
-    - '@@ -0,0 +1,4 @@'
-    - +#!/usr/bin/env python
-    - +import sys
-    - +import subprocess
-    - +subprocess.call(["eslint"] + sys.argv[1:])
-    - diff --git a/test.js b/test.js
-    - new file mode 100644
-    - '--- /dev/null'
-    - +++ b/test.js
-    - '@@ -0,0 +1,3 @@'
-    - +if (foo {
-    - +  var bar
-    - +}
-    - ''
-  approved: false
-  approval_failure: A suitable reviewer has not given a "Ship It!"
-  review_count: 1
-  reviews:
-  - id: 1
-    public: true
-    ship_it: false
-    extra_data:
-      p2rb.review_flag: ' '
-    body_top:
-    - I analyzed your JS changes and found 1 errors.
-    - ''
-    - 'The following files were examined:'
-    - ''
-    - '  test.js'
-    body_top_text_type: plain
-    diff_comments:
-    - id: 1
-      public: true
-      user: eslintbot
-      issue_opened: true
-      issue_status: open
-      first_line: 1
-      num_lines: 1
-      text: 'Error - Parsing error: Unexpected token { (column 10)'
-      text_type: plain
-      diff_id: 6
-      diff_dest_file: test.js
-    diff_count: 1
-
-Cleanup
-
-  $ mozreview stop
-  stopped 9 containers
deleted file mode 100644
--- a/pylib/mozreviewbots/tests/test-pylintbot-diff-context.t
+++ /dev/null
@@ -1,102 +0,0 @@
-#require mozreviewdocker
-  $ . $TESTDIR/hgext/reviewboard/tests/helpers.sh
-  $ . $TESTDIR/pylib/mozreviewbots/tests/helpers.sh
-  $ commonenv rb-python-noop
-  $ pylintsetup pylintbot.ini
-
-Style issues outside of lines changed by the reviewed changeset should
-be ignored
-
-  $ cat >> foo.py << EOF
-  > import subprocess
-  > subprocess.check_call(['program',
-  >     'arg'])
-  > EOF
-
-  $ flake8 foo.py
-  foo.py:3:5: E128 continuation line under-indented for visual indent
-  [1]
-
-  $ hg -q commit -A -m 'old file version'
-  $ hg phase --public -r .
-
-  $ cat >> foo.py << EOF
-  > subprocess.check_call(['program2', 'arg2'])
-  > EOF
-
-  $ flake8 foo.py
-  foo.py:3:5: E128 continuation line under-indented for visual indent
-  [1]
-
-  $ hg commit -m 'Bug 1 - Verify diff context'
-  $ bugzilla create-bug TestProduct TestComponent bug1
-  $ hg push --config reviewboard.autopublish=false > /dev/null
-  (review requests lack reviewers; visit review url to assign reviewers)
-  $ rbmanage publish 1
-
-  $ python -m pylintbot --config-path ../pylintbot.ini
-  INFO:mozreviewbot:reviewing revision: 3a773b9e226c (review request: 2)
-
-  $ rbmanage dumpreview 2
-  id: 2
-  status: pending
-  public: true
-  bugs:
-  - '1'
-  commit: null
-  submitter: default+5
-  summary: Bug 1 - Verify diff context
-  description:
-  - Bug 1 - Verify diff context
-  - ''
-  - 'MozReview-Commit-ID: 124Bxg'
-  target_people: []
-  extra_data:
-    calculated_trophies: true
-  commit_extra_data:
-    p2rb: true
-    p2rb.author: test
-    p2rb.commit_id: 3a773b9e226cc5486fdd724372938f77204ff12c
-    p2rb.first_public_ancestor: 3edbb5ae6222fc9890db26538597a9b417cb7b94
-    p2rb.identifier: bz://1/mynick
-    p2rb.is_squashed: false
-  diffs:
-  - id: 2
-    revision: 1
-    base_commit_id: 3edbb5ae6222fc9890db26538597a9b417cb7b94
-    name: diff
-    extra: {}
-    patch:
-    - diff --git a/foo.py b/foo.py
-    - '--- a/foo.py'
-    - +++ b/foo.py
-    - '@@ -1,3 +1,4 @@'
-    - ' import subprocess'
-    - ' subprocess.check_call([''program'','
-    - '     ''arg''])'
-    - +subprocess.check_call(['program2', 'arg2'])
-    - ''
-  approved: false
-  approval_failure: A suitable reviewer has not given a "Ship It!"
-  review_count: 1
-  reviews:
-  - id: 1
-    public: true
-    ship_it: false
-    extra_data:
-      p2rb.review_flag: ' '
-    body_top:
-    - And now for something completely different.
-    - ''
-    - Congratulations, there were no Python static analysis issues with this patch!
-    - ''
-    - 'The following files were examined:'
-    - ''
-    - '  foo.py'
-    body_top_text_type: plain
-    diff_comments: []
-
-Cleanup
-
-  $ mozreview stop
-  stopped 9 containers
deleted file mode 100644
--- a/pylib/mozreviewbots/tests/test-pylintbot-line-adjustment.t
+++ /dev/null
@@ -1,369 +0,0 @@
-#require mozreviewdocker
-  $ . $TESTDIR/hgext/reviewboard/tests/helpers.sh
-  $ . $TESTDIR/pylib/mozreviewbots/tests/helpers.sh
-  $ commonenv rb-python-noop
-  $ pylintsetup pylintbot.ini
-
-Create tests for line adjustment -= 1
-
-  $ cat >> e121.py << EOF
-  > sorted(
-  >   [1, 2])
-  > EOF
-
-  $ flake8 e121.py
-  e121.py:2:3: E121 continuation line under-indented for hanging indent
-  [1]
-
-  $ cat >> e122.py << EOF
-  > sorted(
-  > [1, 2])
-  > EOF
-
-  $ flake8 e122.py
-  e122.py:2:1: E122 continuation line missing indentation or outdented
-  [1]
-
-  $ cat >> e126.py << EOF
-  > sorted(
-  >         [1, 2])
-  > EOF
-
-  $ flake8 e126.py
-  e126.py:2:9: E126 continuation line over-indented for hanging indent
-  [1]
-
-  $ cat >> e127.py << EOF
-  > sorted([1, 2],
-  >         cmp=None)
-  > EOF
-
-  $ flake8 e127.py
-  e127.py:2:9: E127 continuation line over-indented for visual indent
-  [1]
-
-  $ cat >> e128.py << EOF
-  > sorted([1, 2],
-  >     cmp=None)
-  > EOF
-
-  $ flake8 e128.py
-  e128.py:2:5: E128 continuation line under-indented for visual indent
-  [1]
-
-  $ cat >> e131.py << EOF
-  > l = [
-  >     1,
-  >    2]
-  > EOF
-
-  $ flake8 e131.py
-  e131.py:3:4: E131 continuation line unaligned for hanging indent
-  [1]
-
-  $ cat >> e301.py << EOF
-  > class Foo(object):
-  >     x = None
-  >     def foo():
-  >         pass
-  > EOF
-
-  $ flake8 e301.py
-  e301.py:3:5: E301 expected 1 blank line, found 0
-  [1]
-
-Line numbers for these failures should be adjusted -= 1 and cover 2 lines
-
-  $ hg -q commit -A -m 'Bug 1 - Line adjustment minus 1'
-  $ bugzilla create-bug TestProduct TestComponent bug1
-
-  $ hg push --config reviewboard.autopublish=false > /dev/null
-  $ rbmanage publish 1
-
-  $ python -m pylintbot --config-path ../pylintbot.ini
-  INFO:mozreviewbot:reviewing revision: c45aead0c4d6 (review request: 2)
-
-  $ rbmanage dumpreview 2
-  id: 2
-  status: pending
-  public: true
-  bugs:
-  - '1'
-  commit: null
-  submitter: default+5
-  summary: Bug 1 - Line adjustment minus 1
-  description:
-  - Bug 1 - Line adjustment minus 1
-  - ''
-  - 'MozReview-Commit-ID: 124Bxg'
-  target_people: []
-  extra_data:
-    calculated_trophies: true
-  commit_extra_data:
-    p2rb: true
-    p2rb.author: test
-    p2rb.commit_id: c45aead0c4d66a05a22fce427658fba6f3e20f9c
-    p2rb.first_public_ancestor: 7c5bdf0cec4a90edb36300f8f3679857f46db829
-    p2rb.identifier: bz://1/mynick
-    p2rb.is_squashed: false
-  diffs:
-  - id: 2
-    revision: 1
-    base_commit_id: 7c5bdf0cec4a90edb36300f8f3679857f46db829
-    name: diff
-    extra: {}
-    patch:
-    - diff --git a/e121.py b/e121.py
-    - new file mode 100644
-    - '--- /dev/null'
-    - +++ b/e121.py
-    - '@@ -0,0 +1,2 @@'
-    - +sorted(
-    - +  [1, 2])
-    - diff --git a/e122.py b/e122.py
-    - new file mode 100644
-    - '--- /dev/null'
-    - +++ b/e122.py
-    - '@@ -0,0 +1,2 @@'
-    - +sorted(
-    - +[1, 2])
-    - diff --git a/e126.py b/e126.py
-    - new file mode 100644
-    - '--- /dev/null'
-    - +++ b/e126.py
-    - '@@ -0,0 +1,2 @@'
-    - +sorted(
-    - +        [1, 2])
-    - diff --git a/e127.py b/e127.py
-    - new file mode 100644
-    - '--- /dev/null'
-    - +++ b/e127.py
-    - '@@ -0,0 +1,2 @@'
-    - +sorted([1, 2],
-    - +        cmp=None)
-    - diff --git a/e128.py b/e128.py
-    - new file mode 100644
-    - '--- /dev/null'
-    - +++ b/e128.py
-    - '@@ -0,0 +1,2 @@'
-    - +sorted([1, 2],
-    - +    cmp=None)
-    - diff --git a/e131.py b/e131.py
-    - new file mode 100644
-    - '--- /dev/null'
-    - +++ b/e131.py
-    - '@@ -0,0 +1,3 @@'
-    - +l = [
-    - +    1,
-    - +   2]
-    - diff --git a/e301.py b/e301.py
-    - new file mode 100644
-    - '--- /dev/null'
-    - +++ b/e301.py
-    - '@@ -0,0 +1,4 @@'
-    - '+class Foo(object):'
-    - +    x = None
-    - '+    def foo():'
-    - +        pass
-    - ''
-  approved: false
-  approval_failure: A suitable reviewer has not given a "Ship It!"
-  review_count: 1
-  reviews:
-  - id: 1
-    public: true
-    ship_it: false
-    body_top:
-    - Always look on the bright side of life.
-    - ''
-    - I analyzed your Python changes and found 7 errors.
-    - ''
-    - 'The following files were examined:'
-    - ''
-    - '  e121.py'
-    - '  e122.py'
-    - '  e126.py'
-    - '  e127.py'
-    - '  e128.py'
-    - '  e131.py'
-    - '  e301.py'
-    body_top_text_type: plain
-    diff_comments:
-    - id: 1
-      public: true
-      user: pylintbot
-      issue_opened: true
-      issue_status: open
-      first_line: 1
-      num_lines: 2
-      text: 'E121: continuation line under-indented for hanging indent'
-      text_type: plain
-      diff_id: 8
-      diff_dest_file: e121.py
-    - id: 2
-      public: true
-      user: pylintbot
-      issue_opened: true
-      issue_status: open
-      first_line: 1
-      num_lines: 2
-      text: 'E122: continuation line missing indentation or outdented'
-      text_type: plain
-      diff_id: 9
-      diff_dest_file: e122.py
-    - id: 3
-      public: true
-      user: pylintbot
-      issue_opened: true
-      issue_status: open
-      first_line: 1
-      num_lines: 2
-      text: 'E126: continuation line over-indented for hanging indent'
-      text_type: plain
-      diff_id: 10
-      diff_dest_file: e126.py
-    - id: 4
-      public: true
-      user: pylintbot
-      issue_opened: true
-      issue_status: open
-      first_line: 1
-      num_lines: 2
-      text: 'E127: continuation line over-indented for visual indent'
-      text_type: plain
-      diff_id: 11
-      diff_dest_file: e127.py
-    - id: 5
-      public: true
-      user: pylintbot
-      issue_opened: true
-      issue_status: open
-      first_line: 1
-      num_lines: 2
-      text: 'E128: continuation line under-indented for visual indent'
-      text_type: plain
-      diff_id: 12
-      diff_dest_file: e128.py
-    - id: 6
-      public: true
-      user: pylintbot
-      issue_opened: true
-      issue_status: open
-      first_line: 2
-      num_lines: 2
-      text: 'E131: continuation line unaligned for hanging indent'
-      text_type: plain
-      diff_id: 13
-      diff_dest_file: e131.py
-    - id: 7
-      public: true
-      user: pylintbot
-      issue_opened: true
-      issue_status: open
-      first_line: 2
-      num_lines: 2
-      text: 'E301: expected 1 blank line, found 0'
-      text_type: plain
-      diff_id: 14
-      diff_dest_file: e301.py
-    diff_count: 7
-
-Create tests for line -= 2
-
-  $ hg -q up -r 0
-
-  $ cat >> e302.py << EOF
-  > def foo():
-  >     pass
-  > 
-  > def bar():
-  >     pass
-  > EOF
-
-  $ flake8 e302.py
-  e302.py:4:1: E302 expected 2 blank lines, found 1
-  [1]
-
-  $ hg -q commit -A -m 'Bug 2 - Line adjustment minus 2'
-  $ bugzilla create-bug TestProduct TestComponent bug2
-
-  $ hg push --config reviewboard.autopublish=false > /dev/null
-  $ rbmanage publish 3
-  $ python -m pylintbot --config-path ../pylintbot.ini
-  INFO:mozreviewbot:reviewing revision: 51dfa0ded22a (review request: 4)
-
-  $ rbmanage dumpreview 4
-  id: 4
-  status: pending
-  public: true
-  bugs:
-  - '2'
-  commit: null
-  submitter: default+5
-  summary: Bug 2 - Line adjustment minus 2
-  description:
-  - Bug 2 - Line adjustment minus 2
-  - ''
-  - 'MozReview-Commit-ID: 5ijR9k'
-  target_people: []
-  extra_data:
-    calculated_trophies: true
-  commit_extra_data:
-    p2rb: true
-    p2rb.author: test
-    p2rb.commit_id: 51dfa0ded22aa53d3e7c3d7b5342ba8734c4c6ce
-    p2rb.first_public_ancestor: 7c5bdf0cec4a90edb36300f8f3679857f46db829
-    p2rb.identifier: bz://2/mynick
-    p2rb.is_squashed: false
-  diffs:
-  - id: 4
-    revision: 1
-    base_commit_id: 7c5bdf0cec4a90edb36300f8f3679857f46db829
-    name: diff
-    extra: {}
-    patch:
-    - diff --git a/e302.py b/e302.py
-    - new file mode 100644
-    - '--- /dev/null'
-    - +++ b/e302.py
-    - '@@ -0,0 +1,5 @@'
-    - '+def foo():'
-    - +    pass
-    - +
-    - '+def bar():'
-    - +    pass
-    - ''
-  approved: false
-  approval_failure: A suitable reviewer has not given a "Ship It!"
-  review_count: 1
-  reviews:
-  - id: 2
-    public: true
-    ship_it: false
-    body_top:
-    - Always look on the bright side of life.
-    - ''
-    - I analyzed your Python changes and found 1 errors.
-    - ''
-    - 'The following files were examined:'
-    - ''
-    - '  e302.py'
-    body_top_text_type: plain
-    diff_comments:
-    - id: 8
-      public: true
-      user: pylintbot
-      issue_opened: true
-      issue_status: open
-      first_line: 2
-      num_lines: 3
-      text: 'E302: expected 2 blank lines, found 1'
-      text_type: plain
-      diff_id: 16
-      diff_dest_file: e302.py
-    diff_count: 1
-
-Cleanup
-
-  $ mozreview stop
-  stopped 9 containers
deleted file mode 100644
--- a/pylib/mozreviewbots/tests/test-pylintbot-noop.t
+++ /dev/null
@@ -1,125 +0,0 @@
-#require mozreviewdocker
-  $ . $TESTDIR/hgext/reviewboard/tests/helpers.sh
-  $ . $TESTDIR/pylib/mozreviewbots/tests/helpers.sh
-  $ commonenv rb-python-noop
-  $ pylintsetup pylintbot.ini
-
-Create a review request that doesn't touch any Python files
-
-  $ bugzilla create-bug TestProduct TestComponent bug1
-  $ echo irrelevant > foo
-  $ hg commit -m 'Bug 1 - No Python changes'
-  $ hg push --config reviewboard.autopublish=false > /dev/null
-  (review requests lack reviewers; visit review url to assign reviewers)
-  $ rbmanage publish 1
-
-No review should be left if no Python files were changed.
-
-  $ python -m pylintbot --config-path ../pylintbot.ini
-  INFO:mozreviewbot:reviewing revision: 97bc3c7259df (review request: 2)
-  INFO:mozreviewbot:not reviewing revision: 97bc3c7259dfe4c83e2d1ac3e6b252a5331da9cd no relevant python changes in commit
-
-  $ rbmanage dumpreview 2
-  id: 2
-  status: pending
-  public: true
-  bugs:
-  - '1'
-  commit: null
-  submitter: default+5
-  summary: Bug 1 - No Python changes
-  description:
-  - Bug 1 - No Python changes
-  - ''
-  - 'MozReview-Commit-ID: 124Bxg'
-  target_people: []
-  extra_data:
-    calculated_trophies: true
-  commit_extra_data:
-    p2rb: true
-    p2rb.author: test
-    p2rb.commit_id: 97bc3c7259dfe4c83e2d1ac3e6b252a5331da9cd
-    p2rb.first_public_ancestor: 7c5bdf0cec4a90edb36300f8f3679857f46db829
-    p2rb.identifier: bz://1/mynick
-    p2rb.is_squashed: false
-  diffs:
-  - id: 2
-    revision: 1
-    base_commit_id: 7c5bdf0cec4a90edb36300f8f3679857f46db829
-    name: diff
-    extra: {}
-    patch:
-    - diff --git a/foo b/foo
-    - '--- a/foo'
-    - +++ b/foo
-    - '@@ -1,1 +1,1 @@'
-    - -foo0
-    - +irrelevant
-    - ''
-  approved: false
-  approval_failure: A suitable reviewer has not given a "Ship It!"
-
-If only changes are deletions, then no review should be posted
-
-  $ bugzilla create-bug TestProduct TestComponent bug2
-  $ hg -q up -r 0
-  $ echo dummy > test.py
-  $ hg -q commit -A -m 'Add test.py'
-  $ hg phase --public -r .
-  $ hg rm test.py
-  $ hg commit -m 'Bug 2 - Delete test.py'
-
-  $ hg push --config reviewboard.autopublish=false > /dev/null
-  (review requests lack reviewers; visit review url to assign reviewers)
-  $ rbmanage publish 3
-
-  $ python -m pylintbot --config-path ../pylintbot.ini
-  INFO:mozreviewbot:reviewing revision: bbd1278082cf (review request: 4)
-  INFO:mozreviewbot:not reviewing revision: bbd1278082cfb76d7c5c4422748d5cf8679a5bcd no relevant python changes in commit
-
-Expecting 0 reviews
-
-  $ rbmanage dumpreview 3
-  id: 3
-  status: pending
-  public: true
-  bugs:
-  - '2'
-  commit: bz://2/mynick
-  submitter: default+5
-  summary: bz://2/mynick
-  description: This is the parent review request
-  target_people: []
-  extra_data:
-    calculated_trophies: true
-    p2rb.reviewer_map: '{}'
-  commit_extra_data:
-    p2rb: true
-    p2rb.base_commit: 98dca3b6ee0c2e2bfa0921991abd87ed7abe7baf
-    p2rb.commits: '[["bbd1278082cfb76d7c5c4422748d5cf8679a5bcd", 4]]'
-    p2rb.discard_on_publish_rids: '[]'
-    p2rb.first_public_ancestor: 98dca3b6ee0c2e2bfa0921991abd87ed7abe7baf
-    p2rb.identifier: bz://2/mynick
-    p2rb.is_squashed: true
-    p2rb.unpublished_rids: '[]'
-  diffs:
-  - id: 3
-    revision: 1
-    base_commit_id: 98dca3b6ee0c2e2bfa0921991abd87ed7abe7baf
-    name: diff
-    extra: {}
-    patch:
-    - diff --git a/test.py b/test.py
-    - deleted file mode 100644
-    - '--- a/test.py'
-    - +++ /dev/null
-    - '@@ -1,1 +0,0 @@'
-    - -dummy
-    - ''
-  approved: false
-  approval_failure: Commit bbd1278082cfb76d7c5c4422748d5cf8679a5bcd is not approved.
-
-Cleanup
-
-  $ mozreview stop
-  stopped 9 containers
deleted file mode 100644
--- a/pylib/mozreviewbots/tests/test-pylintbot.t
+++ /dev/null
@@ -1,204 +0,0 @@
-#require mozreviewdocker
-  $ . $TESTDIR/hgext/reviewboard/tests/helpers.sh
-  $ . $TESTDIR/pylib/mozreviewbots/tests/helpers.sh
-  $ commonenv rb-python-noop
-  $ pylintsetup pylintbot.ini
-
-Create a review with Python style violations
-
-  $ bugzilla create-bug TestProduct TestComponent bug1
-
-  $ cat >> foo.py << EOF
-  > def a(): pass
-  > 
-  >   
-  > def b():
-  >     foo = True
-  > EOF
-
-  $ hg -q commit -A -m 'Bug 1 - Bad Python'
-  $ hg push --config reviewboard.autopublish=false > /dev/null
-  $ rbmanage publish 1
-  $ python -m pylintbot --config-path ../pylintbot.ini
-  INFO:mozreviewbot:reviewing revision: 1978e5417012 (review request: 2)
-
-  $ rbmanage dumpreview 2
-  id: 2
-  status: pending
-  public: true
-  bugs:
-  - '1'
-  commit: null
-  submitter: default+5
-  summary: Bug 1 - Bad Python
-  description:
-  - Bug 1 - Bad Python
-  - ''
-  - 'MozReview-Commit-ID: 124Bxg'
-  target_people: []
-  extra_data:
-    calculated_trophies: true
-  commit_extra_data:
-    p2rb: true
-    p2rb.author: test
-    p2rb.commit_id: 1978e5417012a5f63128d09cfd52c52077c761cb
-    p2rb.first_public_ancestor: 7c5bdf0cec4a90edb36300f8f3679857f46db829
-    p2rb.identifier: bz://1/mynick
-    p2rb.is_squashed: false
-  diffs:
-  - id: 2
-    revision: 1
-    base_commit_id: 7c5bdf0cec4a90edb36300f8f3679857f46db829
-    name: diff
-    extra: {}
-    patch:
-    - diff --git a/foo.py b/foo.py
-    - new file mode 100644
-    - '--- /dev/null'
-    - +++ b/foo.py
-    - '@@ -0,0 +1,5 @@'
-    - '+def a(): pass'
-    - +
-    - '+  '
-    - '+def b():'
-    - +    foo = True
-    - ''
-  approved: false
-  approval_failure: A suitable reviewer has not given a "Ship It!"
-  review_count: 1
-  reviews:
-  - id: 1
-    public: true
-    ship_it: false
-    body_top:
-    - Always look on the bright side of life.
-    - ''
-    - I analyzed your Python changes and found 3 errors.
-    - ''
-    - 'The following files were examined:'
-    - ''
-    - '  foo.py'
-    body_top_text_type: plain
-    diff_comments:
-    - id: 1
-      public: true
-      user: pylintbot
-      issue_opened: true
-      issue_status: open
-      first_line: 1
-      num_lines: 1
-      text: 'E701: multiple statements on one line (colon)'
-      text_type: plain
-      diff_id: 2
-      diff_dest_file: foo.py
-    - id: 2
-      public: true
-      user: pylintbot
-      issue_opened: true
-      issue_status: open
-      first_line: 3
-      num_lines: 1
-      text: 'W293: blank line contains whitespace'
-      text_type: plain
-      diff_id: 2
-      diff_dest_file: foo.py
-    - id: 3
-      public: true
-      user: pylintbot
-      issue_opened: true
-      issue_status: open
-      first_line: 5
-      num_lines: 1
-      text: 'F841: local variable ''foo'' is assigned to but never used'
-      text_type: plain
-      diff_id: 2
-      diff_dest_file: foo.py
-    diff_count: 3
-
-Ensure pyflakes warnings are handled
-
-  $ hg -q up -r 0
-  $ cat >> f401.py << EOF
-  > import sys
-  > EOF
-
-  $ hg -q commit -A -m 'Bug 2 - pyflakes'
-  $ bugzilla create-bug TestProduct TestComponent bug1
-  $ hg push --config reviewboard.autopublish=false > /dev/null
-
-  $ rbmanage publish 3
-
-  $ python -m pylintbot --config-path ../pylintbot.ini
-  INFO:mozreviewbot:reviewing revision: c768227fc261 (review request: 4)
-
-  $ rbmanage dumpreview 4
-  id: 4
-  status: pending
-  public: true
-  bugs:
-  - '2'
-  commit: null
-  submitter: default+5
-  summary: Bug 2 - pyflakes
-  description:
-  - Bug 2 - pyflakes
-  - ''
-  - 'MozReview-Commit-ID: 5ijR9k'
-  target_people: []
-  extra_data:
-    calculated_trophies: true
-  commit_extra_data:
-    p2rb: true
-    p2rb.author: test
-    p2rb.commit_id: c768227fc261de5a93e0c813e0ba4a54e24d2697
-    p2rb.first_public_ancestor: 7c5bdf0cec4a90edb36300f8f3679857f46db829
-    p2rb.identifier: bz://2/mynick
-    p2rb.is_squashed: false
-  diffs:
-  - id: 4
-    revision: 1
-    base_commit_id: 7c5bdf0cec4a90edb36300f8f3679857f46db829
-    name: diff
-    extra: {}
-    patch:
-    - diff --git a/f401.py b/f401.py
-    - new file mode 100644
-    - '--- /dev/null'
-    - +++ b/f401.py
-    - '@@ -0,0 +1,1 @@'
-    - +import sys
-    - ''
-  approved: false
-  approval_failure: A suitable reviewer has not given a "Ship It!"
-  review_count: 1
-  reviews:
-  - id: 2
-    public: true
-    ship_it: false
-    body_top:
-    - Always look on the bright side of life.
-    - ''
-    - I analyzed your Python changes and found 1 errors.
-    - ''
-    - 'The following files were examined:'
-    - ''
-    - '  f401.py'
-    body_top_text_type: plain
-    diff_comments:
-    - id: 4
-      public: true
-      user: pylintbot
-      issue_opened: true
-      issue_status: open
-      first_line: 1
-      num_lines: 1
-      text: 'F401: ''sys'' imported but unused'
-      text_type: plain
-      diff_id: 4
-      diff_dest_file: f401.py
-    diff_count: 1
-
-Cleanup
-
-  $ mozreview stop
-  stopped 9 containers
deleted file mode 100644
--- a/pylib/mozreviewbots/tests/test-snarkbot.t
+++ /dev/null
@@ -1,166 +0,0 @@
-#require mozreviewdocker
-  $ . $TESTDIR/hgext/reviewboard/tests/helpers.sh
-  $ commonenv rb-test-snarkbot
-  $ mozreview create-user snarkbot@example.com password 'Snarkbot :snarkbot' --bugzilla-group editbugs --uid 2000 --scm-level 3 > /dev/null
-
-  $ cat > snarkbot.ini << EOF
-  > [pulse]
-  > host = ${PULSE_HOST}
-  > port = ${PULSE_PORT}
-  > userid = guest
-  > password = guest
-  > exchange = exchange/mozreview/
-  > queue = all
-  > ssl = False
-  > routing_key = #
-  > timeout = 60.0
-  > [reviewboard]
-  > url = ${REVIEWBOARD_URL}
-  > user = snarkbot@example.com
-  > password = password
-  > EOF
-
-  $ cd client
-  $ echo foo0 > foo
-  $ hg commit -A -m 'root commit'
-  adding foo
-  $ hg phase --public -r .
-  $ hg push --noreview > /dev/null
-
-Create and publish a review for SnarkBot
-
-  $ bugzilla create-bug TestProduct TestComponent bug1
-
-  $ echo foo1 > foo
-  $ echo foo1 > foo1
-  $ hg add foo1
-  $ hg commit -m 'Bug 1 - Foo 1'
-  $ echo foo2 > foo
-  $ echo foo2 > foo2
-  $ hg add foo2
-  $ hg commit -m 'Bug 1 - Foo 2'
-  $ hg push --config reviewboard.autopublish=false
-  pushing to ssh://$DOCKER_HOSTNAME:$HGPORT6/test-repo
-  (adding commit id to 2 changesets)
-  saved backup bundle to $TESTTMP/client/.hg/strip-backup/a4f23bfb8f88-0ce7b28d-addcommitid.hg (glob)
-  searching for changes
-  remote: adding changesets
-  remote: adding manifests
-  remote: adding file changes
-  remote: added 2 changesets with 4 changes to 3 files
-  remote: recorded push in pushlog
-  submitting 2 changesets for review
-  
-  changeset:  1:729692c35796
-  summary:    Bug 1 - Foo 1
-  review:     http://$DOCKER_HOSTNAME:$HGPORT1/r/2 (draft)
-  
-  changeset:  2:fb16157e773b
-  summary:    Bug 1 - Foo 2
-  review:     http://$DOCKER_HOSTNAME:$HGPORT1/r/3 (draft)
-  
-  review id:  bz://1/mynick
-  review url: http://$DOCKER_HOSTNAME:$HGPORT1/r/1 (draft)
-  
-  (review requests lack reviewers; visit review url to assign reviewers)
-  (visit review url to publish these review requests so others can see them)
-
-  $ rbmanage publish 1
-  $ python -m snarkbot --config-path ../snarkbot.ini
-  INFO:mozreviewbot:reviewing commit: 729692c35796 for review request: 2 diff_revision: 1
-  INFO:mozreviewbot:looking at file: foo (foo)
-  INFO:mozreviewbot:foo1
-  
-  INFO:mozreviewbot:looking at file: foo1 (foo1)
-  INFO:mozreviewbot:foo1
-  
-  INFO:mozreviewbot:reviewing commit: fb16157e773b for review request: 3 diff_revision: 1
-  INFO:mozreviewbot:looking at file: foo (foo)
-  INFO:mozreviewbot:foo2
-  
-  INFO:mozreviewbot:looking at file: foo2 (foo2)
-  INFO:mozreviewbot:foo2
-  
-  $ rbmanage dumpreview 2
-  id: 2
-  status: pending
-  public: true
-  bugs:
-  - '1'
-  commit: null
-  submitter: default+5
-  summary: Bug 1 - Foo 1
-  description:
-  - Bug 1 - Foo 1
-  - ''
-  - 'MozReview-Commit-ID: 124Bxg'
-  target_people: []
-  extra_data:
-    calculated_trophies: true
-  commit_extra_data:
-    p2rb: true
-    p2rb.author: test
-    p2rb.commit_id: 729692c35796d9cbd453ccef97ee0d14139c4a09
-    p2rb.first_public_ancestor: 7c5bdf0cec4a90edb36300f8f3679857f46db829
-    p2rb.identifier: bz://1/mynick
-    p2rb.is_squashed: false
-  diffs:
-  - id: 2
-    revision: 1
-    base_commit_id: 7c5bdf0cec4a90edb36300f8f3679857f46db829
-    name: diff
-    extra: {}
-    patch:
-    - diff --git a/foo b/foo
-    - '--- a/foo'
-    - +++ b/foo
-    - '@@ -1,1 +1,1 @@'
-    - -foo0
-    - +foo1
-    - diff --git a/foo1 b/foo1
-    - new file mode 100644
-    - '--- /dev/null'
-    - +++ b/foo1
-    - '@@ -0,0 +1,1 @@'
-    - +foo1
-    - ''
-  approved: false
-  approval_failure: A suitable reviewer has not given a "Ship It!"
-  review_count: 1
-  reviews:
-  - id: 1
-    public: true
-    ship_it: false
-    extra_data:
-      p2rb.review_flag: ' '
-    body_top: 'This is what I think of your changes:'
-    body_top_text_type: plain
-    diff_comments:
-    - id: 1
-      public: true
-      user: snarkbot
-      issue_opened: true
-      issue_status: open
-      first_line: 1
-      num_lines: 1
-      text: seriously?
-      text_type: plain
-      diff_id: 4
-      diff_dest_file: foo
-    - id: 2
-      public: true
-      user: snarkbot
-      issue_opened: true
-      issue_status: open
-      first_line: 1
-      num_lines: 1
-      text: seriously?
-      text_type: plain
-      diff_id: 5
-      diff_dest_file: foo1
-    diff_count: 2
-
-Cleanup
-
-  $ mozreview stop
-  stopped 9 containers