Bug 1517059 - Tidy up Mozmill/JSBridge resources; r=aceman
authorGeoff Lankow <geoff@darktrojan.net>
Mon, 07 Jan 2019 21:53:00 +1300
changeset 34133 f2f995d7cf9ae66566e1876d71fb9c69111794ad
parent 34132 ab47677bc7ad7c124e96bd50102a6983fdf51a74
child 34134 f8ef458411f47cdc8d7b39dc4ccbf4ab0afaf546
push id389
push userclokep@gmail.com
push dateMon, 18 Mar 2019 19:01:53 +0000
reviewersaceman
bugs1517059
Bug 1517059 - Tidy up Mozmill/JSBridge resources; r=aceman
mail/test/mozmill/content-policy/wrapper.py
mail/test/mozmill/content-tabs/wrapper.py
mail/test/mozmill/folder-tree-modes/wrapper.py
mail/test/mozmill/instrumentation/wrapper.py
mail/test/mozmill/override-main-menu-collapse/wrapper.py
mail/test/mozmill/pref-window/wrapper.py
mail/test/mozmill/runtest.py
mail/test/mozmill/runtestlist.py
mail/test/mozmill/startup-firstrun/wrapper.py
mail/test/resources/installmozmill.py
mail/test/resources/jsbridge/jsbridge/__init__.py
mail/test/resources/jsbridge/jsbridge/jsobjects.py
mail/test/resources/jsbridge/jsbridge/network.py
mail/test/resources/jsbridge/setup.py
mail/test/resources/mozmill/MANIFEST.in
mail/test/resources/mozmill/mozmill/__init__.py
mail/test/resources/mozmill/setup.py
--- a/mail/test/mozmill/content-policy/wrapper.py
+++ b/mail/test/mozmill/content-policy/wrapper.py
@@ -1,15 +1,15 @@
 # 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 os
 import shutil
-import sys
+
 
 def on_profile_created(profiledir):
     """
     On profile creation, this copies *-prefs.js from the current folder to
     profile_dir as a user.js file. These user prefs is interpreted in addition
     to the standard prefs.js file.
     """
     # The pref file is in the same directory this script is in.
--- a/mail/test/mozmill/content-tabs/wrapper.py
+++ b/mail/test/mozmill/content-tabs/wrapper.py
@@ -1,15 +1,15 @@
 # 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 os
 import shutil
-import sys
+
 
 def on_profile_created(profiledir):
     """
     On profile creation, this copies *-prefs.js from the current folder to
     profile_dir as a user.js file. These user prefs is interpreted in addition
     to the standard prefs.js file.
     """
     # The pref file is in the same directory this script is in.
--- a/mail/test/mozmill/folder-tree-modes/wrapper.py
+++ b/mail/test/mozmill/folder-tree-modes/wrapper.py
@@ -1,16 +1,17 @@
 # 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/.
 
 # We install an extension that provides a test folder tree mode.
 
 import os
 
+
 def on_before_start(profile):
     """
     This installs the extension in the test-extension subdirectory into the
     profile folder. We cannot use on_profile_created here because
     install_plugin/install_addon depends on the profile object being fully
     initialized.
     """
 
--- a/mail/test/mozmill/instrumentation/wrapper.py
+++ b/mail/test/mozmill/instrumentation/wrapper.py
@@ -3,21 +3,21 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # For test-instrumentation.js, we need to disable the account provisioner, or
 # else it will spawn immediately and block before we have a chance to run
 # any Mozmill tests.
 
 import os
 import shutil
-import sys
 
 # We don't want any accounts for these tests.
 NO_ACCOUNTS = True
 
+
 def on_profile_created(profiledir):
     """
     On profile creation, this copies *-prefs.js from the current folder to
     profile_dir as a user.js file. These user prefs is interpreted in addition
     to the standard prefs.js file.
     """
     # The pref file is in the same directory this script is in.
     preffile = os.path.join(os.path.dirname(__file__), "prefs.js")
--- a/mail/test/mozmill/override-main-menu-collapse/wrapper.py
+++ b/mail/test/mozmill/override-main-menu-collapse/wrapper.py
@@ -3,23 +3,23 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # For these tests, we need to disable the account provisioner, or
 # else it will spawn immediately and block before we have a chance to run
 # any Mozmill tests.
 
 import os
 import shutil
-import sys
 
 # We don't want any accounts for these tests.
 NO_ACCOUNTS = True
 # Do not force enable main menu bar, we'll set our own value in prefs.js.
 DEFAULT_MENUBAR = True
 
+
 def on_profile_created(profiledir):
     """
     On profile creation, this copies prefs.js from the current folder to
     profile_dir as a user.js file. These user prefs is interpreted in addition
     to the standard prefs.js file.
     """
     # The pref file is in the same directory this script is in.
     preffile = os.path.join(os.path.dirname(__file__), "prefs.js")
--- a/mail/test/mozmill/pref-window/wrapper.py
+++ b/mail/test/mozmill/pref-window/wrapper.py
@@ -9,16 +9,17 @@ import shutil
 import sys
 
 _pref_file_names = {
     "win32": "windows-prefs.js",
     "darwin": "mac-prefs.js",
     "linux2": "linux-prefs.js",
 }
 
+
 def on_profile_created(profiledir):
     """
     On profile creation, this copies *-prefs.js from the current folder to
     profile_dir as a user.js file. These user prefs is interpreted in addition
     to the standard prefs.js file.
     """
     # The pref file is in the same directory this script is in.
     # Fallback to Linux prefs for anything not in the dictionary -- we're
--- a/mail/test/mozmill/runtest.py
+++ b/mail/test/mozmill/runtest.py
@@ -1,42 +1,35 @@
 #!/usr/bin/env python
 # 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/.
 
-"""
-Runs the Bloat test harness
-"""
-
-import sys
-import os, os.path, platform, subprocess, signal
-import shutil
+from base64 import b64decode
+from time import sleep
+import atexit
+import imp
+import jsbridge
+import json
+import mozcrash
+import mozmill
 import mozprofile
 import mozrunner
-import jsbridge
-import mozmill
-import socket
-import copy
-
-# Python 2.6 has the json module, but Python 2.5 doesn't.
-try:
-    import json
-except ImportError:
-    import simplejson as json
+import os
+import platform
+import shutil
+import stat
+import subprocess
+import sys
+import tempfile
 
 SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
 sys.path.append(SCRIPT_DIRECTORY)
 
-import mozcrash
-
-from time import sleep
-import imp
-
-PROFILE_DIR = os.path.join(SCRIPT_DIRECTORY, 'mozmillprofile')
+PROFILE_DIR = os.path.join(tempfile.gettempdir(), 'mozmillprofile')
 SYMBOLS_PATH = None
 PLUGINS_PATH = None
 # XXX This breaks any semblance of test runner modularity, and only works
 # because we know that we run MozMill only once per process. This needs to be
 # fixed if that ever changes.
 TEST_NAME = None
 TESTING_MODULES_DIR = None
 
@@ -50,46 +43,47 @@ wrapper = None
 
 # Shall we print out a big blob of base64 to allow post-processors to print out
 # a screenshot at the time the failure happened?
 USE_RICH_FAILURES = False
 
 # Save screenshots of failures, so they can be uploaded as a TaskCluster artifact?
 UPLOAD_SCREENSHOTS = False
 
+
 # We need this because rmtree-ing read-only files fails on Windows
 def rmtree_onerror(func, path, exc_info):
     """
     Error handler for ``shutil.rmtree``.
 
     If the error is due to an access error (read only file)
     it attempts to add write permission and then retries.
 
     If the error is for another reason it re-raises the error.
 
-    Usage : ``shutil.rmtree(path, onerror=rmtree_onerror)``
+    Usage: ``shutil.rmtree(path, onerror=rmtree_onerror)``
     """
-    import stat
     if not os.access(path, os.W_OK):
         # Is the error an access error ?
         os.chmod(path, stat.S_IWUSR)
         func(path)
     else:
         raise
 
