Bug 1091285 - move dumpScreen in a new mozscreenshot package. r=jgriffin
authorJulien Pagès <j.parkouss@gmail.com>
Wed, 29 Jul 2015 17:50:16 +0200
changeset 255597 c12f87f250009df14c6ee2f8c00f39dbe15dd553
parent 255596 7b5a9b0979d9dc24c64e80185dfb0d3b320f97de
child 255598 8eace5363f8590a120f7e89a4ed05e8b51742285
push id29149
push usercbook@mozilla.com
push dateFri, 31 Jul 2015 10:10:16 +0000
treeherdermozilla-central@e9389ca320ff [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjgriffin
bugs1091285
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1091285 - move dumpScreen in a new mozscreenshot package. r=jgriffin This also completely remove build/automationutils.py.
build/Makefile.in
build/automation.py.in
build/automationutils.py
build/dumpScreen.py
build/mach_bootstrap.py
layout/tools/reftest/Makefile.in
layout/tools/reftest/runreftest.py
testing/config/mozbase_requirements.txt
testing/mochitest/moz.build
testing/mochitest/runtests.py
testing/mozbase/moz.build
testing/mozbase/mozscreenshot/mozscreenshot/__init__.py
testing/mozbase/mozscreenshot/setup.py
testing/mozbase/packages.txt
testing/tools/mach_test_package_bootstrap.py
testing/xpcshell/Makefile.in
testing/xpcshell/mach_commands.py
--- a/build/Makefile.in
+++ b/build/Makefile.in
@@ -94,11 +94,8 @@ application.ini.h: appini_header.py $(FI
 	$(PYTHON) $^ > $@
 export:: application.ini.h
 GARBAGE += application.ini.h
 endif
 endif
 
 libs:: automation.py
 
-ifdef ENABLE_TESTS
-GARBAGE += $(srcdir)/automationutils.pyc
-endif # ENABLE_TESTS
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -11,31 +11,32 @@ import select
 import signal
 import subprocess
 import sys
 import tempfile
 from datetime import datetime, timedelta
 
 SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
 sys.path.insert(0, SCRIPT_DIR)
-import automationutils
 
 # --------------------------------------------------------------
 # TODO: this is a hack for mozbase without virtualenv, remove with bug 849900
 # These paths refer to relative locations to test.zip, not the OBJDIR or SRCDIR
 here = os.path.dirname(os.path.realpath(__file__))
 mozbase = os.path.realpath(os.path.join(os.path.dirname(here), 'mozbase'))
 
 if os.path.isdir(mozbase):
     for package in os.listdir(mozbase):
         package_path = os.path.join(mozbase, package)
         if package_path not in sys.path:
             sys.path.append(package_path)
 
 import mozcrash
+from mozscreenshot import printstatus, dump_screen
+
 
 # ---------------------------------------------------------------
 
 _DEFAULT_PREFERENCE_FILE = os.path.join(SCRIPT_DIR, 'prefs_general.js')
 _DEFAULT_APPS_FILE = os.path.join(SCRIPT_DIR, 'webapps_mochitest.json')
 
 _DEFAULT_WEB_SERVER = "127.0.0.1"
 _DEFAULT_HTTP_PORT = 8888
@@ -362,17 +363,17 @@ class Automation(object):
         raise
 
   def dumpScreen(self, utilityPath):
     if self.haveDumpedScreen:
       self.log.info("Not taking screenshot here: see the one that was previously logged")
       return
 
     self.haveDumpedScreen = True;
-    automationutils.dumpScreen(utilityPath)
+    dump_screen(utilityPath, self.log)
 
 
   def killAndGetStack(self, processPID, utilityPath, debuggerInfo):
     """Kill the process, preferrably in a way that gets us a stack trace.
        Also attempts to obtain a screenshot before killing the process."""
     if not debuggerInfo:
       self.dumpScreen(utilityPath)
     self.killAndGetStackNoScreenshot(processPID, utilityPath, debuggerInfo)
@@ -384,17 +385,17 @@ class Automation(object):
         # ABRT will get picked up by Breakpad's signal handler
         os.kill(processPID, signal.SIGABRT)
         return
       else:
         # We should have a "crashinject" program in our utility path
         crashinject = os.path.normpath(os.path.join(utilityPath, "crashinject.exe"))
         if os.path.exists(crashinject):
           status = subprocess.Popen([crashinject, str(processPID)]).wait()
-          automationutils.printstatus(status, "crashinject")
+          printstatus("crashinject", status)
           if status == 0:
             return
     self.log.info("Can't trigger Breakpad, just killing process")
     self.killPid(processPID)
 
   def waitForFinish(self, proc, utilityPath, timeout, maxTime, startTime, debuggerInfo, symbolsPath):
     """ Look for timeout or crashes and return the status after the process terminates """
     stackFixerFunction = None
@@ -452,17 +453,17 @@ class Automation(object):
         if line:
           self.log.info(line.rstrip().decode("UTF-8", "ignore"))
         self.log.info("TEST-UNEXPECTED-FAIL | %s | application timed out after %d seconds with no output", self.lastTestSeen, int(timeout))
         if browserProcessId == -1:
           browserProcessId = proc.pid
         self.killAndGetStack(browserProcessId, utilityPath, debuggerInfo)
 
     status = proc.wait()
-    automationutils.printstatus(status, "Main app process")
+    printstatus("Main app process", status)
     if status == 0:
       self.lastTestSeen = "Main app process exited normally"
     if status != 0 and not didTimeout and not hitMaxTime:
       self.log.info("TEST-UNEXPECTED-FAIL | %s | Exited with code %d during test run", self.lastTestSeen, status)
     return status
 
   def buildCommandLine(self, app, debuggerInfo, profileDir, testURL, extraArgs):
     """ build the application command line """
deleted file mode 100644
--- a/build/automationutils.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-from __future__ import with_statement
-import logging
-from operator import itemgetter
-import os
-import platform
-import re
-import signal
-import subprocess
-import sys
-import tempfile
-import mozinfo
-
-__all__ = [
-  'dumpScreen',
-  "setAutomationLog",
-  ]
-
-log = logging.getLogger()
-def resetGlobalLog():
-  while log.handlers:
-    log.removeHandler(log.handlers[0])
-  handler = logging.StreamHandler(sys.stdout)
-  log.setLevel(logging.INFO)
-  log.addHandler(handler)
-resetGlobalLog()
-
-def setAutomationLog(alt_logger):
-  global log
-  log = alt_logger
-
-# Python does not provide strsignal() even in the very latest 3.x.
-# This is a reasonable fake.
-def strsig(n):
-  # Signal numbers run 0 through NSIG-1; an array with NSIG members
-  # has exactly that many slots
-  _sigtbl = [None]*signal.NSIG
-  for k in dir(signal):
-    if k.startswith("SIG") and not k.startswith("SIG_") and k != "SIGCLD" and k != "SIGPOLL":
-      _sigtbl[getattr(signal, k)] = k
-  # Realtime signals mostly have no names
-  if hasattr(signal, "SIGRTMIN") and hasattr(signal, "SIGRTMAX"):
-    for r in range(signal.SIGRTMIN+1, signal.SIGRTMAX+1):
-      _sigtbl[r] = "SIGRTMIN+" + str(r - signal.SIGRTMIN)
-  # Fill in any remaining gaps
-  for i in range(signal.NSIG):
-    if _sigtbl[i] is None:
-      _sigtbl[i] = "unrecognized signal, number " + str(i)
-  if n < 0 or n >= signal.NSIG:
-    return "out-of-range signal, number "+str(n)
-  return _sigtbl[n]
-
-def printstatus(status, name = ""):
-  # 'status' is the exit status
-  if os.name != 'posix':
-    # Windows error codes are easier to look up if printed in hexadecimal
-    if status < 0:
-      status += 2**32
-    print "TEST-INFO | %s: exit status %x\n" % (name, status)
-  elif os.WIFEXITED(status):
-    print "TEST-INFO | %s: exit %d\n" % (name, os.WEXITSTATUS(status))
-  elif os.WIFSIGNALED(status):
-    # The python stdlib doesn't appear to have strsignal(), alas
-    print "TEST-INFO | {}: killed by {}".format(name,strsig(os.WTERMSIG(status)))
-  else:
-    # This is probably a can't-happen condition on Unix, but let's be defensive
-    print "TEST-INFO | %s: undecodable exit status %04x\n" % (name, status)
-
-def dumpScreen(utilityPath):
-  """dumps a screenshot of the entire screen to a directory specified by
-  the MOZ_UPLOAD_DIR environment variable"""
-
-  # Need to figure out which OS-dependent tool to use
-  if mozinfo.isUnix:
-    utility = [os.path.join(utilityPath, "screentopng")]
-    utilityname = "screentopng"
-  elif mozinfo.isMac:
-    utility = ['/usr/sbin/screencapture', '-C', '-x', '-t', 'png']
-    utilityname = "screencapture"
-  elif mozinfo.isWin:
-    utility = [os.path.join(utilityPath, "screenshot.exe")]
-    utilityname = "screenshot"
-
-  # Get dir where to write the screenshot file
-  parent_dir = os.environ.get('MOZ_UPLOAD_DIR', None)
-  if not parent_dir:
-    log.info('Failed to retrieve MOZ_UPLOAD_DIR env var')
-    return
-
-  # Run the capture
-  try:
-    tmpfd, imgfilename = tempfile.mkstemp(prefix='mozilla-test-fail-screenshot_', suffix='.png', dir=parent_dir)
-    os.close(tmpfd)
-    returncode = subprocess.call(utility + [imgfilename])
-    printstatus(returncode, utilityname)
-  except OSError, err:
-    log.info("Failed to start %s for screenshot: %s"
-             % (utility[0], err.strerror))
-    return
deleted file mode 100644
--- a/build/dumpScreen.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env python
-
-"""
-test dumpScreen functionality
-"""
-
-import automationutils
-import optparse
-import os
-import sys
-
-
-def main(args=sys.argv[1:]):
-
-    # parse CLI options
-    usage = '%prog [options] path/to/OBJDIR/dist/bin'
-    parser = optparse.OptionParser(usage=usage)
-    options, args = parser.parse_args(args)
-    if len(args) != 1:
-        parser.error("Please provide utility path")
-    utilityPath = args[0]
-
-    # dump the screen to a data: URL
-    uri = automationutils.dumpScreen(utilityPath)
-
-    # print the uri
-    print uri
-
-if __name__ == '__main__':
-    main()
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -102,16 +102,17 @@ SEARCH_PATHS = [
     'testing/mozbase/mozleak',
     'testing/mozbase/mozlog',
     'testing/mozbase/moznetwork',
     'testing/mozbase/mozprocess',
     'testing/mozbase/mozprofile',
     'testing/mozbase/mozrunner',
     'testing/mozbase/mozsystemmonitor',
     'testing/mozbase/mozinfo',
+    'testing/mozbase/mozscreenshot',
     'testing/mozbase/moztest',
     'testing/mozbase/mozversion',
     'testing/mozbase/manifestparser',
     'xpcom/idl-parser',
 ]
 
 # Individual files providing mach commands.
 MACH_MODULES = [
--- a/layout/tools/reftest/Makefile.in
+++ b/layout/tools/reftest/Makefile.in
@@ -14,17 +14,16 @@
   automation.py \
   $(topsrcdir)/testing/mozbase/mozdevice/mozdevice/devicemanager.py \
   $(topsrcdir)/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py \
   $(topsrcdir)/testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py \
   $(topsrcdir)/testing/mozbase/mozdevice/mozdevice/droid.py \
   $(topsrcdir)/testing/mozbase/mozdevice/mozdevice/Zeroconf.py \
   $(topsrcdir)/testing/mozbase/moznetwork/moznetwork/moznetwork.py \
   $(topsrcdir)/build/mobile/b2gautomation.py \
-  $(topsrcdir)/build/automationutils.py \
   $(topsrcdir)/build/mobile/remoteautomation.py \
   $(topsrcdir)/testing/mochitest/server.js \
   $(topsrcdir)/build/pgo/server-locations.txt \
   $(NULL)
 
 _HARNESS_PP_FILES = \
   b2g_start_script.js \
   $(NULL)
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -17,28 +17,25 @@ import signal
 import subprocess
 import sys
 import threading
 
 SCRIPT_DIRECTORY = os.path.abspath(
     os.path.realpath(os.path.dirname(sys.argv[0])))
 sys.path.insert(0, SCRIPT_DIRECTORY)
 
-from automationutils import (
-    dumpScreen,
-    printstatus
-)
 import mozcrash
 import mozdebug
 import mozinfo
 import mozleak
 import mozprocess
 import mozprofile
 import mozrunner
 from mozrunner.utils import test_environment
+from mozscreenshot import printstatus, dump_screen
 
 here = os.path.abspath(os.path.dirname(__file__))
 
 try:
     from mozbuild.base import MozbuildObject
     build_obj = MozbuildObject.from_environment(cwd=here)
 except ImportError:
     build_obj = None
@@ -431,17 +428,17 @@ class RefTest(object):
         self.killAndGetStack(
             proc, utilityPath, debuggerInfo, dump_screen=not debuggerInfo)
 
     def dumpScreen(self, utilityPath):
         if self.haveDumpedScreen:
             log.info("Not taking screenshot here: see the one that was previously logged")
             return
         self.haveDumpedScreen = True
-        dumpScreen(utilityPath)
+        dump_screen(utilityPath, log)
 
     def killAndGetStack(self, process, utilityPath, debuggerInfo, dump_screen=False):
         """
         Kill the process, preferrably in a way that gets us a stack trace.
         Also attempts to obtain a screenshot before killing the process
         if specified.
         """
 
@@ -451,17 +448,17 @@ class RefTest(object):
         if mozinfo.info.get('crashreporter', True) and not debuggerInfo:
             if mozinfo.isWin:
                 # We should have a "crashinject" program in our utility path
                 crashinject = os.path.normpath(
                     os.path.join(utilityPath, "crashinject.exe"))
                 if os.path.exists(crashinject):
                     status = subprocess.Popen(
                         [crashinject, str(process.pid)]).wait()
-                    printstatus(status, "crashinject")
+                    printstatus("crashinject", status)
                     if status == 0:
                         return
             else:
                 try:
                     process.kill(sig=signal.SIGABRT)
                 except OSError:
                     # https://bugzilla.mozilla.org/show_bug.cgi?id=921509
                     log.info("Can't trigger Breakpad, process no longer exists")
--- a/testing/config/mozbase_requirements.txt
+++ b/testing/config/mozbase_requirements.txt
@@ -7,10 +7,11 @@
 ../mozbase/mozinfo
 ../mozbase/mozinstall
 ../mozbase/mozleak
 ../mozbase/mozlog
 ../mozbase/moznetwork
 ../mozbase/mozprocess
 ../mozbase/mozprofile
 ../mozbase/mozrunner
+../mozbase/mozscreenshot
 ../mozbase/moztest
 ../mozbase/mozversion
--- a/testing/mochitest/moz.build
+++ b/testing/mochitest/moz.build
@@ -25,17 +25,16 @@ MOCHITEST_MANIFESTS += [
     'static/mochitest.ini',
     'tests/MochiKit-1.4.2/MochiKit/mochitest.ini',
     'tests/MochiKit-1.4.2/tests/mochitest.ini',
 ]
 MOCHITEST_CHROME_MANIFESTS += ['chrome/chrome.ini']
 
 TEST_HARNESS_FILES.testing.mochitest += [
     '!automation.py',
-    '/build/automationutils.py',
     '/build/mobile/remoteautomation.py',
     '/build/pgo/server-locations.txt',
     '/build/sanitizers/lsan_suppressions.txt',
     '/netwerk/test/httpserver/httpd.js',
     '/testing/mozbase/mozdevice/mozdevice/devicemanager.py',
     '/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py',
     '/testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py',
     '/testing/mozbase/mozdevice/mozdevice/droid.py',
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -29,22 +29,16 @@ import subprocess
 import sys
 import tempfile
 import time
 import traceback
 import urllib2
 import zipfile
 import bisection
 
-from automationutils import (
-    dumpScreen,
-    printstatus,
-    setAutomationLog,
-)
-
 from datetime import datetime
 from manifestparser import TestManifest
 from manifestparser.filters import (
     chunk_by_dir,
     chunk_by_runtime,
     chunk_by_slice,
     pathprefix,
     subsuite,
@@ -53,16 +47,17 @@ from manifestparser.filters import (
 from leaks import ShutdownLeaks, LSANLeaks
 from mochitest_options import MochitestArgumentParser, build_obj
 from mozprofile import Profile, Preferences
 from mozprofile.permissions import ServerLocations
 from urllib import quote_plus as encodeURIComponent
 from mozlog.formatters import TbplFormatter
 from mozlog import commandline
 from mozrunner.utils import test_environment
+from mozscreenshot import dump_screen
 import mozleak
 
 here = os.path.abspath(os.path.dirname(__file__))
 
 
 ###########################
 # Option for NSPR logging #
 ###########################
@@ -521,17 +516,16 @@ class MochitestUtilsMixin(object):
                 MochitestFormatter,
                 "Mochitest specific tbpl formatter")
             self.log = commandline.setup_logging("mochitest",
                                                  logger_options,
                                                  {
                                                      "tbpl": sys.stdout
                                                  })
             MochitestUtilsMixin.log = self.log
-            setAutomationLog(self.log)
 
         self.message_logger = MessageLogger(logger=self.log)
 
     def update_mozinfo(self):
         """walk up directories to find mozinfo.json update the info"""
         # TODO: This should go in a more generic place, e.g. mozinfo
 
         path = SCRIPT_DIR
@@ -1554,17 +1548,17 @@ class Mochitest(MochitestUtilsMixin):
         options.manifestFile = None
 
     def dumpScreen(self, utilityPath):
         if self.haveDumpedScreen:
             self.log.info(
                 "Not taking screenshot here: see the one that was previously logged")
             return
         self.haveDumpedScreen = True
-        dumpScreen(utilityPath)
+        dump_screen(utilityPath, self.log)
 
     def killAndGetStack(
             self,
             processPID,
             utilityPath,
             debuggerInfo,
             dump_screen=False):
         """
@@ -1778,31 +1772,32 @@ class Mochitest(MochitestUtilsMixin):
                                 process_args=kp_kwargs)
 
             # start the runner
             runner.start(debug_args=debug_args,
                          interactive=interactive,
                          outputTimeout=timeout)
             proc = runner.process_handler
             self.log.info("runtests.py | Application pid: %d" % proc.pid)
+            self.log.process_start("Main app process")
 
             if onLaunch is not None:
                 # Allow callers to specify an onLaunch callback to be fired after the
                 # app is launched.
                 # We call onLaunch for b2g desktop mochitests so that we can
                 # run a Marionette script after gecko has completed startup.
                 onLaunch()
 
             # wait until app is finished
             # XXX copy functionality from
             # https://github.com/mozilla/mozbase/blob/master/mozrunner/mozrunner/runner.py#L61
             # until bug 913970 is fixed regarding mozrunner `wait` not returning status
             # see https://bugzilla.mozilla.org/show_bug.cgi?id=913970
             status = proc.wait()
-            printstatus(status, "Main app process")
+            self.log.process_exit("Main app process", status)
             runner.process_handler = None
 
             # finalize output handler
             outputHandler.finish()
 
             # record post-test information
             if status:
                 self.message_logger.dump_buffered()
--- a/testing/mozbase/moz.build
+++ b/testing/mozbase/moz.build
@@ -18,16 +18,17 @@ python_modules = [
     'mozinfo',
     'mozinstall',
     'mozleak',
     'mozlog',
     'moznetwork',
     'mozprocess',
     'mozprofile',
     'mozrunner',
+    'mozscreenshot',
     'mozsystemmonitor',
     'moztest',
     'mozversion',
 ]
 
 TEST_HARNESS_FILES.mozbase += [m + '/**' for m in python_modules]
 
 TEST_HARNESS_FILES.mozbase += [
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozscreenshot/mozscreenshot/__init__.py
@@ -0,0 +1,61 @@
+# 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 mozinfo
+import tempfile
+import subprocess
+from mozlog.formatters.process import strstatus
+
+
+def printstatus(name, returncode):
+    """
+    print the status of a command exit code, formatted for tbpl.
+
+    Note that mozlog structured action "process_exit" should be used
+    instead of that in new code.
+    """
+    print "TEST-INFO | %s: %s" % (name, strstatus(returncode))
+
+
+def dump_screen(utilityPath, log):
+    """dumps a screenshot of the entire screen to a directory specified by
+    the MOZ_UPLOAD_DIR environment variable"""
+
+    is_structured_log = hasattr(log, 'process_exit')
+
+    # Need to figure out which OS-dependent tool to use
+    if mozinfo.isUnix:
+        utility = [os.path.join(utilityPath, "screentopng")]
+        utilityname = "screentopng"
+    elif mozinfo.isMac:
+        utility = ['/usr/sbin/screencapture', '-C', '-x', '-t', 'png']
+        utilityname = "screencapture"
+    elif mozinfo.isWin:
+        utility = [os.path.join(utilityPath, "screenshot.exe")]
+        utilityname = "screenshot"
+
+    # Get dir where to write the screenshot file
+    parent_dir = os.environ.get('MOZ_UPLOAD_DIR', None)
+    if not parent_dir:
+        log.info('Failed to retrieve MOZ_UPLOAD_DIR env var')
+        return
+
+    # Run the capture
+    try:
+        tmpfd, imgfilename = tempfile.mkstemp(
+            prefix='mozilla-test-fail-screenshot_',
+            suffix='.png', dir=parent_dir
+        )
+        os.close(tmpfd)
+        if is_structured_log:
+            log.process_start(utilityname)
+        returncode = subprocess.call(utility + [imgfilename])
+        if is_structured_log:
+            log.process_exit(utilityname, returncode)
+        else:
+            printstatus(utilityname, returncode)
+    except OSError, err:
+        log.info("Failed to start %s for screenshot: %s"
+                 % (utility[0], err.strerror))
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/mozscreenshot/setup.py
@@ -0,0 +1,26 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from setuptools import setup
+
+
+PACKAGE_NAME = 'mozscreenshot'
+PACKAGE_VERSION = '0.1'
+
+
+setup(
+    name=PACKAGE_NAME,
+    version=PACKAGE_VERSION,
+    description="Library for taking screenshots in tests harness",
+    long_description="see http://mozbase.readthedocs.org/",
+    classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+    keywords='mozilla',
+    author='Mozilla Automation and Tools team',
+    author_email='tools@lists.mozilla.org',
+    url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase',
+    license='MPL',
+    packages=['mozscreenshot'],
+    zip_safe=False,
+    install_requires=['mozlog', 'mozinfo'],
+)
--- a/testing/mozbase/packages.txt
+++ b/testing/mozbase/packages.txt
@@ -9,10 +9,11 @@ mozinfo.pth:testing/mozbase/mozinfo
 mozinstall.pth:testing/mozbase/mozinstall
 mozleak.pth:testing/mozbase/mozleak
 mozlog.pth:testing/mozbase/mozlog
 moznetwork.pth:testing/mozbase/moznetwork
 mozprocess.pth:testing/mozbase/mozprocess
 mozprofile.pth:testing/mozbase/mozprofile
 mozrunner.pth:testing/mozbase/mozrunner
 mozsystemmonitor.pth:testing/mozbase/mozsystemmonitor
+mozscreenshot.pth:testing/mozbase/mozscreenshot
 moztest.pth:testing/mozbase/moztest
 mozversion.pth:testing/mozbase/mozversion
--- a/testing/tools/mach_test_package_bootstrap.py
+++ b/testing/tools/mach_test_package_bootstrap.py
@@ -20,16 +20,17 @@ SEARCH_PATHS = [
     'mozbase/mozleak',
     'mozbase/mozlog',
     'mozbase/moznetwork',
     'mozbase/mozprocess',
     'mozbase/mozprofile',
     'mozbase/mozrunner',
     'mozbase/mozsystemmonitor',
     'mozbase/mozinfo',
+    'mozbase/mozscreenshot',
     'mozbase/moztest',
     'mozbase/mozversion',
     'mozbase/manifestparser',
     'tools/mach',
 ]
 
 # Individual files providing mach commands.
 MACH_MODULES = [
--- a/testing/xpcshell/Makefile.in
+++ b/testing/xpcshell/Makefile.in
@@ -14,17 +14,16 @@ TEST_HARNESS_FILES := \
   node-spdy \
   moz-spdy \
   node-http2 \
   moz-http2 \
   $(NULL)
 
 # Extra files needed from $(topsrcdir)/build
 EXTRA_BUILD_FILES := \
-  automationutils.py \
   manifestparser.py \
   $(NULL)
 
 # Components / typelibs that don't get packaged with
 # the build, but that we need for the test harness.
 TEST_HARNESS_COMPONENTS := \
   httpd.js \
   httpd.manifest \
--- a/testing/xpcshell/mach_commands.py
+++ b/testing/xpcshell/mach_commands.py
@@ -73,18 +73,19 @@ class XPCShellRunner(MozbuildObject):
         from mozbuild.testing import TestResolver
         from manifestparser import TestManifest
 
         # TODO Bug 794506 remove once mach integrates with virtualenv.
         build_path = os.path.join(self.topobjdir, 'build')
         if build_path not in sys.path:
             sys.path.append(build_path)
 
-        if not os.path.isfile(os.path.join(self.topsrcdir, 'build', 'automationutils.py')):
-            sys.path.append(os.path.join(self.topsrcdir, 'mozilla', 'build'))
+        src_build_path = os.path.join(self.topsrcdir, 'mozilla', 'build')
+        if os.path.isdir(src_build_path):
+            sys.path.append(src_build_path)
 
         if test_paths == 'all':
             self.run_suite(interactive=interactive,
                            keep_going=keep_going, shuffle=shuffle, sequential=sequential,
                            debugger=debugger, debuggerArgs=debuggerArgs,
                            debuggerInteractive=debuggerInteractive,
                            jsDebugger=jsDebugger, jsDebuggerPort=jsDebuggerPort,
                            rerun_failures=rerun_failures,