+
 class ThunderTestProfile(mozprofile.ThunderbirdProfile):
     preferences = {
         # say yes to debug output via dump
         'browser.dom.window.dump.enabled': True,
         # say no to slow script warnings
         'dom.max_chrome_script_run_time': 0,
         'dom.max_script_run_time': 0,
         # disable extension stuffs
-        'extensions.update.enabled'    : False,
-        'extensions.update.notifyUser' : False,
+        'extensions.update.enabled': False,
+        'extensions.update.notifyUser': False,
         # don't warn about third party extensions in profile or elsewhere.
         'extensions.autoDisableScopes': 10,
         # do not ask about being the default mail client
         'mail.shell.checkDefaultClient': False,
         # do not tell us about the greatness that is mozilla (about:rights)
         'mail.rights.override': True,
         # disable non-gloda indexing daemons
         'mail.winsearch.enable': False,
@@ -97,167 +91,170 @@ class ThunderTestProfile(mozprofile.Thun
         'mail.spotlight.enable': False,
         'mail.spotlight.firstRunDone': True,
         # disable address books for undisclosed reasons
         'ldap_2.servers.osx.position': 0,
         'ldap_2.servers.oe.position': 0,
         # disable the first use junk dialog
         'mailnews.ui.junk.firstuse': False,
         # set the relative dirs properly
-        'mail.root.none-rel' :  "[ProfD]Mail",
-        'mail.root.pop3-rel' :  "[ProfD]Mail",
+        'mail.root.none-rel': "[ProfD]Mail",
+        'mail.root.pop3-rel': "[ProfD]Mail",
         # Do not allow check new mail to be set
-        'mail.startup.enabledMailCheckOnce' :  True,
+        'mail.startup.enabledMailCheckOnce': True,
         # Disable compatibility checking
         'extensions.checkCompatibility.nightly': False,
         # Stop any pings to AMO on add-on install
         'extensions.getAddons.cache.enabled': False,
         # In case a developer is working on a laptop without a network
         # connection, don't detect offline mode; hence we'll still startup
         # online which is what mozmill currently requires. It'll also protect us
         # from any random network failures.
         'offline.autoDetect': False,
         # Don't load what's new or the remote start page - keep everything local
         # under our control.
-        'mailnews.start_page_override.mstone' :  "ignore",
+        'mailnews.start_page_override.mstone': "ignore",
         'mailnews.start_page.url': "about:blank",
         # Do not enable gloda
         'mailnews.database.global.indexer.enabled': False,
         # But do have gloda log if it does anything.  (When disabled, queries
         # are still serviced; they just should not result in any matches.)
         'mailnews.database.global.logging.upstream': True,
         # Do not allow fonts to be upgraded
         'mail.font.windows.version': 2,
         # No, we don't want to be prompted about Telemetry
         'toolkit.telemetry.prompted': 999,
-        }
+    }
 
     menubar_preferences = {
         # Many tests operate items in the main menu, so keep it shown
         # until they are migrated to appmenu.
         'mail.main_menu.collapse_by_default': False,
     }
 
     # Dummied up local accounts to stop the account wizard
     account_preferences = {
-        'mail.account.account1.server' :  "server1",
-        'mail.account.account2.identities' :  "id1,id2",
-        'mail.account.account2.server' :  "server2",
-        'mail.account.account3.server' :  "server3",
-        'mail.accountmanager.accounts' :  "account1,account2,account3",
-        'mail.accountmanager.defaultaccount' :  "account2",
-        'mail.accountmanager.localfoldersserver' :  "server1",
-        'mail.identity.id1.fullName' :  "Tinderbox",
-        'mail.identity.id1.htmlSigFormat' : False,
-        'mail.identity.id1.htmlSigText' : "Tinderbox is soo 90ies",
-        'mail.identity.id1.smtpServer' :  "smtp1",
-        'mail.identity.id1.useremail' :  "tinderbox@foo.invalid",
-        'mail.identity.id1.valid' :  True,
-        'mail.identity.id2.fullName' : "Tinderboxpushlog",
-        'mail.identity.id2.htmlSigFormat' : True,
-        'mail.identity.id2.htmlSigText' : "Tinderboxpushlog is the new <b>hotness!</b>",
-        'mail.identity.id2.smtpServer' : "smtp1",
-        'mail.identity.id2.useremail' : "tinderboxpushlog@foo.invalid",
-        'mail.identity.id2.valid' : True,
-        'mail.server.server1.directory-rel' :  "[ProfD]Mail/Local Folders",
-        'mail.server.server1.hostname' :  "Local Folders",
-        'mail.server.server1.name' :  "Local Folders",
-        'mail.server.server1.type' :  "none",
-        'mail.server.server1.userName' :  "nobody",
-        'mail.server.server2.check_new_mail' :  False,
-        'mail.server.server2.directory-rel' :  "[ProfD]Mail/tinderbox",
-        'mail.server.server2.download_on_biff' :  True,
-        'mail.server.server2.hostname' :  "tinderbox123",
-        'mail.server.server2.login_at_startup' :  False,
-        'mail.server.server2.name' :  "tinderbox@foo.invalid",
-        'mail.server.server2.type' :  "pop3",
-        'mail.server.server2.userName' :  "tinderbox",
+        'mail.account.account1.server': "server1",
+        'mail.account.account2.identities': "id1,id2",
+        'mail.account.account2.server': "server2",
+        'mail.account.account3.server': "server3",
+        'mail.accountmanager.accounts': "account1,account2,account3",
+        'mail.accountmanager.defaultaccount': "account2",
+        'mail.accountmanager.localfoldersserver': "server1",
+        'mail.identity.id1.fullName': "Tinderbox",
+        'mail.identity.id1.htmlSigFormat': False,
+        'mail.identity.id1.htmlSigText': "Tinderbox is soo 90ies",
+        'mail.identity.id1.smtpServer': "smtp1",
+        'mail.identity.id1.useremail': "tinderbox@foo.invalid",
+        'mail.identity.id1.valid': True,
+        'mail.identity.id2.fullName': "Tinderboxpushlog",
+        'mail.identity.id2.htmlSigFormat': True,
+        'mail.identity.id2.htmlSigText': "Tinderboxpushlog is the new <b>hotness!</b>",
+        'mail.identity.id2.smtpServer': "smtp1",
+        'mail.identity.id2.useremail': "tinderboxpushlog@foo.invalid",
+        'mail.identity.id2.valid': True,
+        'mail.server.server1.directory-rel': "[ProfD]Mail/Local Folders",
+        'mail.server.server1.hostname': "Local Folders",
+        'mail.server.server1.name': "Local Folders",
+        'mail.server.server1.type': "none",
+        'mail.server.server1.userName': "nobody",
+        'mail.server.server2.check_new_mail': False,
+        'mail.server.server2.directory-rel': "[ProfD]Mail/tinderbox",
+        'mail.server.server2.download_on_biff': True,
+        'mail.server.server2.hostname': "tinderbox123",
+        'mail.server.server2.login_at_startup': False,
+        'mail.server.server2.name': "tinderbox@foo.invalid",
+        'mail.server.server2.type': "pop3",
+        'mail.server.server2.userName': "tinderbox",
         'mail.server.server2.whiteListAbURI': "",
-        'mail.server.server3.hostname' :  "prpl-irc",
-        'mail.server.server3.imAccount' :  "account1",
-        'mail.server.server3.type' :  "im",
-        'mail.server.server3.userName' :  "mozmilltest@irc.mozilla.invalid",
-        'mail.smtp.defaultserver' :  "smtp1",
-        'mail.smtpserver.smtp1.hostname' :  "tinderbox123",
-        'mail.smtpserver.smtp1.username' :  "tinderbox",
-        'mail.smtpservers' :  "smtp1",
-        'messenger.account.account1.autoLogin' :  False,
-        'messenger.account.account1.firstConnectionState' :  1,
-        'messenger.account.account1.name' :  "mozmilltest@irc.mozilla.invalid",
-        'messenger.account.account1.prpl' :  "prpl-irc",
-        'messenger.accounts' :  "account1",
+        'mail.server.server3.hostname': "prpl-irc",
+        'mail.server.server3.imAccount': "account1",
+        'mail.server.server3.type': "im",
+        'mail.server.server3.userName': "mozmilltest@irc.mozilla.invalid",
+        'mail.smtp.defaultserver': "smtp1",
+        'mail.smtpserver.smtp1.hostname': "tinderbox123",
+        'mail.smtpserver.smtp1.username': "tinderbox",
+        'mail.smtpservers': "smtp1",
+        'messenger.account.account1.autoLogin': False,
+        'messenger.account.account1.firstConnectionState': 1,
+        'messenger.account.account1.name': "mozmilltest@irc.mozilla.invalid",
+        'messenger.account.account1.prpl': "prpl-irc",
+        'messenger.accounts': "account1",
     }
 
     def __init__(self, *args, **kwargs):
         kwargs['profile'] = self.get_profile_dir()
         super(ThunderTestProfile, self).__init__(*args, **kwargs)
         self.set_preferences(self.preferences)
 
-        if (wrapper is not None and hasattr(wrapper, "DEFAULT_MENUBAR")
-            and wrapper.DEFAULT_MENUBAR):
+        if (wrapper is not None and
+                hasattr(wrapper, "DEFAULT_MENUBAR") and
+                wrapper.DEFAULT_MENUBAR):
             pass
         else:
             self.set_preferences(self.menubar_preferences)
 
-        if (wrapper is not None and hasattr(wrapper, "NO_ACCOUNTS")
-            and wrapper.NO_ACCOUNTS):
+        if (wrapper is not None and
+                hasattr(wrapper, "NO_ACCOUNTS") and
+                wrapper.NO_ACCOUNTS):
             pass
         else:
             self.set_preferences(self.account_preferences)
 
-
     def get_profile_dir(self):
         '''
         We always put our profile in the same location.  We only clear it out
         when we are creating a new profile so that we can go in after the run
         and examine things for debugging or general interest.
         '''
         # create a clean directory
         if os.path.exists(PROFILE_DIR):
             shutil.rmtree(PROFILE_DIR, onerror=rmtree_onerror)
         os.makedirs(PROFILE_DIR)
         print 'Using profile dir:', PROFILE_DIR
         if not os.path.exists(PROFILE_DIR):
             raise Exception('somehow failed to create profile dir!')
 
         if PLUGINS_PATH:
-          if not os.path.exists(PLUGINS_PATH):
-            raise Exception('Plugins path "%s" does not exist.' % PLUGINS_PATH)
+            if not os.path.exists(PLUGINS_PATH):
+                raise Exception('Plugins path "%s" does not exist.' % PLUGINS_PATH)
 
-          dest = os.path.join(PROFILE_DIR, "plugins")
-          shutil.copytree(PLUGINS_PATH, dest)
+            dest = os.path.join(PROFILE_DIR, "plugins")
+            shutil.copytree(PLUGINS_PATH, dest)
 
         if wrapper is not None and hasattr(wrapper, "on_profile_created"):
             # It's a little dangerous to allow on_profile_created access to the
             # profile object, because it isn't fully initialized yet
             wrapper.on_profile_created(PROFILE_DIR)
 
         return PROFILE_DIR
 
     def cleanup(self):
         '''
         Do not cleanup at all.  The next iteration will cleanup for us, but
         until that time it's useful for debugging failures to leave everything
         around.
         '''
         pass
 
+
 def ThunderTestRunner(*args, **kwargs):
     kwargs['env'] = env = dict(os.environ)
     # note, we do NOT want to set NO_EM_RESTART or jsbridge wouldn't work
     # avoid dialogs on windows
     if 'NO_EM_RESTART' in env:
         del env['NO_EM_RESTART']
     if 'XPCOM_DEBUG_BREAK' not in env:
         env['XPCOM_DEBUG_BREAK'] = 'stack'
     # do not reuse an existing instance
     env['MOZ_NO_REMOTE'] = '1'
 
     return mozrunner.ThunderbirdRunner(*args, **kwargs)
 
+
 class ThunderTestMozmill(mozmill.MozMill):
     VNC_SERVER_PATH = '/usr/bin/vncserver'
     VNC_PASSWD_PATH = '~/.vnc/passwd'
 
     def __init__(self, *args, **kwargs):
         # Only use the VNC server if the capability is available and a password
         # is already defined so this can run without prompting the user.
         self.use_vnc_server = (
@@ -270,26 +267,26 @@ class ThunderTestMozmill(mozmill.MozMill
         USE_RICH_FAILURES = (os.environ.get('MOZMILL_RICH_FAILURES') == '1')
 
         super(ThunderTestMozmill, self).__init__(*args, **kwargs)
 
     def start(self, profile=None, runner=None):
         if not profile:
             profile = self.profile_class(addons=[jsbridge.extension_path, extension_path])
         self.profile = profile
-        
+
         if not runner:
-            runner = self.runner_class(profile=self.profile, 
+            runner = self.runner_class(profile=self.profile,
                                        cmdargs=["-jsbridge", str(self.jsbridge_port)])
         self.runner = runner
 
         if self.use_vnc_server:
             try:
                 subprocess.check_call([self.VNC_SERVER_PATH, ':99'])
-            except subprocess.CalledProcessError, ex:
+            except subprocess.CalledProcessError:
                 # Okay, so that display probably already exists.  We can either
                 # use it as-is or kill it.  I'm deciding we want to kill it
                 # since there might be other processes alive in there that
                 # want to make trouble for us.
                 subprocess.check_call([self.VNC_SERVER_PATH, '-kill', ':99'])
                 # Now let's try again.  if this didn't work, let's just let
                 # the exception kill us.
                 subprocess.check_call([self.VNC_SERVER_PATH, ':99'])
@@ -336,21 +333,23 @@ def monkeypatched_15_run_tests(self, tes
         frame.runTestFile(test)
     else:
         # run the test files
         for test_dir in self.test_dirs:
             frame.runTestDirectory(test_dir)
 
     # Give a second for any callbacks to finish.
     sleep(1)
+
 if hasattr(mozmill.MozMill, 'find_tests'):
     # Monkey-patch run_tests
     mozmill.MozMill.old_run_tests = mozmill.MozMill.run_tests
     mozmill.MozMill.run_tests = monkeypatched_15_run_tests
 
+
 class ThunderTestCLI(mozmill.CLI):
     mozmill_class = ThunderTestMozmill
 
     def add_options(self, parser):
         mozmill.CLI.add_options(self, parser)
         parser.add_option('--symbols-path', default=None, dest="symbols",
                           help="The path to the symbol files from build_symbols")
         parser.add_option('--plugins-path', default=None, dest="plugins",
@@ -387,17 +386,17 @@ class ThunderTestCLI(mozmill.CLI):
             test_paths = self.options.test
         TEST_NAME = ', '.join([os.path.basename(t) for t in test_paths])
 
         test_dirs = self.test_dirs = []
         for test_file in test_paths:
             test_file = os.path.abspath(test_file)
             if not os.path.isdir(test_file):
                 test_file = os.path.dirname(test_file)
-            if not test_file in test_dirs:
+            if test_file not in test_dirs:
                 test_dirs.append(test_file)
 
         # if we are monkeypatching, give it the test directories.
         if hasattr(self.mozmill, 'find_tests'):
             self.mozmill.test_dirs = test_dirs
 
         self._load_wrapper()
 
@@ -419,16 +418,18 @@ class ThunderTestCLI(mozmill.CLI):
             try:
                 wrapper = imp.load_module(WRAPPER_MODULE_NAME, fd, path, desc)
             finally:
                 if fd is not None:
                     fd.close()
 
 
 TEST_RESULTS = []
+
+
 # Versions of MozMill prior to 1.5 did not output test-pass /
 # TEST-UNEXPECTED-FAIL. Since 1.5 happened this gets output, so we only want
 # a summary at the end to make it easy for developers.
 def logEndTest(obj):
     # If we've got a string here, we know we're later than 1.5, and we can just
     # display a summary at the end as 1.5 will do TEST-UNEXPECTED-FAIL for us.
     if isinstance(obj, str):
         obj = json.loads(obj)
@@ -436,16 +437,18 @@ def logEndTest(obj):
     TEST_RESULTS.append(obj)
 mozmill.LoggerListener.cases['mozmill.endTest'] = logEndTest
 
 # We now send extended meta-data about failures.  We do not want the entire
 # message dumped with this extra data, so clobber the default mozmill.fail
 # with one that wraps it and only tells it the exception message rather than
 # the whole JSON blob.
 ORIGINAL_FAILURE_LOGGER = mozmill.LoggerListener.cases['mozmill.fail']
+
+
 def logFailure(obj):
     if isinstance(obj, basestring):
         obj = json.loads(obj)
     if 'exception' in obj:
         objex = obj['exception']
         if 'message' in objex:
             report_as = objex['message']
         else:
@@ -455,102 +458,102 @@ def logFailure(obj):
     ORIGINAL_FAILURE_LOGGER(report_as)
 mozmill.LoggerListener.cases['mozmill.fail'] = logFailure
 
 
 def prettifyFilename(path, tail_segs_desired=1):
     parts = path.split('/')
     return '/'.join(parts[-tail_segs_desired:])
 
+
 def prettyPrintException(e):
     print '  EXCEPTION:', e.get('message', 'no message!').encode('utf-8')
     print '    at:', prettifyFilename(e.get('fileName', 'nonesuch')), 'line', e.get('lineNumber', 0)
     if 'stack' in e:
         for line in e['stack'].splitlines():
             if not line:
                 continue
             if line[0] == "(":
                 funcname = None
             elif line[0] == "@":
                 # this is probably the root, don't care
                 continue
             else:
                 funcname = line[:line.find('@')]
-            pathAndLine = line[line.rfind('@')+1:]
+            pathAndLine = line[line.rfind('@') + 1:]
             rcolon = pathAndLine.rfind(':')
             if rcolon != -1:
                 path = pathAndLine[:rcolon]
-                line = pathAndLine[rcolon+1:]
+                line = pathAndLine[rcolon + 1:]
             else:
                 path = pathAndLine
                 line = 0
             if funcname:
                 print '      ', funcname, prettifyFilename(path), line
             else:
                 print '           ', prettifyFilename(path), line
 
 
 # Tests that are useless and shouldn't be printed if successful
 TEST_BLACKLIST = ["setupModule", "setupTest", "teardownTest", "teardownModule"]
 
-import pprint, atexit
+
 @atexit.register
 def prettyPrintResults():
     for result in TEST_RESULTS:
-        #pprint.pprint(result)
         testOrSummary = 'TEST'
         if 'summary' in result:
             testOrSummary = 'SUMMARY'
         if len(result['fails']) == 0:
             if result.get('skipped', False):
                 kind = 'SKIP'
             else:
                 kind = 'PASS'
             if result['name'] not in TEST_BLACKLIST:
                 print '%s-%s | %s' % (testOrSummary, kind, result['name'])
         else:
-            print '%s-UNEXPECTED-FAIL | %s | %s' % (testOrSummary, prettifyFilename(result['filename']), result['name'])
+            print '%s-UNEXPECTED-FAIL | %s | %s' % \
+                (testOrSummary, prettifyFilename(result['filename']), result['name'])
         for failure in result['fails']:
             if 'exception' in failure:
                 prettyPrintException(failure['exception'])
 
+
 @atexit.register
 def dumpRichResults():
     if USE_RICH_FAILURES:
         print '##### MOZMILL-RICH-FAILURES-BEGIN #####'
         for result in TEST_RESULTS:
             if len(result['fails']) > 0:
                 for failure in result['fails']:
                     failure['fileName'] = prettifyFilename(result['filename'], 2)
                     failure['testName'] = result['name']
                     print json.dumps(failure)
         print '##### MOZMILL-RICH-FAILURES-END #####'
 
+
 @atexit.register
 def uploadScreenshots():
     if not UPLOAD_SCREENSHOTS:
         return
 
     parent_dir = os.environ.get('MOZ_UPLOAD_DIR', None)
     if not parent_dir:
         return
 
-    from base64 import b64decode
-    from os import fdopen
-    from tempfile import mkstemp
-
     for result in TEST_RESULTS:
         for failure in result['fails']:
             for win in failure['failureContext']['windows']['windows']:
                 prefix = result['name'].replace(':', '_')
-                fp, path = mkstemp(prefix=prefix + '-', suffix='.png', dir=parent_dir)
-                with fdopen(fp, 'wb') as shot:
+                fp, path = tempfile.mkstemp(prefix=prefix + '-', suffix='.png', dir=parent_dir)
+                with os.fdopen(fp, 'wb') as shot:
                     shot.write(b64decode(win['screenshotDataUrl'][len('data:image/png;base64,'):]))
                 print 'Wrote screenshot to', path
 
+
 def checkCrashesAtExit():
     if mozcrash.check_for_crashes(dump_directory=os.path.join(PROFILE_DIR, 'minidumps'),
                                   symbols_path=SYMBOLS_PATH,
                                   test_name=TEST_NAME):
         print >> sys.stderr, 'TinderboxPrint: ' + TEST_NAME + '<br/><em class="testfail">CRASH</em>'
         sys.exit(1)
 
 if __name__ == '__main__':
--- a/mail/test/mozmill/runtestlist.py
+++ b/mail/test/mozmill/runtestlist.py
@@ -7,61 +7,62 @@ import glob
 import optparse
 import sys
 import os
 import subprocess
 import logging
 
 SCRIPT_DIRECTORY = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
 
+
 class RunTestListOptions(optparse.OptionParser):
     """Parsed run test list command line options."""
     def __init__(self, **kwargs):
         optparse.OptionParser.__init__(self, **kwargs)
         defaults = {}
 
         self.add_option("--binary",
-                        action = "store", type = "string", dest = "binary",
-                        help = "Binary to be run")
+                        action="store", type="string", dest="binary",
+                        help="Binary to be run")
         defaults["binary"] = ""
 
         self.add_option("--list",
-                        action = "store", type = "string", dest = "list",
-                        help = "List of tests to be run")
+                        action="store", type="string", dest="list",
+                        help="List of tests to be run")
         defaults["list"] = ""
 
         self.add_option("--dir",
-                        action = "store", type = "string", dest = "dir",
-                        help = "Directory of the tests, leave blank for current directory")
+                        action="store", type="string", dest="dir",
+                        help="Directory of the tests, leave blank for current directory")
         defaults["dir"] = ""
 
         self.add_option("--symbols-path",
-                        action = "store", type = "string", dest = "symbols",
-                        help = "The path to the symbol files from build_symbols")
+                        action="store", type="string", dest="symbols",
+                        help="The path to the symbol files from build_symbols")
         defaults["symbols"] = ""
 
         self.add_option("--total-chunks",
-                        action = "store", type = "int", dest = "total_chunks",
+                        action="store", type="int", dest="total_chunks",
                         help="how many chunks to split the tests up into")
         defaults["total_chunks"] = 1
         self.add_option("--this-chunk",
-                        action = "store", type = "int", dest = "this_chunk",
+                        action="store", type="int", dest="this_chunk",
                         help="which chunk to run between 1 and --total-chunks")
         defaults["this_chunk"] = 1
 
         self.add_option("--plugins-path",
-                        action = "store", type = "string", dest = "plugins",
-                        help = "The path to the plugins folder for the test profiles")
+                        action="store", type="string", dest="plugins",
+                        help="The path to the plugins folder for the test profiles")
 
         self.add_option("--testing-modules-dir",
                         action="store", type="string", dest="testingmodules",
                         help="The path to the testing modules directory")
         defaults["testingmodules"] = ""
 
-        self.set_defaults(**defaults);
+        self.set_defaults(**defaults)
 
         usage = """\
 Usage instructions for runtestlist.py
 """
         self.set_usage(usage)
 
 log = logging.getLogger()
 handler = logging.StreamHandler(sys.stdout)
@@ -121,17 +122,17 @@ for directory in tests:
 
     if options.testingmodules:
         args.append("--testing-modules-dir")
         args.append(os.path.abspath(options.testingmodules))
 
     print args
     outputPipe = subprocess.PIPE
 
-    proc = subprocess.Popen(args, cwd=SCRIPT_DIRECTORY, stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
+    proc = subprocess.Popen(args, cwd=SCRIPT_DIRECTORY, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
 
     testErrors = 0
     testPasses = 0
 
     line = proc.stdout.readline()
     while line != "":
         log.info(line.rstrip())
         if line.find("TEST-UNEXPECTED-") != -1:
--- a/mail/test/mozmill/startup-firstrun/wrapper.py
+++ b/mail/test/mozmill/startup-firstrun/wrapper.py
@@ -3,23 +3,23 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # For these tests, we need to disable the account provisioner, or
 # else it will spawn immediately and block before we have a chance to run
 # any Mozmill tests.
 
 import os
 import shutil
-import sys
 
 # We don't want any accounts for these tests.
 NO_ACCOUNTS = True
 # Do not force enable main menu bar (keep the default).
 DEFAULT_MENUBAR = True
 
+
 def on_profile_created(profiledir):
     """
     On profile creation, this copies *-prefs.js from the current folder to
     profile_dir as a user.js file. These user prefs is interpreted in addition
     to the standard prefs.js file.
     """
     # The pref file is in the same directory this script is in
     preffile = os.path.join(os.path.dirname(__file__), "prefs.js")
--- a/mail/test/resources/installmozmill.py
+++ b/mail/test/resources/installmozmill.py
@@ -9,18 +9,18 @@ install mozmill and its dependencies
 """
 
 import os
 import sys
 from subprocess import call
 
 import buildconfig
 
+# utility functions for cross-platform
 
-# utility functions for cross-platform
 
 def is_windows():
     return sys.platform.startswith('win')
 
 
 def esc(path):
     """quote and escape a path for cross-platform use"""
     return '"%s"' % repr(path)[1:-1]
@@ -44,17 +44,16 @@ def python_script_path(virtual_env, scri
 def entry_point_path(virtual_env, entry_point):
     path = os.path.join(scripts_path(virtual_env), entry_point)
     if is_windows():
         path += '.exe'
     return path
 
 
 # command-line entry point
-
 def main(args=None):
     """command line front-end function"""
 
     # parse command line arguments
     args = args or sys.argv[1:]
 
     # Print the python version
     print 'Python: %s' % sys.version
--- a/mail/test/resources/jsbridge/jsbridge/__init__.py
+++ b/mail/test/resources/jsbridge/jsbridge/__init__.py
@@ -1,179 +1,184 @@
 # ***** BEGIN LICENSE BLOCK *****
 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
-# 
+#
 # The contents of this file are subject to the Mozilla Public License Version
 # 1.1 (the "License"); you may not use this file except in compliance with
 # the License. You may obtain a copy of the License at
 # http://www.mozilla.org/MPL/
-# 
+#
 # Software distributed under the License is distributed on an "AS IS" basis,
 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 # for the specific language governing rights and limitations under the
 # License.
-# 
+#
 # The Original Code is Mozilla Corporation Code.
-# 
+#
 # The Initial Developer of the Original Code is
 # Mikeal Rogers.
 # Portions created by the Initial Developer are Copyright (C) 2008 -2009
 # the Initial Developer. All Rights Reserved.
-# 
+#
 # Contributor(s):
 #  Mikeal Rogers <mikeal.rogers@gmail.com>
 #  Henrik Skupin <hskupin@mozilla.com>
-# 
+#
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 # in which case the provisions of the GPL or the LGPL are applicable instead
 # of those above. If you wish to allow use of your version of this file only
 # under the terms of either the GPL or the LGPL, and not to allow others to
 # use your version of this file under the terms of the MPL, indicate your
 # decision by deleting the provisions above and replace them with the notice
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
-# 
+#
 # ***** END LICENSE BLOCK *****
 
 import socket
 import os
-import copy
 import asyncore
 
 from time import sleep
-from network import Bridge, BackChannel, create_network
+from network import create_network
 from jsobjects import JSObject
 
 import mozrunner
 
 settings_env = 'JSBRIDGE_SETTINGS_FILE'
 
 parent = os.path.abspath(os.path.dirname(__file__))
 extension_path = os.path.join(parent, 'extension')
 
 window_string = "Cc['@mozilla.org/appshell/window-mediator;1'].getService(Ci.nsIWindowMediator).getMostRecentWindow('')"
 
 wait_to_create_timeout = 60
 
+
 def wait_and_create_network(host, port, timeout=wait_to_create_timeout):
     ttl = 0
     while ttl < timeout:
         try:
             s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
             s.connect((host, port))
             s.close()
             break
         except socket.error:
             pass
         sleep(.25)
         ttl += .25
     if ttl == timeout:
         raise Exception("Sorry, cannot connect to jsbridge extension, port %s" % port)
-    
+
     back_channel, bridge = create_network(host, port)
     sleep(.5)
-    
+
     while back_channel.registered is False:
         back_channel.close()
         bridge.close()
         asyncore.socket_map = {}
         sleep(1)
         back_channel, bridge = create_network(host, port)
-    
+
     return back_channel, bridge
 
+
 class CLI(mozrunner.CLI):
     """Command line interface."""
-    
+
     module = "jsbridge"
 
     def add_options(self, parser):
         """add options to parser"""
 
         mozrunner.CLI.add_options(self, parser)
-        parser.add_option('-D', '--debug', dest="debug", 
+        parser.add_option('-D', '--debug', dest="debug",
                           action="store_true",
-                          help="Debug mode", 
+                          help="Debug mode",
                           metavar="JSBRIDGE_DEBUG",
                           default=False)
-        parser.add_option('-s', '--shell', dest="shell", 
+        parser.add_option('-s', '--shell', dest="shell",
                           action="store_true",
                           help="Start a Python shell",
                           metavar="JSBRIDGE_SHELL",
                           default=False)
         parser.add_option('-u', '--usecode', dest="usecode",
                           action="store_true",
                           help="Use code module instead of iPython",
                           default=False)
         parser.add_option('-P', '--port', dest="port", default="24242",
                           help="TCP port to run jsbridge on.")
 
     def profile_args(self):
         profargs = super(CLI, self).profile_args()
         # Bug 1103551: re-enable these preferences.
-        #if self.options.debug:
+        # if self.options.debug:
         #    profargs.setdefault('prefences', []).update({
         #      'extensions.checkCompatibility':False,
         #      'devtools.errorconsole.enabled':True,
         #      'javascript.options.strict': True
         #    })
         profargs.setdefault('addons', []).append(extension_path)
         return profargs
 
     def command_args(self):
         args = super(CLI, self).command_args()
         if self.options.debug:
             args.append('-jsconsole')
-        if not 'jsbridge' in args:
+        if 'jsbridge' not in args:
             args += ['-jsbridge', self.options.port]
         return args
-        
+
     def run(self):
         runner = self.create_runner()
         runner.start()
         self.start_jsbridge_network()
         if self.options.shell:
             self.start_shell(runner)
         else:
             try:
                 runner.wait()
             except KeyboardInterrupt:
                 runner.stop()
-                
+
         runner.profile.cleanup()
-    
+
     def start_shell(self, runner):
         try:
             import IPython
         except:
             IPython = None
         if not hasattr(self, 'bridge'):
             self.start_jsbridge_network()
         jsobj = JSObject(self.bridge, window_string)
-        
+
         if IPython is None or self.options.usecode:
             import code
-            code.interact(local={"jsobj":jsobj, 
-                                 "getBrowserWindow":lambda : getBrowserWindow(self.bridge),
-                                 "back_channel":self.back_channel,
-                                 })
+            code.interact(local={
+                "jsobj": jsobj,
+                "getBrowserWindow": lambda: getBrowserWindow(self.bridge),
+                "back_channel": self.back_channel,
+            })
         else:
             from IPython.Shell import IPShellEmbed
             ipshell = IPShellEmbed([])
-            ipshell(local_ns={"jsobj":jsobj, 
-                              "getBrowserWindow":lambda : getBrowserWindow(self.bridge),
-                              "back_channel":self.back_channel,
-                              })
+            ipshell(local_ns={
+                "jsobj": jsobj,
+                "getBrowserWindow": lambda: getBrowserWindow(self.bridge),
+                "back_channel": self.back_channel,
+            })
         runner.stop()
-        
+
     def start_jsbridge_network(self, timeout=10):
         port = int(self.options.port)
         host = '127.0.0.1'
         self.back_channel, self.bridge = wait_and_create_network(host, port, timeout)
 
+
 def cli():
     CLI().run()
 
+
 def getBrowserWindow(bridge):
     return JSObject(bridge, "Cc['@mozilla.org/appshell/window-mediator;1'].getService(Ci.nsIWindowMediator).getMostRecentWindow('')")
--- a/mail/test/resources/jsbridge/jsbridge/jsobjects.py
+++ b/mail/test/resources/jsbridge/jsbridge/jsobjects.py
@@ -30,138 +30,149 @@
 # use your version of this file under the terms of the MPL, indicate your
 # decision by deleting the provisions above and replace them with the notice
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
+
 def init_jsobject(cls, bridge, name, value, description=None):
     """Initialize a js object that is a subclassed base type; int, str, unicode, float."""
     obj = cls(value)
     obj._bridge_ = bridge
     obj._name_ = name
     obj._description_ = description
     return obj
 
+
 def create_jsobject(bridge, fullname, value=None, obj_type=None, override_set=False):
     """Create a single JSObject for named object on other side of the bridge.
-    
+
     Handles various initization cases for different JSObjects."""
     description = bridge.describe(fullname)
     obj_type = description['type']
     value = description.get('data', None)
-    
+
     if value is True or value is False:
         return value
 
     if js_type_cases.has_key(obj_type):
         cls, needs_init = js_type_cases[obj_type]
         # Objects that requires initialization are base types that have "values".
         if needs_init:
             obj = init_jsobject(cls, bridge, fullname, value, description=description)
         else:
             obj = cls(bridge, fullname, description=description, override_set=override_set)
         return obj
     else:
         # Something very bad happened, we don't have a representation for the given type.
-        raise TypeError("Don't have a JSObject for javascript type "+obj_type)
-    
+        raise TypeError("Don't have a JSObject for javascript type " + obj_type)
+
+
 class JSObject(object):
     """Base javascript object representation."""
     _loaded_ = False
-    
+
     def __init__(self, bridge, name, override_set=False, description=None, *args, **kwargs):
         self._bridge_ = bridge
         if not override_set:
             name = bridge.set(name)['data']
         self._name_ = name
         self._description_ = description
-    
+
     def __jsget__(self, name):
         """Abstraction for final step in get events; __getitem__ and __getattr__.
         """
         result = create_jsobject(self._bridge_, name, override_set=True)
         return result
-    
+
     def __getattr__(self, name):
-        """Get the object from jsbridge. 
-        
+        """Get the object from jsbridge.
+
         Handles lazy loading of all attributes of self."""
         # A little hack so that ipython returns all the names.
         if name == '_getAttributeNames':
-            return lambda : self._bridge_.describe(self._name_)['attributes']
-            
+            return lambda: self._bridge_.describe(self._name_)['attributes']
+
         attributes = self._bridge_.describe(self._name_)['attributes']
         if name in attributes:
-            return self.__jsget__(self._name_+'["'+name+'"]')
+            return self.__jsget__(self._name_ + '["' + name + '"]')
         else:
-            raise AttributeError(name+" is undefined.")
-    
+            raise AttributeError(name + " is undefined.")
+
     __getitem__ = __getattr__
-        
+
     def __setattr__(self, name, value):
         """Set the given JSObject as an attribute of this JSObject and make proper javascript
         assignment on the other side of the bridge."""
         if name.startswith('_') and name.endswith('_'):
             return object.__setattr__(self, name, value)
 
         response = self._bridge_.setAttribute(self._name_, name, value)
         object.__setattr__(self, name, create_jsobject(self._bridge_, response['data'], override_set=True))
-    
+
     __setitem__ = __setattr__
 
+
 class JSFunction(JSObject):
     """Javascript function represenation.
-    
-    Returns a JSObject instance for the serialized js type with 
-    name set to the full javascript call for this function. 
-    """    
-    
+
+    Returns a JSObject instance for the serialized js type with
+    name set to the full javascript call for this function.
+    """
+
     def __init__(self, bridge, name, override_set=False, description=None, *args, **kwargs):
         self._bridge_ = bridge
         self._name_ = name
         self._description_ = description
-    
+
     def __call__(self, *args):
         response = self._bridge_.execFunction(self._name_, args)
         if response['data'] is not None:
             return create_jsobject(self._bridge_, response['data'], override_set=True)
 
 
 class JSString(JSObject, unicode):
     "Javascript string representation."
     __init__ = unicode.__init__
 
-class JSInt(JSObject, int): 
+
+class JSInt(JSObject, int):
     """Javascript number representation for Python int."""
     __init__ = int.__init__
 
+
 class JSFloat(JSObject, float):
     """Javascript number representation for Python float."""
     __init__ = float.__init__
-    
+
+
 class JSUndefined(JSObject):
-    """Javascript undefined representation."""    
-    __str__ = lambda self : "undefined"
+    """Javascript undefined representation."""
+    def __str__(self):
+        return "undefined"
 
     def __cmp__(self, other):
         if isinstance(other, JSUndefined):
             return True
         else:
             return False
 
-    __nonzero__ = lambda self: False
+    def __nonzero__(self):
+        return False
 
-js_type_cases = {'function'  :(JSFunction, False,), 
-                  'object'   :(JSObject, False,), 
-                  'array'    :(JSObject, False,),
-                  'string'   :(JSString, True,), 
-                  'number'   :(JSFloat, True,),
-                  'undefined':(JSUndefined, False,),
-                  'null'     :(JSObject, False,),
-                  }
-py_type_cases = {unicode  :JSString,
-                  str     :JSString,
-                  int     :JSInt,
-                  float   :JSFloat,
-                  }
+js_type_cases = {
+    'function': (JSFunction, False,),
+    'object': (JSObject, False,),
+    'array': (JSObject, False,),
+    'string': (JSString, True,),
+    'number': (JSFloat, True,),
+    'undefined': (JSUndefined, False,),
+    'null': (JSObject, False,),
+}
+py_type_cases = {
+    unicode: JSString,
+    str: JSString,
+    int: JSInt,
+    float: JSFloat,
+}
--- a/mail/test/resources/jsbridge/jsbridge/network.py
+++ b/mail/test/resources/jsbridge/jsbridge/network.py
@@ -43,17 +43,20 @@ import uuid
 from time import sleep
 from threading import Thread
 
 import json
 from json.encoder import encode_basestring_ascii, encode_basestring
 
 logger = logging.getLogger(__name__)
 
-class JavaScriptException(Exception): pass
+
+class JavaScriptException(Exception):
+    pass
+
 
 class Telnet(asyncore.dispatcher):
     def __init__(self, host, port):
         self.host, self.port = host, port
         asyncore.dispatcher.__init__(self)
         self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
         self.connect((host, port))
         self.buffer = ''
@@ -61,45 +64,46 @@ class Telnet(asyncore.dispatcher):
 
     def __del__(self):
         self.close()
 
     def handle_close(self):
         """override method of asyncore.dispatcher"""
         self.close()
 
-    def handle_expt(self): 
-        self.close() # connection failed, shutdown
-    
+    def handle_expt(self):
+        self.close()  # connection failed, shutdown
+
     def writable(self):
         return (len(self.buffer) > 0)
 
     def handle_write(self):
         sent = self.send(self.buffer)
         self.buffer = self.buffer[sent:]
-        
+
     def read_all(self):
         import socket
         data = ''
         while self.connected:
             try:
                 data += self.recv(4096)
             except socket.error:
                 break
         return data
 
     def handle_read(self):
         self.data = self.read_all()
         self.process_read(self.data)
 
-        
-    read_callback = lambda self, data: None
+    def read_callback(self, data):
+        return None
 
 decoder = json.JSONDecoder()
 
+
 class JSObjectEncoder(json.JSONEncoder):
     """Encoder that supports jsobject references by name."""
 
     def encode(self, o):
         import jsobjects
         if isinstance(o, jsobjects.JSObject):
             return o._name_
         else:
@@ -143,163 +147,166 @@ class JSObjectEncoder(json.JSONEncoder):
                 markers[markerid] = o
             for chunk in self._iterencode_default(o, markers):
                 yield chunk
             if markers is not None:
                 del markers[markerid]
 
 encoder = JSObjectEncoder()
 
-class JSBridgeDisconnectError(Exception): 
+
+class JSBridgeDisconnectError(Exception):
     """exception raised when an unexpected disconect happens"""
 
 
 class Bridge(Telnet):
-    
+
     trashes = []
     reading = False
     sbuffer = ''
     events_list = []
 
     callbacks = {}
-        
+
     bridge_type = "bridge"
-    
+
     registered = False
-    timeout_ctr = 0. # global timeout counter
-    
+    timeout_ctr = 0.  # global timeout counter
+
     def __init__(self, host, port, timeout=60.):
         """
         - timeout : failsafe timeout for each call to run in seconds
         """
         self.timeout = timeout
         Telnet.__init__(self, host, port)
         sleep(.1)
 
         # XXX we've actually already connected in Telnet
         self.connect((host, port))
-    
+
     def handle_connect(self):
         self.register()
 
     def run(self, _uuid, exec_string, interval=.2, raise_exeption=True):
-
-
         exec_string += '\r\n'
         self.send(exec_string)
 
         while _uuid not in self.callbacks.keys():
 
             Bridge.timeout_ctr += interval
             if Bridge.timeout_ctr > self.timeout:
                 print 'Timeout: %s' % exec_string
                 raise JSBridgeDisconnectError("Connection timed out")
-            
+
             sleep(interval)
             try:
                 self.send('')
             except socket.error, e:
                 raise JSBridgeDisconnectError("JSBridge Socket Disconnected: %s" % e)
 
-        Bridge.timeout_ctr = 0. # reset the counter
-        
+        Bridge.timeout_ctr = 0.  # reset the counter
+
         callback = self.callbacks.pop(_uuid)
         if callback['result'] is False and raise_exeption is True:
             raise JavaScriptException(callback['exception'])
-        return callback 
-        
+        return callback
+
     def register(self):
         _uuid = str(uuid.uuid1())
-        self.send('bridge.register("'+_uuid+'", "'+self.bridge_type+'")\r\n')
+        self.send('bridge.register("' + _uuid + '", "' + self.bridge_type + '")\r\n')
         self.registered = True
 
     def execFunction(self, func_name, args, interval=.25):
         _uuid = str(uuid.uuid1())
         exec_args = [encoder.encode(_uuid), func_name, encoder.encode(args)]
-        return self.run(_uuid, 'bridge.execFunction('+ ', '.join(exec_args)+')', interval)
-        
+        return self.run(_uuid, 'bridge.execFunction(' + ', '.join(exec_args) + ')', interval)
+
     def setAttribute(self, obj_name, name, value):
         _uuid = str(uuid.uuid1())
         exec_args = [encoder.encode(_uuid), obj_name, encoder.encode(name), encoder.encode(value)]
-        return self.run(_uuid, 'bridge.setAttribute('+', '.join(exec_args)+')')
-        
+        return self.run(_uuid, 'bridge.setAttribute(' + ', '.join(exec_args) + ')')
+
     def set(self, obj_name):
         _uuid = str(uuid.uuid1())
-        return self.run(_uuid, 'bridge.set('+', '.join([encoder.encode(_uuid), obj_name])+')')
-        
+        return self.run(_uuid, 'bridge.set(' + ', '.join([encoder.encode(_uuid), obj_name]) + ')')
+
     def describe(self, obj_name):
         _uuid = str(uuid.uuid1())
-        return self.run(_uuid, 'bridge.describe('+', '.join([encoder.encode(_uuid), obj_name])+')')
-    
+        return self.run(_uuid, 'bridge.describe(' + ', '.join([encoder.encode(_uuid), obj_name]) + ')')
+
     def fire_callbacks(self, obj):
         self.callbacks[obj['uuid']] = obj
-    
+
     def process_read(self, data):
         """Parse out json objects and fire callbacks."""
         self.sbuffer += data
         self.reading = True
         self.parsing = True
         while self.parsing:
             # Remove erroneous data in front of callback object
             index = self.sbuffer.find('{')
             if index is not -1 and index is not 0:
                 self.sbuffer = self.sbuffer[index:]
-            # Try to get a json object from the data stream    
+            # Try to get a json object from the data stream
             try:
                 obj, index = decoder.raw_decode(self.sbuffer)
-            except Exception, e:
+            except Exception:
                 self.parsing = False
-            # If we got an object fire the callback infra    
+            # If we got an object fire the callback infra
             if self.parsing:
                 self.fire_callbacks(obj)
                 self.sbuffer = self.sbuffer[index:]
-        
+
+
 class BackChannel(Bridge):
-    
+
     bridge_type = "backchannel"
-    
+
     def __init__(self, host, port):
         Bridge.__init__(self, host, port)
         self.uuid_listener_index = {}
         self.event_listener_index = {}
         self.global_listeners = []
-        
+
     def fire_callbacks(self, obj):
         """Handle all callback fireing on json objects pulled from the data stream."""
         self.fire_event(**dict([(str(key), value,) for key, value in obj.items()]))
 
     def add_listener(self, callback, uuid=None, eventType=None):
         if uuid is not None:
             self.uuid_listener_index.setdefault(uuid, []).append(callback)
         if eventType is not None:
             self.event_listener_index.setdefault(eventType, []).append(callback)
 
     def add_global_listener(self, callback):
         self.global_listeners.append(callback)
 
     def fire_event(self, eventType=None, uuid=None, result=None, exception=None):
-        Bridge.timeout_ctr = 0. # reset the counter
+        Bridge.timeout_ctr = 0.  # reset the counter
         event = eventType
         if uuid is not None and self.uuid_listener_index.has_key(uuid):
             for callback in self.uuid_listener_index[uuid]:
                 callback(result)
         if event is not None and self.event_listener_index.has_key(event):
             for callback in self.event_listener_index[event]:
                 callback(result)
         for listener in self.global_listeners:
             listener(eventType, result)
 
 thread = None
- 
+
+
 def create_network(hostname, port):
     back_channel = BackChannel(hostname, port)
     bridge = Bridge(hostname, port)
     global thread
     if not thread or not thread.isAlive():
         def do():
-            try: asyncore.loop(use_poll=True)
-            except select.error:pass
-            
+            try:
+                asyncore.loop(use_poll=True)
+            except select.error:
+                pass
+
         thread = Thread(target=do)
-        getattr(thread, 'setDaemon', lambda x : None)(True)
+        getattr(thread, 'setDaemon', lambda x: None)(True)
         thread.start()
-    
+
     return back_channel, bridge
--- a/mail/test/resources/jsbridge/setup.py
+++ b/mail/test/resources/jsbridge/setup.py
@@ -30,17 +30,16 @@
 # use your version of this file under the terms of the MPL, indicate your
 # decision by deleting the provisions above and replace them with the notice
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
-import sys
 from setuptools import setup, find_packages
 
 desc = """Python to JavaScript bridge interface."""
 summ = """A powerful and extensible Python to JavaScript bridge interface."""
 
 PACKAGE_NAME = "jsbridge"
 # You must update the required version in mozmill's setup.py to match this.
 PACKAGE_VERSION = "2.4.15"
@@ -52,24 +51,27 @@ setup(name=PACKAGE_NAME,
       description=desc,
       long_description=summ,
       author='Mikeal Rogers, Mozilla',
       author_email='mikeal.rogers@gmail.com',
       url='http://github.com/mozautomation/mozmill',
       license='http://www.apache.org/licenses/LICENSE-2.0',
       packages=find_packages(exclude=['test']),
       include_package_data=True,
-      package_data = {'': ['*.js', '*.css', '*.html', '*.txt', '*.xpi', '*.rdf', '*.xul', '*.jsm', '*.xml' 'extension'],},
+      package_data={
+          '': ['*.js', '*.css', '*.html', '*.txt', '*.xpi', '*.rdf', '*.xul', '*.jsm', '*.xml' 'extension'],
+      },
       zip_safe=False,
       entry_points="""
           [console_scripts]
           jsbridge = jsbridge:cli
         """,
-      platforms =['Any'],
-      install_requires = requires,
-      classifiers=['Development Status :: 4 - Beta',
-                   'Environment :: Console',
-                   'Intended Audience :: Developers',
-                   'License :: OSI Approved :: Apache Software License',
-                   'Operating System :: OS Independent',
-                   'Topic :: Software Development :: Libraries :: Python Modules',
-                  ]
-     )
+      platforms=['Any'],
+      install_requires=requires,
+      classifiers=[
+          'Development Status :: 4 - Beta',
+          'Environment :: Console',
+          'Intended Audience :: Developers',
+          'License :: OSI Approved :: Apache Software License',
+          'Operating System :: OS Independent',
+          'Topic :: Software Development :: Libraries :: Python Modules',
+      ]
+      )
--- a/mail/test/resources/mozmill/MANIFEST.in
+++ b/mail/test/resources/mozmill/MANIFEST.in
@@ -1,2 +1,1 @@
-recursive-include docs *
 recursive-include mozmill/extension *
--- a/mail/test/resources/mozmill/mozmill/__init__.py
+++ b/mail/test/resources/mozmill/mozmill/__init__.py
@@ -32,17 +32,16 @@
 # use your version of this file under the terms of the MPL, indicate your
 # decision by deleting the provisions above and replace them with the notice
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
-import copy
 import imp
 import os
 import socket
 import sys
 import traceback
 import urllib2
 
 from datetime import datetime, timedelta
@@ -50,53 +49,56 @@ import manifestparser
 
 try:
     import json
 except:
     import simplejson as json
 
 # setup logger
 import logging
-logger = logging.getLogger('mozmill')
 
 import jsbridge
 from jsbridge.network import JSBridgeDisconnectError
 import mozprofile
 import mozrunner
 
 from time import sleep
 
+logger = logging.getLogger('mozmill')
 basedir = os.path.abspath(os.path.dirname(__file__))
 
 extension_path = os.path.join(basedir, 'extension')
 
 mozmillModuleJs = "ChromeUtils.import('chrome://mozmill/content/modules/mozmill.js')"
 
 try:
     import pkg_resources
     version = pkg_resources.get_distribution('mozmill').version
 except:
     # pkg_resources not available
     version = None
 
+
 class LoggerListener(object):
     cases = {
-        'mozmill.pass':   lambda string: logger.info('Step Pass: ' + string),
-        'mozmill.fail':   lambda string: logger.error('Test Failure: ' + string),
-        'mozmill.skip':   lambda string: logger.info('Test Skipped: ' + string)
+        'mozmill.pass': lambda string: logger.info('Step Pass: ' + string),
+        'mozmill.fail': lambda string: logger.error('Test Failure: ' + string),
+        'mozmill.skip': lambda string: logger.info('Test Skipped: ' + string)
     }
-    
+
     class default(object):
-        def __init__(self, eName): self.eName = eName
+        def __init__(self, eName):
+            self.eName = eName
+
         def __call__(self, string):
             if string:
                 logger.debug(self.eName + ' | ' + string)
             else:
                 logger.debug(self.eName)
-    
+
     def __call__(self, eName, obj):
         if obj == {}:
             string = ''
         else:
             string = json.dumps(obj)
 
         if eName not in self.cases:
             self.cases[eName] = self.default(eName)
@@ -119,33 +121,35 @@ class MozMill(object):
 
     You should *NOT* vary from this order of execution.  If you have need to
     run different sets of tests, create a new instantiation of MozMill
     """
 
     report_type = 'mozmill-test'
 
     def __init__(self,
-                 runner_class=mozrunner.FirefoxRunner, 
+                 runner_class=mozrunner.FirefoxRunner,
                  profile_class=mozprofile.FirefoxProfile,
                  jsbridge_port=24242,
                  jsbridge_timeout=60):
         """
         - runner_class : which mozrunner class to use
         - profile_class : which class to use to generate application profiles
         - jsbridge_port : port jsbridge uses to connect to to the application
         - jsbridge_timeout : how long to go without jsbridge communication
         """
-        
+
         self.runner_class = runner_class
         self.profile_class = profile_class
         self.jsbridge_port = jsbridge_port
         self.jsbridge_timeout = jsbridge_timeout
 
-        self.passes = [] ; self.fails = [] ; self.skipped = []
+        self.passes = []
+        self.fails = []
+        self.skipped = []
         self.alltests = []
 
         self.persisted = {}
         self.endRunnerCalled = False
         self.shutdownModes = enum('default', 'user_shutdown', 'user_restart')
         self.currentShutdownMode = self.shutdownModes.default
         self.userShutdownEnabled = False
         self.tests = []
@@ -174,95 +178,100 @@ class MozMill(object):
     def persist_listener(self, obj):
         self.persisted = obj
 
     def fire_python_callback(self, method, arg, python_callbacks_module):
         meth = getattr(python_callbacks_module, method)
         try:
             meth(arg)
         except Exception, e:
-            self.endTest_listener({"name":method, "failed":1, 
-                                   "python_exception_type":e.__class__.__name__,
-                                   "python_exception_string":str(e),
-                                   "python_traceback":traceback.format_exc(),
-                                   "filename":python_callbacks_module.__file__})
+            self.endTest_listener({
+                "name": method,
+                "failed": 1,
+                "python_exception_type": e.__class__.__name__,
+                "python_exception_string": str(e),
+                "python_traceback": traceback.format_exc(),
+                "filename": python_callbacks_module.__file__
+            })
             return False
-        self.endTest_listener({"name":method, "failed":0, 
-                               "filename":python_callbacks_module.__file__})
+        self.endTest_listener({
+            "name": method,
+            "failed": 0,
+            "filename": python_callbacks_module.__file__
+        })
         return True
-    
+
     def firePythonCallback_listener(self, obj):
         callback_file = "%s_callbacks.py" % os.path.splitext(obj['filename'])[0]
         if os.path.isfile(callback_file):
             python_callbacks_module = imp.load_source('callbacks', callback_file)
         else:
             raise Exception("No valid callback file")
         self.fire_python_callback(obj['method'], obj['arg'], python_callbacks_module)
 
     def create_network(self):
 
         # get the bridge and the back-channel
         self.back_channel, self.bridge = jsbridge.wait_and_create_network("127.0.0.1",
                                                                           self.jsbridge_port)
 
         # set a timeout on jsbridge actions in order to ensure termination
         self.back_channel.timeout = self.bridge.timeout = self.jsbridge_timeout
-        
+
         # Assign listeners to the back channel
         for listener in self.listeners:
             self.back_channel.add_listener(listener[0], **listener[1])
         for global_listener in self.global_listeners:
             self.back_channel.add_global_listener(global_listener)
 
     def start(self, profile=None, runner=None):
 
         if not profile:
             profile = self.profile_class(addons=[jsbridge.extension_path, extension_path])
         self.profile = profile
-        
+
         if not runner:
-            runner = self.runner_class(profile=self.profile, 
+            runner = self.runner_class(profile=self.profile,
                                        cmdargs=["-jsbridge", str(self.jsbridge_port)])
 
         self.add_listener(self.firePythonCallback_listener, eventType='mozmill.firePythonCallback')
         self.runner = runner
         self.endRunnerCalled = False
-        
+
         self.runner.start()
         self.create_network()
         self.appinfo = self.get_appinfo(self.bridge)
 
         # set the starttime for the tests
         # XXX assumes run_tests will be called soon after (currently true)
         self.starttime = datetime.utcnow()
 
     def find_tests(self, tests, files=None):
         if files is None:
             files = []
         for test in tests:
 
             # tests have to be absolute paths to be loaded from JS
             test = os.path.abspath(test)
-            
+
             if os.path.isdir(test):
                 directory = test
                 for f in os.listdir(directory):
                     if not f.startswith('test'):
                         continue
                     path = os.path.join(directory, f)
                     if os.path.isdir(path):
                         self.find_tests([path], files)
                     else:
                         if f.endswith('.js') and path not in files:
                             files.append(path)
             else:
                 files.append(test)
         return files
 
-
     def run_tests(self, tests, sleeptime=0):
         """
         run test files or directories
         - test : test files or directories to run
         - sleeptime : initial time to sleep [s] (not sure why the default is 4)
         """
 
         tests = self.find_tests(tests)
@@ -299,56 +308,57 @@ class MozMill(object):
             print "TEST-UNEXPECTED-FAIL | %s | %s" % (test['filename'], test['name'])
             self.fails.append(test)
         else:
             print "TEST-PASS | %s | %s" % (test['filename'], test['name'])
             self.passes.append(test)
 
     def endRunner_listener(self, obj):
         self.endRunnerCalled = True
-        
+
     def userShutdown_listener(self, obj):
         if obj in [self.shutdownModes.default, self.shutdownModes.user_restart, self.shutdownModes.user_shutdown]:
             self.currentShutdownMode = obj
-        self.userShutdownEnabled = not self.userShutdownEnabled        
+        self.userShutdownEnabled = not self.userShutdownEnabled
 
-    ### methods for reporting
+    # Methods for reporting
 
     def printStats(self):
         """print pass/failed/skipped statistics"""
         print "INFO Passed: %d" % len(self.passes)
         print "INFO Failed: %d" % len(self.fails)
         print "INFO Skipped: %d" % len(self.skipped)
-        
+
     def report_disconnect(self):
         test = self.current_test
         test['passes'] = []
         test['fails'] = [{
-          'exception' : {
-            'message': 'Disconnect Error: Application unexpectedly closed'
-          }
+            'exception': {
+                'message': 'Disconnect Error: Application unexpectedly closed'
+            }
         }]
         test['passed'] = 0
         test['failed'] = 1
         self.alltests.append(test)
         self.fails.append(test)
 
     def get_appinfo(self, bridge):
         """ Collect application specific information """
 
         mozmill = jsbridge.JSObject(bridge, mozmillModuleJs)
         appInfo = mozmill.appInfo
 
-        results = {'application_id': str(appInfo.ID),
-                   'application_name': str(appInfo.name),
-                   'application_version': str(appInfo.version),
-                   'application_locale': str(mozmill.locale),
-                   'platform_buildid': str(appInfo.platformBuildID),
-                   'platform_version': str(appInfo.platformVersion),
-                  }
+        results = {
+            'application_id': str(appInfo.ID),
+            'application_name': str(appInfo.name),
+            'application_version': str(appInfo.version),
+            'application_locale': str(mozmill.locale),
+            'platform_buildid': str(appInfo.platformBuildID),
+            'platform_version': str(appInfo.platformVersion),
+        }
 
         return results
 
     def get_platform_information(self):
         """ Retrieves platform information for test reports. Parts of that code
             come from the dirtyharry application:
             http://github.com/harthur/dirtyharry/blob/master/dirtyutils.py """
 
@@ -358,19 +368,19 @@ class MozMill(object):
         (system, node, release, version, machine, processor) = platform.uname()
         (bits, linkage) = platform.architecture()
         service_pack = ''
 
         if system in ["Microsoft", "Windows"]:
             # There is a Python bug on Windows to determine platform values
             # http://bugs.python.org/issue7860
             if "PROCESSOR_ARCHITEW6432" in os.environ:
-              processor = os.environ.get("PROCESSOR_ARCHITEW6432", processor)
+                processor = os.environ.get("PROCESSOR_ARCHITEW6432", processor)
             else:
-              processor = os.environ.get('PROCESSOR_ARCHITECTURE', processor)
+                processor = os.environ.get('PROCESSOR_ARCHITECTURE', processor)
             system = os.environ.get("OS", system).replace('_', ' ')
             service_pack = os.sys.getwindowsversion()[4]
         elif system == "Linux":
             (distro, version, codename) = platform.dist()
             version = distro + " " + version
             if not processor:
                 processor = machine
         elif system == "Darwin":
@@ -384,60 +394,62 @@ class MozMill(object):
             elif bits == "64bit":
                 processor = "x86_64"
         elif processor == "AMD64":
             bits = "64bit"
             processor = "x86_64"
         elif processor == "Power Macintosh":
             processor = "ppc"
 
-        bits = re.search('(\d+)bit', bits).group(1)
+        bits = re.search(r'(\d+)bit', bits).group(1)
 
-        platform = {'hostname': node,
-                    'system': system,
-                    'version': version,
-                    'service_pack': service_pack,
-                    'processor': processor,
-                    'bits': bits
-                   }
+        platform = {
+            'hostname': node,
+            'system': system,
+            'version': version,
+            'service_pack': service_pack,
+            'processor': processor,
+            'bits': bits
+        }
 
         return platform
 
     def get_report(self):
         """get the report results"""
         format = "%Y-%m-%dT%H:%M:%SZ"
 
         assert self.tests, 'no tests have been run!'
         assert self.starttime, 'starttime not set; have you started the tests?'
         if not self.endtime:
             self.endtime = datetime.utcnow()
 
-        report = {'report_type': self.report_type,
-                  'mozmill_version': version,
-                  'time_start': self.starttime.strftime(format),
-                  'time_end': self.endtime.strftime(format),
-                  'time_upload': 'n/a',
-                  'tests_passed': len(self.passes),
-                  'tests_failed': len(self.fails),
-                  'tests_skipped': len(self.skipped),
-                  'results': self.alltests
-                 }
+        report = {
+            'report_type': self.report_type,
+            'mozmill_version': version,
+            'time_start': self.starttime.strftime(format),
+            'time_end': self.endtime.strftime(format),
+            'time_upload': 'n/a',
+            'tests_passed': len(self.passes),
+            'tests_failed': len(self.fails),
+            'tests_skipped': len(self.skipped),
+            'results': self.alltests
+        }
 
         report.update(self.appinfo)
         report.update(self.runner.get_repositoryInfo())
         report['system_info'] = self.get_platform_information()
 
         return report
 
     def send_report(self, results, report_url):
         """ Send a report of the results to a CouchdB instance or a file. """
 
         # report to file or stdout
         f = None
-        if report_url == 'stdout': # stdout
+        if report_url == 'stdout':  # stdout
             f = sys.stdout
         if report_url.startswith('file://'):
             filename = report_url.split('file://', 1)[1]
             try:
                 f = file(filename, 'w')
             except Exception, e:
                 print "Printing results to '%s' failed (%s)." % (filename, e)
                 return
@@ -471,40 +483,40 @@ class MozMill(object):
     def report(self, report_url):
         """print statistics and send the JSON report"""
         self.printStats()
 
         if report_url:
             results = self.get_report()
             return self.send_report(results, report_url)
 
-    ### methods for shutting down and cleanup
+    # Methods for shutting down and cleanup
 
     def stop_runner(self, timeout=30, close_bridge=False, hard=False):
         sleep(1)
         try:
             mozmill = jsbridge.JSObject(self.bridge, mozmillModuleJs)
             mozmill.cleanQuit()
         except (socket.error, JSBridgeDisconnectError):
             pass
         except:
             self.runner.cleanup()
             raise
-        
+
         if not close_bridge:
             starttime = datetime.utcnow()
             self.runner.wait(timeout=timeout)
             endtime = datetime.utcnow()
-            if ( endtime - starttime ) > timedelta(seconds=timeout):
+            if (endtime - starttime) > timedelta(seconds=timeout):
                 try:
                     self.runner.stop()
                 except:
                     pass
                 self.runner.wait()
-        else: # TODO: unify this logic with the above better
+        else:  # TODO: unify this logic with the above better
             if hard:
                 self.runner.cleanup()
                 return
 
             # XXX this call won't actually finish in the specified timeout time
             self.runner.wait(timeout=timeout)
 
             self.back_channel.close()
@@ -539,72 +551,72 @@ class MozMillRestart(MozMill):
         MozMill.__init__(self, *args, **kwargs)
         self.python_callbacks = []
 
     def add_listener(self, callback, **kwargs):
         self.listeners.append((callback, kwargs,))
 
     def add_global_listener(self, callback):
         self.global_listeners.append(callback)
-    
+
     def start(self, runner=None, profile=None):
-        
+
         if not profile:
             profile = self.profile_class(addons=[jsbridge.extension_path, extension_path])
         self.profile = profile
-        
+
         if not runner:
-            runner = self.runner_class(profile=self.profile, 
+            runner = self.runner_class(profile=self.profile,
                                        cmdargs=["-jsbridge", str(self.jsbridge_port)])
         self.runner = runner
         self.endRunnerCalled = False
         self.add_listener(self.firePythonCallback_listener, eventType='mozmill.firePythonCallback')
 
         # set the starttime for the tests
         # XXX assumes run_tests will be called soon after (currently true)
         self.starttime = datetime.utcnow()
-     
+
     def firePythonCallback_listener(self, obj):
         if obj['fire_now']:
             self.fire_python_callback(obj['method'], obj['arg'], self.python_callbacks_module)
         else:
             self.python_callbacks.append(obj)
-        
+
     def start_runner(self):
 
         # if user_restart we don't need to start the browser back up
         if self.currentShutdownMode != self.shutdownModes.user_restart:
             self.runner.start()
 
         self.create_network()
         self.appinfo = self.get_appinfo(self.bridge)
         frame = jsbridge.JSObject(self.bridge,
                                   "ChromeUtils.import('chrome://mozmill/content/modules/frame.js')")
         return frame
 
     def run_dir(self, test_dir, sleeptime=0):
         """run a directory of restart tests resetting the profile per directory"""
 
         # TODO:  document this behaviour!
-        if os.path.isfile(os.path.join(test_dir, 'testPre.js')):   
+        if os.path.isfile(os.path.join(test_dir, 'testPre.js')):
             pre_test = os.path.join(test_dir, 'testPre.js')
-            post_test = os.path.join(test_dir, 'testPost.js') 
+            post_test = os.path.join(test_dir, 'testPost.js')
             if not os.path.exists(pre_test) or not os.path.exists(post_test):
-                print "Skipping "+test_dir+" does not contain both pre and post test."
+                print "Skipping " + test_dir + " does not contain both pre and post test."
                 return
-            
+
             tests = [pre_test, post_test]
         else:
             if not os.path.isfile(os.path.join(test_dir, 'test1.js')):
-                print "Skipping "+test_dir+" does not contain any known test file names"
+                print "Skipping " + test_dir + " does not contain any known test file names"
                 return
             tests = []
             counter = 1
-            while os.path.isfile(os.path.join(test_dir, "test"+str(counter)+".js")):
-                tests.append(os.path.join(test_dir, "test"+str(counter)+".js"))
+            while os.path.isfile(os.path.join(test_dir, "test" + str(counter) + ".js")):
+                tests.append(os.path.join(test_dir, "test" + str(counter) + ".js"))
                 counter += 1
 
         self.add_listener(self.endRunner_listener, eventType='mozmill.endRunner')
 
         if os.path.isfile(os.path.join(test_dir, 'callbacks.py')):
             self.python_callbacks_module = imp.load_source('callbacks', os.path.join(test_dir, 'callbacks.py'))
 
         for test in tests:
@@ -615,89 +627,88 @@ class MozMillRestart(MozMill):
 
             frame.persisted = self.persisted
             try:
                 frame.runTestFile(test)
                 while not self.endRunnerCalled:
                     sleep(.25)
                 self.currentShutdownMode = self.shutdownModes.default
                 self.stop_runner()
-                sleep(2) # Give mozrunner some time to shutdown the browser
+                sleep(2)  # Give mozrunner some time to shutdown the browser
             except JSBridgeDisconnectError:
                 if not self.userShutdownEnabled:
                     raise JSBridgeDisconnectError()
             self.userShutdownEnabled = False
 
             for callback in self.python_callbacks:
                 self.fire_python_callback(callback['method'], callback['arg'], self.python_callbacks_module)
             self.python_callbacks = []
-        
-        self.python_callbacks_module = None    
-        
+
+        self.python_callbacks_module = None
+
         # Reset the profile.
         profile = self.runner.profile
         profile.cleanup()
         if profile.create_new:
-            profile.profile = profile.create_new_profile(self.runner.binary)                
+            profile.profile = profile.create_new_profile(self.runner.binary)
         for addon in profile.addons:
             profile.install_addon(addon)
         if jsbridge.extension_path not in profile.addons:
             profile.install_addon(jsbridge.extension_path)
         if extension_path not in profile.addons:
             profile.install_addon(extension_path)
         profile.set_preferences(profile.preferences)
 
     def find_tests(self, tests):
         files = []
 
         # make sure these are all directories
-        not_dir = [ i for i in tests
-                    if not os.path.isdir(i) ]
+        not_dir = [i for i in tests if not os.path.isdir(i)]
         if not_dir:
             raise IOError('Restart tests must be directories (%s)' % ', '.join(not_dir))
 
         for test_dir in tests:
 
             # tests have to be absolute paths, for some reason
             test_dir = os.path.abspath(test_dir)
 
             # XXX this allows for only one sub-level of test directories
             # is this a spec or a side-effect?
             # If the former, it should be documented
             test_dirs = [os.path.join(test_dir, d)
-                         for d in os.listdir(test_dir) 
+                         for d in os.listdir(test_dir)
                          if d.startswith('test') and os.path.isdir(os.path.join(test_dir, d))]
 
             if len(test_dirs):
                 files.extend(test_dirs)
             else:
                 files.append(test_dir)
 
         return files
-    
+
     def run_tests(self, tests, sleeptime=0):
 
         test_dirs = self.find_tests(tests)
         self.tests.extend(test_dirs)
 
         for test_dir in test_dirs:
             self.run_dir(test_dir, sleeptime)
 
         # cleanup the profile
         self.runner.cleanup()
 
         # Give a second for any pending callbacks to finish
-        sleep(1) 
+        sleep(1)
 
     def stop(self, fatal=False):
         """MozmillRestart doesn't need to do cleanup as this is already done per directory"""
 
         # XXX this is a one-off to fix bug 581733
         # really, the entire shutdown sequence should be reconsidered and
-        # made more robust. 
+        # made more robust.
         # See https://bugzilla.mozilla.org/show_bug.cgi?id=581733#c20
         # This will *NOT* work with all edge cases and it shouldn't be
         # expected that adding on more kills() for each edge case will ever
         # be able to fix a systematic issue by patching holes
         if fatal:
             self.runner.cleanup()
 
 
@@ -709,17 +720,17 @@ class CLI(jsbridge.CLI):
         jsbridge.CLI.add_options(self, parser)
         parser.add_option("-t", "--test", dest="test", action='append',
                           default=[],
                           help="Run test")
         parser.add_option("-l", "--logfile", dest="logfile",
                           default=None,
                           help="Log all events to file.")
         parser.add_option("--show-errors", dest="showerrors",
-                          default=False, 
+                          default=False,
                           action="store_true",
                           help="Print logger errors to the console.")
         parser.add_option("--report", dest="report", default=False,
                           help="Report the results. Requires url to results server. Use 'stdout' for stdout.")
         parser.add_option("--show-all", dest="showall", default=False,
                           action="store_true",
                           help="Show all test output.")
         parser.add_option("--timeout", dest="timeout", type="float",
@@ -736,26 +747,28 @@ class CLI(jsbridge.CLI):
                                           jsbridge_port=int(self.options.port),
                                           jsbridge_timeout=self.options.timeout,
                                           )
 
         self.tests = []
 
         # setup log formatting
         self.mozmill.add_global_listener(LoggerListener())
-        log_options = { 'format': "%(levelname)s | %(message)s",
-                        'level': logging.CRITICAL }
+        log_options = {
+            'format': "%(levelname)s | %(message)s",
+            'level': logging.CRITICAL,
+        }
         if self.options.showerrors:
             log_options['level'] = logging.ERROR
         if self.options.logfile:
             log_options['filename'] = self.options.logfile
             log_options['filemode'] = 'w'
             log_options['level'] = logging.DEBUG
         if self.options.test and self.options.showall:
-            log_options['level'] = logging.DEBUG    
+            log_options['level'] = logging.DEBUG
         logging.basicConfig(**log_options)
 
     def profile_args(self):
         profargs = super(CLI, self).profile_args()
         profargs.setdefault('addons', []).append(extension_path)
         return profargs
 
     def run(self):
@@ -765,22 +778,21 @@ class CLI(jsbridge.CLI):
             manifest_parser = manifestparser.TestManifest(manifests=self.options.manifests)
 
             self.tests.extend(manifest_parser.test_paths())
 
         # expand user directory for individual tests
         for test in self.options.test:
             test = os.path.expanduser(test)
             self.tests.append(test)
-                
+
         # check existence for the tests
-        missing = [ test for test in self.tests
-                    if not os.path.exists(test) ]
+        missing = [test_ for test_ in self.tests if not os.path.exists(test)]
         if missing:
-            raise IOError("Not a valid test file/directory: %s" % ', '.join(["'%s'" % test for test in missing]))
+            raise IOError("Not a valid test file/directory: %s" % ', '.join(["'%s'" % test_ for test_ in missing]))
 
         # create a Mozrunner
         runner = self.create_runner()
 
         runner.cmdargs.extend(self.options.appArgs)
 
         # make sure the application starts in the foreground
         if '-foreground' not in runner.cmdargs:
@@ -796,29 +808,29 @@ class CLI(jsbridge.CLI):
 
             # run the tests
             disconnected = False
             try:
                 self.mozmill.run_tests(self.tests)
             except JSBridgeDisconnectError:
                 disconnected = True
                 if not self.mozmill.userShutdownEnabled:
-                    self.mozmill.report_disconnect()               
+                    self.mozmill.report_disconnect()
                     print 'TEST-UNEXPECTED-FAIL | Disconnect Error: Application unexpectedly closed'
                 runner.cleanup()
             except:
                 runner.cleanup()
                 raise
 
             # shutdown the test harness
             self.mozmill.stop(fatal=disconnected)
 
             # print statistics and send the JSON report
             self.mozmill.report(self.options.report)
-            
+
             if self.mozmill.fails or disconnected:
                 sys.exit(1)
         else:
             if self.options.shell:
                 self.start_shell(runner)
             else:
                 try:
                     if not hasattr(runner, 'process_handler'):
@@ -834,27 +846,33 @@ class CLI(jsbridge.CLI):
 class RestartCLI(CLI):
     mozmill_class = MozMillRestart
 
 
 class ThunderbirdCLI(CLI):
     profile_class = mozprofile.ThunderbirdProfile
     runner_class = mozrunner.ThunderbirdRunner
 
+
 class ThunderbirdRestartCLI(RestartCLI):
     profile_class = mozprofile.ThunderbirdProfile
     runner_class = mozrunner.ThunderbirdRunner
 
+
 def enum(*sequential, **named):
     enums = dict(zip(sequential, range(len(sequential))), **named)
     return type('Enum', (), enums)
 
+
 def cli():
     CLI().run()
 
+
 def tbird_cli():
     ThunderbirdCLI().run()
 
+
 def restart_cli():
     RestartCLI().run()
 
+
 def tbird_restart_cli():
     ThunderbirdRestartCLI().run()
--- a/mail/test/resources/mozmill/setup.py
+++ b/mail/test/resources/mozmill/setup.py
@@ -48,28 +48,33 @@ setup(name=PACKAGE_NAME,
       description=desc,
       long_description=summ,
       author='Mozilla, Mikeal Rogers',
       author_email='mikeal.rogers@gmail.com',
       url='http://github.com/mozautomation/mozmill',
       license='http://www.apache.org/licenses/LICENSE-2.0',
       packages=find_packages(exclude=['test']),
       include_package_data=True,
-      package_data = {'': ['*.js', '*.css', '*.html', '*.txt', '*.xpi', '*.rdf', '*.xul', '*.jsm', '*.xml'],},
+      package_data={
+          '': ['*.js', '*.css', '*.html', '*.txt', '*.xpi', '*.rdf', '*.xul', '*.jsm', '*.xml'],
+      },
       zip_safe=False,
       entry_points="""
           [console_scripts]
           mozmill = mozmill:cli
           mozmill-thunderbird = mozmill:tbird_cli
           mozmill-restart = mozmill:restart_cli
         """,
-      platforms =['Any'],
-      install_requires = ['jsbridge == 2.4.15',
-                          'mozrunner >= 6.0',
-                          'manifestparser >= 0.9'],
-      classifiers=['Development Status :: 4 - Beta',
-                   'Environment :: Console',
-                   'Intended Audience :: Developers',
-                   'License :: OSI Approved :: Apache Software License',
-                   'Operating System :: OS Independent',
-                   'Topic :: Software Development :: Libraries :: Python Modules',
-                  ]
-     )
+      platforms=['Any'],
+      install_requires=[
+          'jsbridge == 2.4.15',
+          'mozrunner >= 6.0',
+          'manifestparser >= 0.9'
+      ],
+      classifiers=[
+          'Development Status :: 4 - Beta',
+          'Environment :: Console',
+          'Intended Audience :: Developers',
+          'License :: OSI Approved :: Apache Software License',
+          'Operating System :: OS Independent',
+          'Topic :: Software Development :: Libraries :: Python Modules',
+      ]
+      )