Merge inbound to mozilla-central a=merge
authorarthur.iakab <aiakab@mozilla.com>
Sat, 23 Jun 2018 12:58:43 +0300
changeset 809880 368ae05266bd2fefd96e6a1bd7b7e478749fcec3
parent 809862 9f789b5cdc1dabd0986b552b44804cf91585e93e (current diff)
parent 809879 4789a350ec7ff9ed4d6f300bcecdde771f54cc77 (diff)
child 809881 df99c2671e5741e9209f68768006fbd1d293f7b9
child 809885 836ba1340189d22e4ee9f0fdb1c4ff339fbd266f
child 809886 62035cdfe372b6969894f819a5e0a4d0885726fe
child 809889 5bbe54174894e1b71aaf83006c2744bb8836816e
child 809921 885ed481dee338ed5952fcbc83699ab3975f6465
child 809929 848d96c4ad6cbb8d041610c69de12ea12ea6139f
child 810286 c73f274c9f2533aa3fd98af11d5ddbf4bd52d055
push id113833
push userbmo:ato@sny.no
push dateSat, 23 Jun 2018 12:17:51 +0000
reviewersmerge
milestone62.0a1
Merge inbound to mozilla-central a=merge
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -1430,16 +1430,32 @@ def security_hardening_cflags(hardening_
         js_flags=js_flags,
     )
 
 
 add_old_configure_assignment('MOZ_HARDENING_CFLAGS', security_hardening_cflags.flags)
 add_old_configure_assignment('MOZ_HARDENING_CFLAGS_JS', security_hardening_cflags.js_flags)
 imply_option('--enable-pie', depends_if('--enable-hardening')(lambda v: v))
 
+# Code Coverage
+# ==============================================================
+
+js_option('--enable-coverage', env='MOZ_CODE_COVERAGE',
+          help='Enable code coverage')
+
+
+@depends('--enable-coverage')
+def code_coverage(value):
+    if value:
+        return True
+
+
+set_config('MOZ_CODE_COVERAGE', code_coverage)
+set_define('MOZ_CODE_COVERAGE', code_coverage)
+
 # ==============================================================
 
 option(env='RUSTFLAGS',
        nargs=1,
        help='Rust compiler flags')
 set_config('RUSTFLAGS', depends('RUSTFLAGS')(lambda flags: flags))
 
 
@@ -1505,26 +1521,31 @@ def rust_compiler_flags(opt_level, debug
 
 
 set_config('MOZ_RUST_DEFAULT_FLAGS', rust_compiler_flags)
 
 # Rust incremental compilation
 # ==============================================================
 
 
-@depends(rustc_opt_level, debug_rust, 'MOZ_AUTOMATION')
-def cargo_incremental(opt_level, debug_rust, automation):
+@depends(rustc_opt_level, debug_rust, 'MOZ_AUTOMATION', code_coverage)
+def cargo_incremental(opt_level, debug_rust, automation, code_coverage):
     """Return a value for the CARGO_INCREMENTAL environment variable."""
 
     # We never want to use incremental compilation in automation.  sccache
     # handles our automation use case much better than incremental compilation
     # would.
     if automation:
         return '0'
 
+    # Coverage instrumentation doesn't play well with incremental compilation
+    # https://github.com/rust-lang/rust/issues/50203.
+    if code_coverage:
+        return '0'
+
     # Incremental compilation is automatically turned on for debug builds, so
     # we don't need to do anything special here.
     if debug_rust:
         return
 
     # --enable-release automatically sets -O2 for Rust code, and people can
     # set RUSTC_OPT_LEVEL to 2 or even 3 if they want to profile Rust code.
     # Let's assume that if Rust code is using -O2 or higher, we shouldn't
@@ -1633,32 +1654,16 @@ add_old_configure_assignment('ENABLE_CLA
                              depends_if('--enable-clang-plugin')(lambda _: True))
 
 js_option('--enable-mozsearch-plugin', env='ENABLE_MOZSEARCH_PLUGIN',
           help="Enable building with the mozsearch indexer plugin")
 
 add_old_configure_assignment('ENABLE_MOZSEARCH_PLUGIN',
                              depends_if('--enable-mozsearch-plugin')(lambda _: True))
 
-# Code Coverage
-# ==============================================================
-
-js_option('--enable-coverage', env='MOZ_CODE_COVERAGE',
-          help='Enable code coverage')
-
-
-@depends('--enable-coverage')
-def code_coverage(value):
-    if value:
-        return True
-
-
-set_config('MOZ_CODE_COVERAGE', code_coverage)
-set_define('MOZ_CODE_COVERAGE', code_coverage)
-
 # Libstdc++ compatibility hacks
 # ==============================================================
 #
 js_option('--enable-stdcxx-compat', env='MOZ_STDCXX_COMPAT',
           help='Enable compatibility with older libstdc++')
 
 
 @template
--- a/js/src/ds/LifoAlloc.cpp
+++ b/js/src/ds/LifoAlloc.cpp
@@ -21,18 +21,18 @@ using mozilla::tl::BitSize;
 
 namespace js {
 namespace detail {
 
 /* static */
 UniquePtr<BumpChunk>
 BumpChunk::newWithCapacity(size_t size, bool protect)
 {
-    MOZ_ASSERT(RoundUpPow2(size) == size);
-    MOZ_ASSERT(size >= sizeof(BumpChunk));
+    MOZ_DIAGNOSTIC_ASSERT(RoundUpPow2(size) == size);
+    MOZ_DIAGNOSTIC_ASSERT(size >= sizeof(BumpChunk));
     void* mem = js_malloc(size);
     if (!mem)
         return nullptr;
 
     UniquePtr<BumpChunk> result(new (mem) BumpChunk(size, protect));
 
     // We assume that the alignment of LIFO_ALLOC_ALIGN is less than that of the
     // underlying memory allocator -- creating a new BumpChunk should always
--- a/layout/reftests/w3c-css/submitted/contain/contain-paint-ignored-cases-internal-table-001-ref.html
+++ b/layout/reftests/w3c-css/submitted/contain/contain-paint-ignored-cases-internal-table-001-ref.html
@@ -1,30 +1,30 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset=utf-8>
-    <title>CSS Reftest Reference</title>
-    <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com">
-    <style>
-      tr {
-        z-index: 10;
-      }
-      th {
-        background-color: blue;
-        padding-left: 50px;
-      }
-      caption {
-        position: fixed;
-        background-color: yellow;
-        z-index: 2;
-      }
-    </style>
-  </head>
-  <body>
-    <table>
-      <caption>PASS</caption>
-      <tr>
-        <th>&emsp;</th>
-      </tr>
-    </table>
-  </body>
-</html>
\ No newline at end of file
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <title>CSS Reftest Reference</title>
+    <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com">
+    <style>
+      tr {
+        z-index: 10;
+      }
+      th {
+        background-color: blue;
+        padding-left: 50px;
+      }
+      caption {
+        position: fixed;
+        background-color: yellow;
+        z-index: 2;
+      }
+    </style>
+  </head>
+  <body>
+    <table>
+      <caption>PASS</caption>
+      <tr>
+        <th>&emsp;</th>
+      </tr>
+    </table>
+  </body>
+</html>
--- a/layout/reftests/w3c-css/submitted/contain/contain-paint-ignored-cases-internal-table-001a.html
+++ b/layout/reftests/w3c-css/submitted/contain/contain-paint-ignored-cases-internal-table-001a.html
@@ -1,34 +1,34 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset=utf-8>
-    <title>CSS-contain test: paint containment on internal table elements except table-cell.</title>
-    <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com">
-    <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint">
-    <link rel="match" href="contain-paint-ignored-cases-internal-table-001-ref.html">
-    <meta name="assert" content="Paint containment should not apply to internal table elements except table-cell. This test testes only the tr element, and confirms contain:paint does not create a stacking context.">
-    <style>
-      tr {
-        contain: paint;
-        z-index: 10;
-      }
-      th {
-        background-color: blue;
-        padding-left: 50px;
-      }
-      caption {
-        position: fixed;
-        background-color: yellow;
-        z-index: 2;
-      }
-    </style>
-  </head>
-  <body>
-    <table>
-      <caption>PASS</caption>
-      <tr>
-        <th>&emsp;</th>
-      </tr>
-    </table>
-  </body>
-</html>
\ No newline at end of file
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <title>CSS-contain test: paint containment on internal table elements except table-cell.</title>
+    <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com">
+    <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint">
+    <link rel="match" href="contain-paint-ignored-cases-internal-table-001-ref.html">
+    <meta name="assert" content="Paint containment should not apply to internal table elements except table-cell. This test testes only the tr element, and confirms contain:paint does not create a stacking context.">
+    <style>
+      tr {
+        contain: paint;
+        z-index: 10;
+      }
+      th {
+        background-color: blue;
+        padding-left: 50px;
+      }
+      caption {
+        position: fixed;
+        background-color: yellow;
+        z-index: 2;
+      }
+    </style>
+  </head>
+  <body>
+    <table>
+      <caption>PASS</caption>
+      <tr>
+        <th>&emsp;</th>
+      </tr>
+    </table>
+  </body>
+</html>
--- a/layout/reftests/w3c-css/submitted/contain/contain-paint-ignored-cases-internal-table-001b.html
+++ b/layout/reftests/w3c-css/submitted/contain/contain-paint-ignored-cases-internal-table-001b.html
@@ -1,36 +1,36 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset=utf-8>
-    <title>CSS-contain test: paint containment on internal table elements except table-cell.</title>
-    <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com">
-    <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint">
-    <link rel="match" href="contain-paint-ignored-cases-internal-table-001-ref.html">
-    <meta name="assert" content="Paint containment should not apply to internal table elements except table-cell. This test testes only the tbody element, and confirms contain:paint does not create a stacking context.">
-    <style>
-      tbody {
-        contain: paint;
-        z-index: 10;
-      }
-      th {
-        background-color: blue;
-        padding-left: 50px;
-      }
-      caption {
-        position: fixed;
-        background-color: yellow;
-        z-index: 2;
-      }
-    </style>
-  </head>
-  <body>
-    <table>
-      <caption>PASS</caption>
-      <tbody>
-        <tr>
-          <th>&emsp;</th>
-        </tr>
-      </tbody>
-    </table>
-  </body>
-</html>
\ No newline at end of file
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <title>CSS-contain test: paint containment on internal table elements except table-cell.</title>
+    <link rel="author" title="Yusuf Sermet" href="mailto:ysermet@mozilla.com">
+    <link rel="help" href="http://www.w3.org/TR/css-containment-1/#containment-paint">
+    <link rel="match" href="contain-paint-ignored-cases-internal-table-001-ref.html">
+    <meta name="assert" content="Paint containment should not apply to internal table elements except table-cell. This test testes only the tbody element, and confirms contain:paint does not create a stacking context.">
+    <style>
+      tbody {
+        contain: paint;
+        z-index: 10;
+      }
+      th {
+        background-color: blue;
+        padding-left: 50px;
+      }
+      caption {
+        position: fixed;
+        background-color: yellow;
+        z-index: 2;
+      }
+    </style>
+  </head>
+  <body>
+    <table>
+      <caption>PASS</caption>
+      <tbody>
+        <tr>
+          <th>&emsp;</th>
+        </tr>
+      </tbody>
+    </table>
+  </body>
+</html>
--- a/layout/reftests/w3c-css/submitted/contain/contain-paint-ignored-cases-no-principal-box-001-ref.html
+++ b/layout/reftests/w3c-css/submitted/contain/contain-paint-ignored-cases-no-principal-box-001-ref.html
@@ -49,9 +49,9 @@
   <div id="div2">
     <div id="div2_1"></div>
 
     <div id="div2_2"></div>
   </div>
 
   <div id="div3"></div>
 </body>
-</html>
\ No newline at end of file
+</html>
--- a/layout/reftests/w3c-css/submitted/contain/contain-paint-ignored-cases-no-principal-box-001.html
+++ b/layout/reftests/w3c-css/submitted/contain/contain-paint-ignored-cases-no-principal-box-001.html
@@ -53,9 +53,9 @@
   <div id="div2">
     <div id="div2_1"></div>
 
     <div id="div2_2"></div>
   </div>
 
   <div id="div3"></div>
 </body>
-</html>
\ No newline at end of file
+</html>
--- a/memory/replace/dmd/dmd.py
+++ b/memory/replace/dmd/dmd.py
@@ -190,16 +190,19 @@ variable is used to find breakpad symbol
 
     p.add_argument('--print-clamp-stats', action='store_true',
                    help='print information about the results of pointer clamping; mostly '
                    'useful for debugging clamping')
 
     p.add_argument('--filter-stacks-for-testing', action='store_true',
                    help='filter stack traces; only useful for testing purposes')
 
+    p.add_argument('--allocation-filter',
+                   help='Only print entries that have a stack that matches the filter')
+
     p.add_argument('input_file',
                    help='a file produced by DMD')
 
     p.add_argument('input_file2', nargs='?',
                    help='a file produced by DMD; if present, it is diff\'d with input_file')
 
     return p.parse_args(sys.argv[1:])
 
@@ -545,16 +548,21 @@ def printDigest(args, digest):
         out(separator)
         numRecords = len(records)
         cmpRecords = sortByChoices[args.sort_by]
         sortedRecords = sorted(records.values(), cmp=cmpRecords, reverse=True)
         kindBlocks = 0
         kindUsableSize = 0
         maxRecord = 1000
 
+        if args.allocation_filter:
+            sortedRecords = list(filter(
+                lambda x: any(map(lambda y: args.allocation_filter in y, x.allocatedAtDesc)),
+                sortedRecords))
+
         # First iteration: get totals, etc.
         for record in sortedRecords:
             kindBlocks += record.numBlocks
             kindUsableSize += record.usableSize
 
         # Second iteration: print.
         if numRecords == 0:
             out('# no {:} heap blocks\n'.format(recordKind))
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -1426,17 +1426,17 @@ nsHttpConnection::ReadTimeoutTick(PRInte
     }
 
     if (!mNPNComplete) {
       // We can reuse mLastWriteTime here, because it is set when the
       // connection is activated and only change when a transaction
       // succesfullu write to the socket and this can only happen after
       // the TLS handshake is done.
       PRIntervalTime initialTLSDelta = now - mLastWriteTime;
-      if (initialTLSDelta > gHttpHandler->TLSHandshakeTimeout()) {
+      if (initialTLSDelta > PR_MillisecondsToInterval(gHttpHandler->TLSHandshakeTimeout())) {
         LOG(("canceling transaction: tls handshake takes too long: tls handshake "
              "last %ums, timeout is %dms.",
              PR_IntervalToMilliseconds(initialTLSDelta),
              gHttpHandler->TLSHandshakeTimeout()));
 
         // This will also close the connection
         CloseTransaction(mTransaction, NS_ERROR_NET_TIMEOUT);
         return UINT32_MAX;
--- a/taskcluster/ci/test/compiled.yml
+++ b/taskcluster/ci/test/compiled.yml
@@ -1,35 +1,38 @@
 job-defaults:
     e10s: false
     mozharness:
         script:
             by-test-platform:
-                android.*: android_emulator_unittest.py
+                android-em.*: android_emulator_unittest.py
+                android-hw.*: android_hardware_unittest.py
                 default: desktop_unittest.py
         config:
             by-test-platform:
-                android.*:
+                android-em.*:
                     - android/android_common.py
                     - android/androidarm_4_3.py
+                android-hw.*:
+                    - android/android_hw.py
                 linux.*:
                     - unittests/linux_unittest.py
                     - remove_executables.py
                 macosx.*:
                     - unittests/mac_unittest.py
                 windows.*:
                     - unittests/win_taskcluster_unittest.py
 
 cppunit:
     description: "CPP Unit Tests"
     suite: cppunittest
     treeherder-symbol: Cpp
     instance-size:
         by-test-platform:
-            android.*: xlarge
+            android-em.*: xlarge
             default: default
     run-on-projects: built-projects
 
 gtest:
     description: "GTests run"
     suite: gtest
     treeherder-symbol: GTest
     instance-size: xlarge
@@ -48,29 +51,33 @@ gtest:
             default: default
 
 jittest:
     description: "JIT Test run"
     suite: jittest/jittest-chunked
     treeherder-symbol: Jit
     instance-size:
         by-test-platform:
-            android.*: xlarge
+            android-em.*: xlarge
             default: default
     run-on-projects: built-projects
     chunks:
         by-test-platform:
             windows.*: 1
             windows10-64-ccov/debug: 6
             macosx.*: 1
-            android-4.3-arm7-api-15/debug: 20
+            android-em-4.3-arm7-api-15/debug: 20
             android.*: 10
             default: 6
     max-run-time:
         by-test-platform:
             windows10-64-ccov/debug: 7200
             default: 3600
     mozharness:
         chunked:
             by-test-platform:
                 windows.*: false
                 macosx.*: false
                 default: true
+    tier:
+        by-test-platform:
+            android-hw.*: 3
+            default: default
--- a/taskcluster/ci/test/marionette.yml
+++ b/taskcluster/ci/test/marionette.yml
@@ -1,47 +1,47 @@
 job-defaults:
     suite: marionette
     mozharness:
         script:
             by-test-platform:
-                android.*: android_emulator_unittest.py
+                android-em.*: android_emulator_unittest.py
                 default: marionette.py
         config:
             by-test-platform:
-                android.*:
+                android-em.*:
                     - android/android_common.py
                     - android/androidarm_4_3.py
                 macosx.*:
                     - marionette/prod_config.py
                     - marionette/mac_taskcluster_config.py
                 windows.*:
                     - marionette/windows_taskcluster_config.py
                 default:
                     - marionette/prod_config.py
                     - remove_executables.py
 
 marionette:
     description: "Marionette unittest run"
     treeherder-symbol: Mn
     max-run-time:
         by-test-platform:
-            android.*: 3600
+            android-em.*: 3600
             default: 5400
     instance-size:
         by-test-platform:
-            android.*: xlarge
+            android-em.*: xlarge
             default: default
     tier:
         by-test-platform:
-            android.*: 2
+            android-em.*: 2
             default: default
     chunks:
         by-test-platform:
-            android.*: 10
+            android-em.*: 10
             default: 1
 
 marionette-headless:
     description: "Marionette headless unittest run"
     treeherder-symbol: MnH
     max-run-time: 5400
     instance-size: default
     mozharness:
--- a/taskcluster/ci/test/misc.yml
+++ b/taskcluster/ci/test/misc.yml
@@ -5,66 +5,66 @@ geckoview:
     instance-size: xlarge
     loopback-video: true
     e10s: false
     target: geckoview_example.apk
     mozharness:
         script: android_emulator_unittest.py
         config:
             by-test-platform:
-                android-4.2-x86/opt:
+                android-em-4.2-x86/opt:
                     - android/android_common.py
                     - android/androidx86.py
-                android-7.0-x86/opt:
+                android-em-7.0-x86/opt:
                     - android/android_common.py
                     - android/androidx86_7_0.py
-                android.*:
+                android-em.*:
                     - android/android_common.py
                     - android/androidarm_4_3.py
         extra-options:
             - --test-suite=geckoview
 
 geckoview-junit:
     description: "Geckoview junit run"
     suite: geckoview-junit
     treeherder-symbol: gv-junit
     instance-size: xlarge
     loopback-video: true
     e10s: true
     target: geckoview-androidTest.apk
     max-run-time: 3600
     chunks:
         by-test-platform:
-            android-4.3-arm7-api-16/debug: 3
+            android-em-4.3-arm7-api-16/debug: 3
             default: 1
     mozharness:
         script: android_emulator_unittest.py
         config:
             by-test-platform:
-                android-4.2-x86/opt:
+                android-em-4.2-x86/opt:
                     - android/android_common.py
                     - android/androidx86.py
-                android-7.0-x86/opt:
+                android-em-7.0-x86/opt:
                     - android/android_common.py
                     - android/androidx86_7_0.py
-                android.*:
+                android-em.*:
                     - android/android_common.py
                     - android/androidarm_4_3_junit.py
         extra-options:
             - --test-suite=geckoview-junit
 
 robocop:
     description: "Robocop run"
     suite: robocop
     treeherder-symbol: M(rc)
     instance-size: xlarge
     chunks:
         by-test-platform:
-            # android-4.3-arm7-api-16/debug -- not run
-            android-4.3-arm7-api-16/opt: 4
+            # android-em-4.3-arm7-api-16/debug -- not run
+            android-em-4.3-arm7-api-16/opt: 4
     loopback-video: true
     e10s: false
     mozharness:
         script: android_emulator_unittest.py
         config:
             - android/android_common.py
             - android/androidarm_4_3.py
         extra-options:
@@ -87,40 +87,40 @@ telemetry-tests-client:
 
 test-verify:
     description: "Extra verification of tests modified on this push"
     suite: test-verify
     treeherder-symbol: TV
     loopback-video: true
     instance-size:
         by-test-platform:
-            android.*: xlarge
+            android-em.*: xlarge
             default: default
     max-run-time: 10800
     allow-software-gl-layers: false
     run-on-projects:
         by-test-platform:
             # do not run on ccov or jsdcov
             .*-ccov/.*: []
             .*-jsdcov/.*: []
             .*-asan/.*: []
             # do not run on beta or release: usually just confirms earlier results
             default: ['trunk', 'try']
     tier: 2
     mozharness:
         script:
             by-test-platform:
-                android.*: android_emulator_unittest.py
+                android-em.*: android_emulator_unittest.py
                 default: desktop_unittest.py
         config:
             by-test-platform:
-                android.*:
+                android-em.*:
                     - android/android_common.py
                     - android/androidarm_4_3.py
-                android-7.0-x86/opt:
+                android-em-7.0-x86/opt:
                     - android/android_common.py
                     - android/androidx86_7_0.py
                 linux.*:
                     - unittests/linux_unittest.py
                     - remove_executables.py
                 macosx.*:
                     - unittests/mac_unittest.py
                 windows.*:
@@ -131,40 +131,40 @@ test-verify:
 test-verify-gpu:
     description: "Extra verification of tests modified on this push on gpu instances"
     suite: test-verify
     treeherder-symbol: TVg
     loopback-video: true
     virtualization: virtual-with-gpu
     instance-size:
         by-test-platform:
-            android.*: xlarge
+            android-em.*: xlarge
             default: default
     max-run-time: 10800
     allow-software-gl-layers: false
     run-on-projects:
         by-test-platform:
             # do not run on ccov or jsdcov
             .*-ccov/.*: []
             .*-jsdcov/.*: []
             .*-asan/.*: []
             # do not run on beta or release: usually just confirms earlier results
             default: ['trunk', 'try']
     tier: 2
     mozharness:
         script:
             by-test-platform:
-                android.*: android_emulator_unittest.py
+                android-em.*: android_emulator_unittest.py
                 default: desktop_unittest.py
         config:
             by-test-platform:
-                android.*:
+                android-em.*:
                     - android/android_common.py
                     - android/androidarm_4_3.py
-                android-7.0-x86/opt:
+                android-em-7.0-x86/opt:
                     - android/android_common.py
                     - android/androidx86_7_0.py
                 linux.*:
                     - unittests/linux_unittest.py
                     - remove_executables.py
                 macosx.*:
                     - unittests/mac_unittest.py
                 windows.*:
@@ -184,21 +184,21 @@ test-coverage:
     run-on-projects:
         by-test-platform:
             .*-ccov/.*: built-projects
             default: []
     tier: 2
     mozharness:
         script:
             by-test-platform:
-                android.*: android_emulator_unittest.py
+                android-em.*: android_emulator_unittest.py
                 default: desktop_unittest.py
         config:
             by-test-platform:
-                android.*:
+                android-em.*:
                     - android/android_common.py
                     - android/androidarm_4_3.py
                 linux.*:
                     - unittests/linux_unittest.py
                     - remove_executables.py
                 macosx.*:
                     - unittests/mac_unittest.py
                 windows.*:
@@ -222,21 +222,21 @@ test-coverage-gpu:
             default: []
     tier:
         by-test-platform:
             windows10-64-asan.*: 3
             default: 2
     mozharness:
         script:
             by-test-platform:
-                android.*: android_emulator_unittest.py
+                android-em.*: android_emulator_unittest.py
                 default: desktop_unittest.py
         config:
             by-test-platform:
-                android.*:
+                android-em.*:
                     - android/android_common.py
                     - android/androidarm_4_3.py
                 linux.*:
                     - unittests/linux_unittest.py
                     - remove_executables.py
                 macosx.*:
                     - unittests/mac_unittest.py
                 windows.*:
--- a/taskcluster/ci/test/mochitest.yml
+++ b/taskcluster/ci/test/mochitest.yml
@@ -1,29 +1,32 @@
 job-defaults:
     target:
         by-test-platform:
-            android-7.0-x86/opt: geckoview-androidTest.apk
+            android-em-7.0-x86/opt: geckoview-androidTest.apk
             default: null
     mozharness:
         script:
             by-test-platform:
-                android.*: android_emulator_unittest.py
+                android-em.*: android_emulator_unittest.py
+                android-hw.*: android_hardware_unittest.py
                 default: desktop_unittest.py
         config:
             by-test-platform:
-                android-4.2-x86/opt:
+                android-em-4.2-x86/opt:
                     - android/android_common.py
                     - android/androidx86.py
-                android-7.0-x86/opt:
+                android-em-7.0-x86/opt:
                     - android/android_common.py
                     - android/androidx86_7_0.py
-                android.*:
+                android-em.*:
                     - android/android_common.py
                     - android/androidarm_4_3.py
+                android-hw.*:
+                    - android/android_hw.py
                 linux.*:
                     - unittests/linux_unittest.py
                     - remove_executables.py
                 macosx.*:
                     - unittests/mac_unittest.py
                 windows.*:
                     - unittests/win_taskcluster_unittest.py
 
@@ -37,51 +40,51 @@ mochitest:
     loopback-video: true
     run-on-projects:
         by-test-platform:
             windows10-64-qr/debug: []  # bug 1424755
             default: built-projects
     instance-size:
         by-test-platform:
             linux64-jsdcov/opt: xlarge
-            android.*: xlarge
+            android-em.*: xlarge
             default: default
     virtualization:
         by-test-platform:
             windows10-64-qr/.*: virtual-with-gpu
             default: virtual
     chunks:
         by-test-platform:
-            android-4.3-arm7-api-16/debug: 48
-            android-7.0-x86/opt: 4
-            android.*: 24
+            android-em-4.3-arm7-api-16/debug: 48
+            android-em-7.0-x86/opt: 4
+            android-em.*: 24
             linux.*/debug: 16
             linux64-asan/opt: 10
             linux64-.*cov/opt: 10
             windows10-64-ccov/debug: 10
             default: 5
     e10s:
         by-test-platform:
             linux32/debug: both
             default: true
     max-run-time:
         by-test-platform:
-            android.*: 7200
+            android-em.*: 7200
             default: 5400
     allow-software-gl-layers: false
     mozharness:
         mochitest-flavor: plain
         extra-options:
             by-test-platform:
-                android.*:
+                android-em.*:
                     - --test-suite=mochitest
                 default: []
         chunked:
             by-test-platform:
-                android.*: false
+                android-em.*: false
                 default: true
 
 mochitest-a11y:
     description: "Mochitest a11y run"
     suite: mochitest/a11y
     treeherder-symbol: M(a11y)
     loopback-video: true
     e10s: false
@@ -153,39 +156,39 @@ browser-screenshots:
 
 mochitest-chrome:
     description: "Mochitest chrome run"
     suite: mochitest/chrome
     treeherder-symbol: M(c)
     loopback-video: true
     instance-size:
         by-test-platform:
-            android.*: xlarge
+            android-em.*: xlarge
             default: default
     chunks:
         by-test-platform:
-            android-4.3-arm7-api-16/debug: 8
-            android-7.0-x86/opt: 1
-            android.*: 4
+            android-em-4.3-arm7-api-16/debug: 8
+            android-em-7.0-x86/opt: 1
+            android-em.*: 4
             default: 3
     max-run-time:
         by-test-platform:
-            android.*: 7200
+            android-em.*: 7200
             default: 3600
     e10s: false
     mozharness:
         mochitest-flavor: chrome
         extra-options:
             by-test-platform:
-                android.*:
+                android-em.*:
                     - --test-suite=mochitest-chrome
                 default: []
         chunked:
             by-test-platform:
-                android.*: false
+                android-em.*: false
                 default: true
 
 mochitest-clipboard:
     description: "Mochitest clipboard run"
     suite: mochitest/clipboard
     treeherder-symbol: M(cl)
     loopback-video: true
     instance-size: xlarge
@@ -196,17 +199,17 @@ mochitest-clipboard:
     e10s:
         by-test-platform:
             linux32/debug: both
             default: true
     mozharness:
         mochitest-flavor: plain
         extra-options:
             by-test-platform:
-                android.*:
+                android-em.*:
                     # note that Android runs fewer suites than other platforms
                     - --test-suite=mochitest-plain-clipboard
                 default:
                     - --mochitest-suite=plain-clipboard,chrome-clipboard,browser-chrome-clipboard
 
 mochitest-devtools-chrome:
     description: "Mochitest devtools-chrome run"
     suite:
@@ -240,28 +243,28 @@ mochitest-devtools-chrome:
 mochitest-gpu:
     description: "Mochitest GPU run"
     suite: mochitest/gpu
     treeherder-symbol: M(gpu)
     run-on-projects: built-projects
     loopback-video: true
     instance-size:
         by-test-platform:
-            android.*: xlarge
+            android-em.*: xlarge
             default: default
     virtualization: virtual-with-gpu
     e10s:
         by-test-platform:
             linux32/debug: both
             default: true
     mozharness:
         mochitest-flavor: plain
         extra-options:
             by-test-platform:
-                android.*:
+                android-em.*:
                     # note that Android runs fewer suites than other platforms
                     - --test-suite=mochitest-plain-gpu
                 default:
                     - --mochitest-suite=plain-gpu,chrome-gpu,browser-chrome-gpu
     tier:
         by-test-platform:
             linux64-qr/.*: 1
             default: default
@@ -277,34 +280,35 @@ mochitest-media:
     run-on-projects: built-projects
     loopback-video: true
     virtualization:
         by-test-platform:
             windows10-64-qr/.*: virtual-with-gpu
             default: virtual
     instance-size:
         by-test-platform:
-            android.*: xlarge
+            android-em.*: xlarge
             default: large
     chunks:
         by-test-platform:
-            android-7.0-x86/opt: 1
+            android-em-7.0-x86/opt: 1
             macosx64.*: 1
             windows10-64.*: 1
             default: 3
     mozharness:
         mochitest-flavor: plain
         chunked:
             by-test-platform:
                 android.*: false
                 macosx64.*: false
                 windows10-64.*: false
                 default: true
     tier:
         by-test-platform:
+            android-hw.*: 3
             linux64-qr/.*: 1
             default: default
 
 mochitest-plain-headless:
     description: "Mochitest plain headless run"
     suite: mochitest/plain-chunked
     treeherder-symbol: M(h)
     loopback-video: true
@@ -345,38 +349,38 @@ mochitest-webgl:
     treeherder-symbol: M(gl)
     run-on-projects:
         by-test-platform:
             windows10-64-ccov/.*: []  # Do not run on Windows ccov, see bug 1419475.
             default: built-projects
     virtualization: virtual-with-gpu
     chunks:
         by-test-platform:
-            android.*: 10
-            android-7.0-x86/opt: 1
+            android-em-7.0-x86/opt: 1
+            android-em.*: 10
             windows.*: 8
             default: 3
     e10s:
         by-test-platform:
             linux32/debug: both
             default: true
     loopback-video: true
     max-run-time:
         by-test-platform:
             windows.*: 5400
-            android.*: 7200
+            android-em.*: 7200
             default: 3600
     instance-size:
         by-test-platform:
-            android.*: xlarge
+            android-em.*: xlarge
             default: default
     # Bug 1296733: llvmpipe with mesa 9.2.1 lacks thread safety
     allow-software-gl-layers: false
     mozharness:
         mochitest-flavor: plain
         chunked:
             by-test-platform:
-                android.*: false
+                android-em.*: false
                 default: true
     tier:
         by-test-platform:
             linux64-qr/.*: 1
             default: default
--- a/taskcluster/ci/test/reftest.yml
+++ b/taskcluster/ci/test/reftest.yml
@@ -1,24 +1,24 @@
 job-defaults:
     target:
         by-test-platform:
-            android-7.0-x86/opt: geckoview-androidTest.apk
+            android-em-7.0-x86/opt: geckoview-androidTest.apk
             default: null
     mozharness:
         script:
             by-test-platform:
-                android.*: android_emulator_unittest.py
+                android-em.*: android_emulator_unittest.py
                 default: desktop_unittest.py
         config:
             by-test-platform:
-                android-7.0-x86/opt:
+                android-em-7.0-x86/opt:
                     - android/android_common.py
                     - android/androidx86_7_0.py
-                android.*:
+                android-em.*:
                     - android/android_common.py
                     - android/androidarm_4_3.py
                 linux.*:
                     - unittests/linux_unittest.py
                     - remove_executables.py
                 macosx.*:
                     - unittests/mac_unittest.py
                 windows.*:
@@ -26,27 +26,27 @@ job-defaults:
 
 crashtest:
     description: "Crashtest run"
     suite: reftest/crashtest
     treeherder-symbol: R(C)
     run-on-projects: built-projects
     instance-size:
         by-test-platform:
-            android.*: xlarge
+            android-em.*: xlarge
             default: default
     virtualization:
         by-test-platform:
             windows10-64-qr/.*: virtual-with-gpu
             default: virtual
     chunks:
         by-test-platform:
-            android-4.3-arm7-api-16/debug: 10
-            android-7.0-x86/opt: 1
-            android.*: 4
+            android-em-4.3-arm7-api-16/debug: 10
+            android-em-7.0-x86/opt: 1
+            android-em.*: 4
             default: 1
     e10s:
         by-test-platform:
             linux32/debug: both
             default: true
     tier:
         by-test-platform:
             .*-qr/.*: 1
@@ -55,77 +55,77 @@ crashtest:
 jsreftest:
     description: "JS Reftest run"
     suite: reftest/jsreftest
     schedules-component: jsreftest  # scheduling for this reftest is different from the others..
     treeherder-symbol: R(J)
     run-on-projects: built-projects
     instance-size:
         by-test-platform:
-            android.*: xlarge
+            android-em.*: xlarge
             default: default
     chunks:
         by-test-platform:
-            android-4.3-arm7-api-16/debug: 100
-            android-7.0-x86/opt: 3
-            android.*: 40
+            android-em-4.3-arm7-api-16/debug: 100
+            android-em-7.0-x86/opt: 3
+            android-em.*: 40
             windows.*: 2
             windows10-64-ccov/debug: 5
             linux64-ccov/.*: 5
             linux64-qr/.*: 4
             linux32/debug: 5
             macosx.*: 2
             default: 3
     max-run-time:
         by-test-platform:
-            android.*: 7200
+            android-em.*: 7200
             windows10-64-ccov/debug: 7200
             default: 3600
     tier:
         by-test-platform:
             linux64-qr/.*: 1
             default: default
 
 reftest:
     description: "Reftest run"
     suite: reftest/reftest
     treeherder-symbol: R(R)
     run-on-projects: built-projects
     instance-size:
         by-test-platform:
-            android.*: xlarge
+            android-em.*: xlarge
             default: default
     virtualization:
         by-test-platform:
             windows10.*: hardware
             default: virtual-with-gpu
     chunks:
         by-test-platform:
-            android-4.3-arm7-api-16/debug: 56
-            android-7.0-x86/opt: 5
-            android.*: 28
+            android-em-4.3-arm7-api-16/debug: 56
+            android-em-7.0-x86/opt: 5
+            android-em.*: 28
             macosx64.*/opt: 2
             macosx64.*/debug: 3
             windows.*/opt: 2
             windows.*/debug: 4
             windows10-64-ccov/debug: 6
             default: 8
     e10s:
         by-test-platform:
             linux32/debug: both
             default: true
     max-run-time:
         by-test-platform:
-            android.*: 7200
+            android-em.*: 7200
             windows10-64-ccov/debug: 5400
             default: 3600
     mozharness:
         chunked:
             by-test-platform:
-                android.*: false
+                android-em.*: false
                 macosx64/opt: false
                 windows10-64.*/opt: false
                 default: true
     tier:
         by-test-platform:
             linux64-qr/.*: 1
             default: default
 
--- a/taskcluster/ci/test/test-platforms.yml
+++ b/taskcluster/ci/test/test-platforms.yml
@@ -268,33 +268,54 @@ macosx64-qr/debug:
         - macosx64-qr-tests
 
 macosx64-ccov/debug:
     build-platform: macosx64-ccov/debug
     test-sets:
         - macosx64-tests
 
 ##
-# Android platforms (matching /android.*/)
+# Android platforms (matching /android-em.*/)
+#
+# android-em test platforms execute on android emulators.
 
-android-4.3-arm7-api-16/debug:
+android-em-4.3-arm7-api-16/debug:
     build-platform: android-api-16/debug
     test-sets:
         - android-common-tests
         - android-gradle-tests
 
-android-4.3-arm7-api-16/opt:
+android-em-4.3-arm7-api-16/opt:
     build-platform: android-api-16/opt
     test-sets:
         - android-common-tests
         - android-opt-tests
         - android-gradle-tests
 
-android-4.2-x86/opt:
+android-em-4.2-x86/opt:
     build-platform: android-x86/opt
     test-sets:
         - android-x86-tests
 
 # Coming soon!
-# android-7.0-x86/opt:
+# android-em-7.0-x86/opt:
 #     build-platform: android-x86/opt
 #     test-sets:
 #         - android-x86-kvm-tests
+
+# android-hw test platforms execute on real devices attached to Autophone hosts.
+
+# android-hw-p2-8-0 Google Pixel 2 Android 8.0
+
+android-hw-p2-8-0-arm7-api-16/opt:
+    build-platform: android-api-16/opt
+    test-sets:
+        - android-hw-arm7-opt-unittests
+
+android-hw-p2-8-0-arm7-api-16/debug:
+    build-platform: android-api-16/debug
+    test-sets:
+        - android-hw-arm7-debug-unittests
+
+android-hw-p2-8-0-android-aarch64/opt:
+    build-platform: android-aarch64/opt
+    test-sets:
+        - android-hw-aarch64-opt-unittests
--- a/taskcluster/ci/test/test-sets.yml
+++ b/taskcluster/ci/test/test-sets.yml
@@ -363,8 +363,17 @@ android-x86-kvm-tests:
     - reftest
     - test-verify
 
 devtools-tests:
     - mochitest-devtools-chrome
 
 mochitest-headless:
     - mochitest-plain-headless
+
+android-hw-arm7-opt-unittests:
+    - mochitest-media
+
+android-hw-arm7-debug-unittests:
+    - mochitest-media
+
+android-hw-aarch64-opt-unittests:
+    - jittest
--- a/taskcluster/ci/test/xpcshell.yml
+++ b/taskcluster/ci/test/xpcshell.yml
@@ -1,20 +1,20 @@
 job-defaults:
     mozharness:
         script:
             by-test-platform:
-                android.*: android_emulator_unittest.py
+                android-em.*: android_emulator_unittest.py
                 default: desktop_unittest.py
         config:
             by-test-platform:
-                android-4.2-x86/opt:
+                android-em-4.2-x86/opt:
                     - android/android_common.py
                     - android/androidx86.py
-                android.*:
+                android-em.*:
                     - android/android_common.py
                     - android/androidarm_4_3.py
                 linux.*:
                     - unittests/linux_unittest.py
                     - remove_executables.py
                 macosx.*:
                     - unittests/mac_unittest.py
                 windows.*:
@@ -35,28 +35,28 @@ xpcshell:
     run-on-projects:
         by-test-platform:
             windows10-64-asan/opt: []  # No XPCShell on ASAN yet
             default: built-projects
     chunks:
         by-test-platform:
             linux32/debug: 12
             linux64/debug: 10
-            android-4.2-x86/opt: 6
-            android-4.3-arm7-api-16/debug: 12
+            android-em-4.2-x86/opt: 6
+            android-em-4.3-arm7-api-16/debug: 12
             macosx.*: 1
             windows.*: 1
             windows10-64-ccov/debug: 8
             default: 8
     instance-size:
         by-test-platform:
-            android.*: xlarge
+            android-em.*: xlarge
             default: default
     max-run-time:
         by-test-platform:
-            android-4.3-arm7-api-16/debug: 7200
+            android-em-4.3-arm7-api-16/debug: 7200
             default: 5400
     e10s: false
     allow-software-gl-layers: false
     tier:
         by-test-platform:
             windows10-64-asan.*: 3
             default: default
--- a/taskcluster/taskgraph/transforms/job/mozharness_test.py
+++ b/taskcluster/taskgraph/transforms/job/mozharness_test.py
@@ -378,8 +378,91 @@ def mozharness_test_on_native_engine(con
             for i, c in enumerate(command):
                 if isinstance(c, basestring) and c.startswith('--test-suite'):
                     command[i] += suffix
 
     if 'download-symbols' in mozharness:
         download_symbols = mozharness['download-symbols']
         download_symbols = {True: 'true', False: 'false'}.get(download_symbols, download_symbols)
         command.append('--download-symbols=' + download_symbols)
+
+
+@run_job_using('script-engine-autophone', 'mozharness-test', schema=mozharness_test_run_schema)
+def mozharness_test_on_script_engine_autophone(config, job, taskdesc):
+    test = taskdesc['run']['test']
+    mozharness = test['mozharness']
+    worker = taskdesc['worker']
+    is_talos = test['suite'] == 'talos'
+    if worker['os'] != 'linux':
+        raise Exception('os: {} not supported on script-engine-autophone'.format(worker['os']))
+
+    installer_url = get_artifact_url('<build>', mozharness['build-artifact-name'])
+    mozharness_url = get_artifact_url('<build>',
+                                      'public/build/mozharness.zip')
+
+    artifacts = [
+        # (artifact name prefix, in-image path)
+        ("public/test/", "/builds/worker/artifacts"),
+        ("public/logs/", "/builds/worker/workspace/build/upload/logs"),
+        ("public/test_info/", "/builds/worker/workspace/build/blobber_upload_dir"),
+    ]
+
+    worker['artifacts'] = [{
+        'name': prefix,
+        'path': path,
+        'type': 'directory',
+    } for (prefix, path) in artifacts]
+
+    if test['reboot']:
+        worker['reboot'] = test['reboot']
+
+    worker['env'] = env = {
+        'GECKO_HEAD_REPOSITORY': config.params['head_repository'],
+        'GECKO_HEAD_REV': config.params['head_rev'],
+        'MOZHARNESS_CONFIG': ' '.join(mozharness['config']),
+        'MOZHARNESS_SCRIPT': mozharness['script'],
+        'MOZHARNESS_URL': {'task-reference': mozharness_url},
+        'MOZILLA_BUILD_URL': {'task-reference': installer_url},
+        "MOZ_NO_REMOTE": '1',
+        "NO_EM_RESTART": '1',
+        "XPCOM_DEBUG_BREAK": 'warn',
+        "NO_FAIL_ON_TEST_ERRORS": '1',
+        "MOZ_HIDE_RESULTS_TABLE": '1',
+        "MOZ_NODE_PATH": "/usr/local/bin/node",
+        'MOZ_AUTOMATION': '1',
+        'WORKSPACE': '/builds/worker/workspace',
+        'TASKCLUSTER_WORKER_TYPE': job['worker-type'],
+
+    }
+    # talos tests don't need Xvfb
+    if is_talos:
+        env['NEED_XVFB'] = 'false'
+
+    script = 'test-linux.sh'
+    worker['context'] = '{}/raw-file/{}/taskcluster/scripts/tester/{}'.format(
+        config.params['head_repository'], config.params['head_rev'], script
+    )
+
+    command = worker['command'] = ["./{}".format(script)]
+    command.extend([
+        {"task-reference": "--installer-url=" + installer_url},
+        {"task-reference": "--test-packages-url=" + test_packages_url(taskdesc)},
+    ])
+    if mozharness.get('include-blob-upload-branch'):
+        command.append('--blob-upload-branch=' + config.params['project'])
+    command.extend(mozharness.get('extra-options', []))
+
+    # TODO: remove the need for run['chunked']
+    if mozharness.get('chunked') or test['chunks'] > 1:
+        # Implement mozharness['chunking-args'], modifying command in place
+        if mozharness['chunking-args'] == 'this-chunk':
+            command.append('--total-chunk={}'.format(test['chunks']))
+            command.append('--this-chunk={}'.format(test['this-chunk']))
+        elif mozharness['chunking-args'] == 'test-suite-suffix':
+            suffix = mozharness['chunk-suffix'].replace('<CHUNK>', str(test['this-chunk']))
+            for i, c in enumerate(command):
+                if isinstance(c, basestring) and c.startswith('--test-suite'):
+                    command[i] += suffix
+
+    if 'download-symbols' in mozharness:
+        download_symbols = mozharness['download-symbols']
+        download_symbols = {True: 'true', False: 'false'}.get(download_symbols, download_symbols)
+        command.append('--download-symbols=' + download_symbols)
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -405,16 +405,46 @@ task_description_schema = Schema({
             # task image path from which to read artifact
             Required('path'): basestring,
 
             # name of the produced artifact (root of the names for
             # type=directory)
             Required('name'): basestring,
         }],
     }, {
+        Required('implementation'): 'script-engine-autophone',
+        Required('os'): Any('macosx', 'linux'),
+
+        # A link for an executable to download
+        Optional('context'): basestring,
+
+        # Tells the worker whether machine should reboot
+        # after the task is finished.
+        Optional('reboot'):
+            Any(False, 'always', 'never', 'on-exception', 'on-failure'),
+
+        # the command to run
+        Optional('command'): [taskref_or_string],
+
+        # environment variables
+        Optional('env'): {basestring: taskref_or_string},
+
+        # artifacts to extract from the task image after completion
+        Optional('artifacts'): [{
+            # type of artifact -- simple file, or recursive directory
+            Required('type'): Any('file', 'directory'),
+
+            # task image path from which to read artifact
+            Required('path'): basestring,
+
+            # name of the produced artifact (root of the names for
+            # type=directory)
+            Required('name'): basestring,
+        }],
+    }, {
         Required('implementation'): 'scriptworker-signing',
 
         # the maximum time to run, in seconds
         Required('max-run-time'): int,
 
         # list of artifact URLs for the artifacts that should be signed
         Required('upstream-artifacts'): [{
             # taskId of the task with the artifact
@@ -1205,16 +1235,39 @@ def build_macosx_engine_payload(config, 
     }
     if worker.get('reboot'):
         task_def['payload'] = worker['reboot']
 
     if task.get('needs-sccache'):
         raise Exception('needs-sccache not supported in native-engine')
 
 
+@payload_builder('script-engine-autophone')
+def build_script_engine_autophone_payload(config, task, task_def):
+    worker = task['worker']
+    artifacts = map(lambda artifact: {
+        'name': artifact['name'],
+        'path': artifact['path'],
+        'type': artifact['type'],
+        'expires': task_def['expires'],
+    }, worker.get('artifacts', []))
+
+    task_def['payload'] = {
+        'context': worker['context'],
+        'command': worker['command'],
+        'env': worker['env'],
+        'artifacts': artifacts,
+    }
+    if worker.get('reboot'):
+        task_def['payload'] = worker['reboot']
+
+    if task.get('needs-sccache'):
+        raise Exception('needs-sccache not supported in taskcluster-worker')
+
+
 transforms = TransformSequence()
 
 
 @transforms.add
 def set_defaults(config, tasks):
     for task in tasks:
         task.setdefault('shipping-phase', None)
         task.setdefault('shipping-product', None)
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -539,31 +539,33 @@ def set_treeherder_machine_platform(conf
         'linux64-pgo/opt': 'linux64/pgo',
         'macosx64/debug': 'osx-10-10/debug',
         'macosx64/opt': 'osx-10-10/opt',
         'win64-asan/opt': 'windows10-64/asan',
         'win32-pgo/opt': 'windows7-32/pgo',
         'win64-pgo/opt': 'windows10-64/pgo',
         # The build names for Android platforms have partially evolved over the
         # years and need to be translated.
-        'android-api-16/debug': 'android-4-3-armv7-api16/debug',
-        'android-api-16/opt': 'android-4-3-armv7-api16/opt',
-        'android-x86/opt': 'android-4-2-x86/opt',
+        'android-api-16/debug': 'android-em-4-3-armv7-api16/debug',
+        'android-api-16/opt': 'android-em-4-3-armv7-api16/opt',
+        'android-x86/opt': 'android-em-4-2-x86/opt',
         'android-api-16-gradle/opt': 'android-api-16-gradle/opt',
     }
     for test in tests:
         # For most desktop platforms, the above table is not used for "regular"
         # builds, so we'll always pick the test platform here.
         # On macOS though, the regular builds are in the table.  This causes a
         # conflict in `verify_task_graph_symbol` once you add a new test
         # platform based on regular macOS builds, such as for QR.
         # Since it's unclear if the regular macOS builds can be removed from
         # the table, workaround the issue for QR.
         if '-qr' in test['test-platform']:
             test['treeherder-machine-platform'] = test['test-platform']
+        elif 'android-hw' in test['test-platform']:
+            test['treeherder-machine-platform'] = test['test-platform']
         else:
             test['treeherder-machine-platform'] = translation.get(
                 test['build-platform'], test['test-platform'])
         yield test
 
 
 @transforms.add
 def set_tier(config, tests):
@@ -595,19 +597,19 @@ def set_tier(config, tests):
                                          'windows10-64-pgo/opt',
                                          'windows10-64-devedition/opt',
                                          'windows10-64-nightly/opt',
                                          'windows10-64-asan/opt',
                                          'macosx64/opt',
                                          'macosx64/debug',
                                          'macosx64-nightly/opt',
                                          'macosx64-devedition/opt',
-                                         'android-4.3-arm7-api-16/opt',
-                                         'android-4.3-arm7-api-16/debug',
-                                         'android-4.2-x86/opt']:
+                                         'android-em-4.3-arm7-api-16/opt',
+                                         'android-em-4.3-arm7-api-16/debug',
+                                         'android-em-4.2-x86/opt']:
                 test['tier'] = 1
             else:
                 test['tier'] = 2
 
         yield test
 
 
 @transforms.add
@@ -680,16 +682,18 @@ def handle_suite_category(config, tests)
         test['attributes']['unittest_flavor'] = flavor
 
         script = test['mozharness']['script']
         category_arg = None
         if suite.startswith('test-verify') or suite.startswith('test-coverage'):
             pass
         elif script == 'android_emulator_unittest.py':
             category_arg = '--test-suite'
+        elif script == 'android_hardware_unittest.py':
+            category_arg = '--test-suite'
         elif script == 'desktop_unittest.py':
             category_arg = '--{}-suite'.format(suite)
 
         if category_arg:
             test['mozharness'].setdefault('extra-options', [])
             extra = test['mozharness']['extra-options']
             if not any(arg.startswith(category_arg) for arg in extra):
                 extra.append('{}={}'.format(category_arg, flavor))
@@ -923,16 +927,26 @@ def set_worker_type(config, tests):
                 win_worker_type_platform = WINDOWS_WORKER_TYPES['windows10-64']
             else:
                 # the other jobs run on a vm which may or may not be a win10 vm
                 win_worker_type_platform = WINDOWS_WORKER_TYPES[
                     test_platform.split('/')[0]
                 ]
             # now we have the right platform set the worker type accordingly
             test['worker-type'] = win_worker_type_platform[test['virtualization']]
+        elif test_platform.startswith('android-hw-g5'):
+            if test['suite'] == 'raptor':
+                test['worker-type'] = 'proj-autophone/gecko-t-ap-perf-g5'
+            else:
+                test['worker-type'] = 'proj-autophone/gecko-t-ap-unit-g5'
+        elif test_platform.startswith('android-hw-p2'):
+            if test['suite'] == 'raptor':
+                test['worker-type'] = 'proj-autophone/gecko-t-ap-perf-p2'
+            else:
+                test['worker-type'] = 'proj-autophone/gecko-t-ap-unit-p2'
         elif test_platform.startswith('linux') or test_platform.startswith('android'):
             if test.get('suite', '') == 'talos' and \
                  not test['build-platform'].startswith('linux64-ccov'):
                 test['worker-type'] = 'releng-hardware/gecko-t-linux-talos'
             else:
                 test['worker-type'] = LINUX_WORKER_TYPES[test['instance-size']]
         else:
             raise Exception("unknown test_platform {}".format(test_platform))
--- a/taskcluster/taskgraph/try_option_syntax.py
+++ b/taskcluster/taskgraph/try_option_syntax.py
@@ -127,17 +127,20 @@ UNITTEST_PLATFORM_PRETTY_NAMES = {
         'linux64-asan',
         'linux64-stylo-sequential'
     ],
     'x64': [
         'linux64',
         'linux64-asan',
         'linux64-stylo-sequential'
     ],
-    'Android 4.3': ['android-4.3-arm7-api-16'],
+    'Android 4.3 Emulator': ['android-em-4.3-arm7-api-16'],
+    'Android 7.0 Moto G5 32bit': ['android-hw-g5-7.0-arm7-api-16'],
+    'Android 8.0 Google Pixel 2 32bit': ['android-hw-p2-8.0-arm7-api-16'],
+    'Android 8.0 Google Pixel 2 64bit': ['android-hw-p2-8.0-android-aarch64'],
     '10.10': ['macosx64'],
     # other commonly-used substrings for platforms not yet supported with
     # in-tree taskgraphs:
     # '10.10.5': [..TODO..],
     # '10.6': [..TODO..],
     # '10.8': [..TODO..],
     # 'Android 2.3 API9': [..TODO..],
     'Windows 7':  ['windows7-32'],
--- a/taskcluster/taskgraph/util/workertypes.py
+++ b/taskcluster/taskgraph/util/workertypes.py
@@ -41,16 +41,20 @@ WORKER_TYPES = {
     'scriptworker-prov-v1/balrogworker-v1': ('balrog', None),
     'scriptworker-prov-v1/beetmoverworker-v1': ('beetmover', None),
     'scriptworker-prov-v1/pushapk-v1': ('push-apk', None),
     "scriptworker-prov-v1/signing-linux-v1": ('scriptworker-signing', None),
     "scriptworker-prov-v1/shipit": ('shipit', None),
     "scriptworker-prov-v1/shipit-dev": ('shipit', None),
     "scriptworker-prov-v1/treescript-v1": ('treescript', None),
     'releng-hardware/gecko-t-osx-1010': ('generic-worker', 'macosx'),
+    'proj-autophone/gecko-t-ap-perf-g5': ('script-engine-autophone', 'linux'),
+    'proj-autophone/gecko-t-ap-unit-g5': ('script-engine-autophone', 'linux'),
+    'proj-autophone/gecko-t-ap-perf-p2': ('script-engine-autophone', 'linux'),
+    'proj-autophone/gecko-t-ap-unit-p2': ('script-engine-autophone', 'linux'),
 }
 
 
 def worker_type_implementation(worker_type):
     """Get the worker implementation and OS for the given workerType, where the
     OS represents the host system, not the target OS, in the case of
     cross-compiles."""
     # assume that worker types for all levels are the same implementation
--- a/testing/awsy/awsy/test_base_memory_usage.py
+++ b/testing/awsy/awsy/test_base_memory_usage.py
@@ -21,16 +21,17 @@ CHECKPOINTS = [
     },
 ]
 
 # A description of each perfherder suite and the path to its values.
 PERF_SUITES = [
     { 'name': "Base Content Resident Unique Memory", 'node': "resident-unique" },
     { 'name': "Base Content Heap Unclassified", 'node': "explicit/heap-unclassified" },
     { 'name': "Base Content JS", 'node': "js-main-runtime/" },
+    { 'name': "Base Content Explicit", 'node': "explicit/" },
 ]
 
 class TestMemoryUsage(AwsyTestCase):
     """
     Provides a base case test that just loads about:memory and reports the
     memory usage of a single content process.
     """
 
--- a/testing/awsy/conf/base-prefs.json
+++ b/testing/awsy/conf/base-prefs.json
@@ -5,10 +5,11 @@
     "network.proxy.socks": "localhost",
     "network.proxy.socks_port": 90000,
     "network.proxy.socks_remote_dns": true,
     "network.proxy.type": 1,
     "plugin.disable": true,
     "startup.homepage_override_url": "",
     "startup.homepage_welcome_url": "",
     "browser.startup.homepage": "about:blank",
-    "browser.newtabpage.enabled": false
+    "browser.newtabpage.enabled": false,
+    "security.sandbox.content.level": 0
 }
--- a/testing/awsy/conf/prefs.json
+++ b/testing/awsy/conf/prefs.json
@@ -3,10 +3,11 @@
     "javascript.options.asyncstack": false,
     "image.mem.surfacecache.min_expiration_ms": 10000,
     "network.proxy.socks": "localhost",
     "network.proxy.socks_port": 90000,
     "network.proxy.socks_remote_dns": true,
     "network.proxy.type": 1,
     "plugin.disable": true,
     "startup.homepage_override_url": "",
-    "startup.homepage_welcome_url": ""
+    "startup.homepage_welcome_url": "",
+    "security.sandbox.content.level": 0
 }
--- a/testing/awsy/mach_commands.py
+++ b/testing/awsy/mach_commands.py
@@ -152,16 +152,22 @@ class MachCommands(MachCommandBase):
             if 'DMD' not in os.environ:
                 os.environ['DMD'] = '1'
 
             # Work around a startup crash with DMD on windows
             if mozinfo.os == 'win':
                 kwargs['pref'] = 'security.sandbox.content.level:0'
                 self.log(logging.WARNING, 'awsy', {},
                     'Forcing \'security.sandbox.content.level\' = 0 because DMD is enabled.')
+            elif mozinfo.os == 'mac':
+                # On mac binary is in MacOS and dmd.py is in Resources, ie:
+                #   Name.app/Contents/MacOS/libdmd.dylib
+                #   Name.app/Contents/Resources/dmd.py
+                bin_dir = os.path.join(bin_dir, "../Resources/")
+
 
             # Also add the bin dir to the python path so we can use dmd.py
             if bin_dir not in sys.path:
                 sys.path.append(bin_dir)
 
         for k, v in kwargs.iteritems():
             setattr(args, k, v)
 
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/configs/android/android_hw.py
@@ -0,0 +1,343 @@
+import os
+
+config = {
+    "robocop_package_name": "org.mozilla.roboexample.test",
+    "marionette_address": "%(device_ip)s:2828",
+    "marionette_test_manifest": "unit-tests.ini",
+    "exes": {},
+    "log_tbpl_level": "info",
+    "log_raw_level": "info",
+    "env": {
+        "DISPLAY": ":0.0",
+        "PATH": "%(PATH)s",
+        "MINIDUMP_SAVEPATH": "%(abs_work_dir)s/../minidumps"
+    },
+    "default_actions": [
+        'clobber',
+        'download-and-extract',
+        'create-virtualenv',
+        'verify-device',
+        'install',
+        'run-tests',
+    ],
+    # from android_common.py
+    "download_tooltool": True,
+    "download_minidump_stackwalk": True,
+    # hostutils_manifest_path is relative to branch's root in hg.mozilla.org.
+    "hostutils_manifest_path": "testing/config/tooltool-manifests/linux64/hostutils.manifest",
+    "tooltool_cache": "/builds/worker/tooltool_cache",
+    "tooltool_servers": ['https://api.pub.build.mozilla.org/tooltool/'],
+    # minidump_tooltool_manifest_path is relative to workspace/build/tests/
+    "minidump_tooltool_manifest_path": "config/tooltool-manifests/linux64/releng.manifest",
+    "find_links": [
+        "https://pypi.pub.build.mozilla.org/pub",
+    ],
+    "pip_index": False,
+    "suite_definitions": {
+        "mochitest": {
+            "run_filename": "runtestsremote.py",
+            "testsdir": "mochitest",
+            "options": [
+                "--app=%(app)s",
+                "--remote-webserver=%(remote_webserver)s",
+                "--xre-path=%(xre_path)s",
+                "--utility-path=%(utility_path)s",
+                "--http-port=%(http_port)s",
+                "--ssl-port=%(ssl_port)s",
+                "--certificate-path=%(certs_path)s",
+                "--symbols-path=%(symbols_path)s",
+                "--quiet",
+                "--log-raw=%(raw_log_file)s",
+                "--log-raw-level=%(log_raw_level)s",
+                "--log-errorsummary=%(error_summary_file)s",
+                "--log-tbpl-level=%(log_tbpl_level)s",
+                "--extra-profile-file=fonts",
+                "--extra-profile-file=hyphenation",
+                "--screenshot-on-fail",
+                "--deviceSerial=%(device_serial)s",
+            ],
+        },
+        "mochitest-gl": {
+            "run_filename": "runtestsremote.py",
+            "testsdir": "mochitest",
+            "options": [
+                "--app=%(app)s",
+                "--remote-webserver=%(remote_webserver)s",
+                "--xre-path=%(xre_path)s",
+                "--utility-path=%(utility_path)s",
+                "--http-port=%(http_port)s",
+                "--ssl-port=%(ssl_port)s",
+                "--certificate-path=%(certs_path)s",
+                "--symbols-path=%(symbols_path)s",
+                "--quiet",
+                "--log-raw=%(raw_log_file)s",
+                "--log-raw-level=%(log_raw_level)s",
+                "--log-errorsummary=%(error_summary_file)s",
+                "--log-tbpl-level=%(log_tbpl_level)s",
+                "--screenshot-on-fail",
+                "--subsuite=webgl",
+                "--deviceSerial=%(device_serial)s",
+            ],
+        },
+        "mochitest-chrome": {
+            "run_filename": "runtestsremote.py",
+            "testsdir": "mochitest",
+            "options": [
+                "--app=%(app)s",
+                "--remote-webserver=%(remote_webserver)s",
+                "--xre-path=%(xre_path)s",
+                "--utility-path=%(utility_path)s",
+                "--http-port=%(http_port)s",
+                "--ssl-port=%(ssl_port)s",
+                "--certificate-path=%(certs_path)s",
+                "--symbols-path=%(symbols_path)s",
+                "--quiet",
+                "--log-raw=%(raw_log_file)s",
+                "--log-raw-level=%(log_raw_level)s",
+                "--log-errorsummary=%(error_summary_file)s",
+                "--log-tbpl-level=%(log_tbpl_level)s",
+                "--extra-profile-file=fonts",
+                "--extra-profile-file=hyphenation",
+                "--screenshot-on-fail",
+                "--flavor=chrome",
+                "--deviceSerial=%(device_serial)s",
+            ],
+        },
+        "mochitest-plain-gpu": {
+            "run_filename": "runtestsremote.py",
+            "testsdir": "mochitest",
+            "options": [
+                "--app=%(app)s",
+                "--remote-webserver=%(remote_webserver)s",
+                "--xre-path=%(xre_path)s",
+                "--utility-path=%(utility_path)s",
+                "--http-port=%(http_port)s",
+                "--ssl-port=%(ssl_port)s",
+                "--certificate-path=%(certs_path)s",
+                "--symbols-path=%(symbols_path)s",
+                "--quiet",
+                "--log-raw=%(raw_log_file)s",
+                "--log-raw-level=%(log_raw_level)s",
+                "--log-errorsummary=%(error_summary_file)s",
+                "--log-tbpl-level=%(log_tbpl_level)s",
+                "--screenshot-on-fail",
+                "--subsuite=gpu",
+                "--deviceSerial=%(device_serial)s",
+            ],
+        },
+        "mochitest-plain-clipboard": {
+            "run_filename": "runtestsremote.py",
+            "testsdir": "mochitest",
+            "options": [
+                "--app=%(app)s",
+                "--remote-webserver=%(remote_webserver)s",
+                "--xre-path=%(xre_path)s",
+                "--utility-path=%(utility_path)s",
+                "--http-port=%(http_port)s",
+                "--ssl-port=%(ssl_port)s",
+                "--certificate-path=%(certs_path)s",
+                "--symbols-path=%(symbols_path)s",
+                "--quiet",
+                "--log-raw=%(raw_log_file)s",
+                "--log-raw-level=%(log_raw_level)s",
+                "--log-errorsummary=%(error_summary_file)s",
+                "--log-tbpl-level=%(log_tbpl_level)s",
+                "--screenshot-on-fail",
+                "--subsuite=clipboard",
+                "--deviceSerial=%(device_serial)s",
+            ],
+        },
+        "mochitest-media": {
+            "run_filename": "runtestsremote.py",
+            "testsdir": "mochitest",
+            "options": [
+                "--app=%(app)s",
+                "--remote-webserver=%(remote_webserver)s",
+                "--xre-path=%(xre_path)s",
+                "--utility-path=%(utility_path)s",
+                "--http-port=%(http_port)s",
+                "--ssl-port=%(ssl_port)s",
+                "--certificate-path=%(certs_path)s",
+                "--symbols-path=%(symbols_path)s",
+                "--quiet",
+                "--log-raw=%(raw_log_file)s",
+                "--log-raw-level=%(log_raw_level)s",
+                "--log-errorsummary=%(error_summary_file)s",
+                "--log-tbpl-level=%(log_tbpl_level)s",
+                "--screenshot-on-fail",
+                "--chunk-by-runtime",
+                "--subsuite=media",
+                "--deviceSerial=%(device_serial)s",
+            ],
+        },
+        "robocop": {
+            "run_filename": "runrobocop.py",
+            "testsdir": "mochitest",
+            "options": [
+                "--app=%(app)s",
+                "--remote-webserver=%(remote_webserver)s",
+                "--xre-path=%(xre_path)s",
+                "--utility-path=%(utility_path)s",
+                "--certificate-path=%(certs_path)s",
+                "--symbols-path=%(symbols_path)s",
+                "--quiet",
+                "--log-raw=%(raw_log_file)s",
+                "--log-raw-level=%(log_raw_level)s",
+                "--log-errorsummary=%(error_summary_file)s",
+                "--log-tbpl-level=%(log_tbpl_level)s",
+                "--robocop-apk=../../robocop.apk",
+                "--deviceSerial=%(device_serial)s",
+            ],
+        },
+        "reftest": {
+            "run_filename": "remotereftest.py",
+            "testsdir": "reftest",
+            "options": [
+                "--app=%(app)s",
+                "--ignore-window-size",
+                "--remote-webserver=%(remote_webserver)s",
+                "--xre-path=%(xre_path)s",
+                "--utility-path=%(utility_path)s",
+                "--http-port=%(http_port)s",
+                "--ssl-port=%(ssl_port)s",
+                "--httpd-path", "%(modules_dir)s",
+                "--symbols-path=%(symbols_path)s",
+                "--extra-profile-file=fonts",
+                "--extra-profile-file=hyphenation",
+                "--suite=reftest",
+                "--log-raw=%(raw_log_file)s",
+                "--log-raw-level=%(log_raw_level)s",
+                "--log-errorsummary=%(error_summary_file)s",
+                "--log-tbpl-level=%(log_tbpl_level)s",
+                "--deviceSerial=%(device_serial)s",
+            ],
+            "tests": ["tests/layout/reftests/reftest.list",],
+        },
+        "crashtest": {
+            "run_filename": "remotereftest.py",
+            "testsdir": "reftest",
+            "options": [
+                "--app=%(app)s",
+                "--ignore-window-size",
+                "--remote-webserver=%(remote_webserver)s",
+                "--xre-path=%(xre_path)s",
+                "--utility-path=%(utility_path)s",
+                "--http-port=%(http_port)s",
+                "--ssl-port=%(ssl_port)s",
+                "--httpd-path",
+                "%(modules_dir)s",
+                "--symbols-path=%(symbols_path)s",
+                "--suite=crashtest",
+                "--log-tbpl-level=%(log_tbpl_level)s",
+                "--deviceSerial=%(device_serial)s",
+            ],
+            "tests": ["tests/testing/crashtest/crashtests.list",],
+        },
+        "jittest": {
+            "run_filename": "jit_test.py",
+            "testsdir": "jit-test/jit-test",
+            "options": [
+                "../../bin/js",
+                "--remote",
+                "-j",
+                "1",
+                "--localLib=../../bin",
+                "--no-slow",
+                "--no-progress",
+                "--format=automation",
+                "--jitflags=all",
+                "--deviceSerial=%(device_serial)s",
+            ],
+        },
+        "jsreftest": {
+            "run_filename": "remotereftest.py",
+            "testsdir": "reftest",
+            "options": [
+                "--app=%(app)s",
+                "--ignore-window-size",
+                "--remote-webserver=%(remote_webserver)s", "--xre-path=%(xre_path)s",
+                "--utility-path=%(utility_path)s", "--http-port=%(http_port)s",
+                "--ssl-port=%(ssl_port)s", "--httpd-path", "%(modules_dir)s",
+                "--symbols-path=%(symbols_path)s",
+                "--extra-profile-file=jsreftest/tests/user.js",
+                "--suite=jstestbrowser",
+                "--log-tbpl-level=%(log_tbpl_level)s",
+                "--deviceSerial=%(device_serial)s",
+            ],
+            "tests": ["../jsreftest/tests/jstests.list",],
+        },
+        "xpcshell": {
+            "run_filename": "remotexpcshelltests.py",
+            "testsdir": "xpcshell",
+            "install": False,
+            "options": [
+                "--xre-path=%(xre_path)s",
+                "--testing-modules-dir=%(modules_dir)s",
+                "--apk=%(installer_path)s",
+                "--no-logfiles",
+                "--symbols-path=%(symbols_path)s",
+                "--manifest=tests/xpcshell.ini",
+                "--log-raw=%(raw_log_file)s",
+                "--log-raw-level=%(log_raw_level)s",
+                "--log-errorsummary=%(error_summary_file)s",
+                "--log-tbpl-level=%(log_tbpl_level)s",
+                "--test-plugin-path=none",
+                "--deviceSerial=%(device_serial)s",
+                "--remoteTestRoot=/data/local/tests",
+            ],
+        },
+        "cppunittest": {
+            "run_filename": "remotecppunittests.py",
+            "testsdir": "cppunittest",
+            "install": False,
+            "options": [
+                "--symbols-path=%(symbols_path)s",
+                "--xre-path=%(xre_path)s",
+                "--localBinDir=../bin",
+                "--apk=%(installer_path)s",
+                ".",
+                "--deviceSerial=%(device_serial)s",
+            ],
+        },
+        "marionette": {
+            "run_filename": os.path.join("harness", "marionette_harness", "runtests.py"),
+            "testsdir": "marionette",
+            "options": [
+                "--app=fennec",
+                "--package=%(app)s",
+                "--address=%(address)s",
+                "%(test_manifest)s",
+                "--disable-e10s",
+                "--gecko-log=%(gecko_log)s",
+                "--log-raw=%(raw_log_file)s",
+                "--log-raw-level=%(log_raw_level)s",
+                "--log-errorsummary=%(error_summary_file)s",
+                "--log-tbpl-level=%(log_tbpl_level)s",
+                "--symbols-path=%(symbols_path)s",
+                "--startup-timeout=300",
+                "--device=%(device_serial)s",
+            ],
+        },
+        "geckoview": {
+            "run_filename": "rungeckoview.py",
+            "testsdir": "mochitest",
+            "options": [
+                "--utility-path=%(utility_path)s",
+                "--symbols-path=%(symbols_path)s",
+                "--deviceSerial=%(device_serial)s",
+            ],
+        },
+        "geckoview-junit": {
+            "run_filename": "runjunit.py",
+            "testsdir": "mochitest",
+            "options": [
+                "--certificate-path=%(certs_path)s",
+                "--remote-webserver=%(remote_webserver)s",
+                "--symbols-path=%(symbols_path)s",
+                "--utility-path=%(utility_path)s",
+                "--deviceSerial=%(device_serial)s",
+            ],
+        },
+
+    },  # end suite_definitions
+}
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/scripts/android_hardware_unittest.py
@@ -0,0 +1,723 @@
+#!/usr/bin/env python
+# ***** BEGIN LICENSE BLOCK *****
+# 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/.
+# ***** END LICENSE BLOCK *****
+
+import copy
+import datetime
+import glob
+import os
+import re
+import sys
+import signal
+import subprocess
+import time
+import tempfile
+
+# load modules from parent dir
+sys.path.insert(1, os.path.dirname(sys.path[0]))
+
+from mozharness.base.log import FATAL
+from mozharness.base.script import BaseScript, PreScriptAction, PostScriptAction
+from mozharness.mozilla.automation import TBPL_RETRY, EXIT_STATUS_DICT
+from mozharness.mozilla.mozbase import MozbaseMixin
+from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options
+from mozharness.mozilla.testing.codecoverage import CodeCoverageMixin
+
+
+class AndroidHardwareTest(TestingMixin, BaseScript, MozbaseMixin,
+                          CodeCoverageMixin):
+    config_options = [[
+        ["--test-suite"],
+        {"action": "store",
+         "dest": "test_suite",
+         "default": None
+         }
+    ], [
+        ["--adb-path"],
+        {"action": "store",
+         "dest": "adb_path",
+         "default": None,
+         "help": "Path to adb",
+         }
+    ], [
+        ["--total-chunk"],
+        {"action": "store",
+         "dest": "total_chunks",
+         "default": None,
+         "help": "Number of total chunks",
+         }
+    ], [
+        ["--this-chunk"],
+        {"action": "store",
+         "dest": "this_chunk",
+         "default": None,
+         "help": "Number of this chunk",
+         }
+    ], [
+        ["--log-raw-level"],
+        {"action": "store",
+         "dest": "log_raw_level",
+         "default": "info",
+         "help": "Set log level (debug|info|warning|error|critical|fatal)",
+         }
+    ], [
+        ["--log-tbpl-level"],
+        {"action": "store",
+         "dest": "log_tbpl_level",
+         "default": "info",
+         "help": "Set log level (debug|info|warning|error|critical|fatal)",
+         }
+    ]] + copy.deepcopy(testing_config_options)
+
+    app_name = None
+
+    def __init__(self, require_config_file=False):
+        super(AndroidHardwareTest, self).__init__(
+            config_options=self.config_options,
+            all_actions=['clobber',
+                         'download-and-extract',
+                         'create-virtualenv',
+                         'verify-device',
+                         'install',
+                         'run-tests',
+                         ],
+            require_config_file=require_config_file,
+            config={
+                'virtualenv_modules': [],
+                'virtualenv_requirements': [],
+                'require_test_zip': True,
+                # IP address of the host as seen from the device.
+                'remote_webserver': os.environ['HOST_IP'],
+            }
+        )
+
+        # these are necessary since self.config is read only
+        c = self.config
+        abs_dirs = self.query_abs_dirs()
+        self.adb_path = self.query_exe('adb')
+        self.logcat_file = None
+        self.logcat_proc = None
+        self.installer_url = c.get('installer_url')
+        self.installer_path = c.get('installer_path')
+        self.test_url = c.get('test_url')
+        self.test_packages_url = c.get('test_packages_url')
+        self.test_manifest = c.get('test_manifest')
+        self.robocop_path = os.path.join(abs_dirs['abs_work_dir'], "robocop.apk")
+        self.minidump_stackwalk_path = c.get("minidump_stackwalk_path")
+        self.device_name = os.environ['DEVICE_NAME']
+        self.device_serial = os.environ['DEVICE_SERIAL']
+        self.device_ip = os.environ['DEVICE_IP']
+        self.test_suite = c.get('test_suite')
+        self.this_chunk = c.get('this_chunk')
+        self.total_chunks = c.get('total_chunks')
+        if self.test_suite and self.test_suite not in self.config["suite_definitions"]:
+            # accept old-style test suite name like "mochitest-3"
+            m = re.match("(.*)-(\d*)", self.test_suite)
+            if m:
+                self.test_suite = m.group(1)
+                if self.this_chunk is None:
+                    self.this_chunk = m.group(2)
+        self.sdk_level = None
+        self.xre_path = None
+        self.log_raw_level = c.get('log_raw_level')
+        self.log_tbpl_level = c.get('log_tbpl_level')
+
+    def _query_tests_dir(self):
+        dirs = self.query_abs_dirs()
+        try:
+            test_dir = self.config["suite_definitions"][self.test_suite]["testsdir"]
+        except Exception:
+            test_dir = self.test_suite
+        return os.path.join(dirs['abs_test_install_dir'], test_dir)
+
+    def query_abs_dirs(self):
+        if self.abs_dirs:
+            return self.abs_dirs
+        abs_dirs = super(AndroidHardwareTest, self).query_abs_dirs()
+        dirs = {}
+        dirs['abs_test_install_dir'] = os.path.join(
+            abs_dirs['abs_work_dir'], 'tests')
+        dirs['abs_test_bin_dir'] = os.path.join(
+            abs_dirs['abs_work_dir'], 'tests', 'bin')
+        dirs['abs_xre_dir'] = os.path.join(
+            abs_dirs['abs_work_dir'], 'hostutils')
+        dirs['abs_modules_dir'] = os.path.join(
+            dirs['abs_test_install_dir'], 'modules')
+        dirs['abs_blob_upload_dir'] = os.path.join(
+            abs_dirs['abs_work_dir'], 'blobber_upload_dir')
+        dirs['abs_mochitest_dir'] = os.path.join(
+            dirs['abs_test_install_dir'], 'mochitest')
+        dirs['abs_reftest_dir'] = os.path.join(
+            dirs['abs_test_install_dir'], 'reftest')
+        dirs['abs_xpcshell_dir'] = os.path.join(
+            dirs['abs_test_install_dir'], 'xpcshell')
+        dirs['abs_marionette_dir'] = os.path.join(
+            dirs['abs_test_install_dir'], 'marionette', 'harness', 'marionette_harness')
+        dirs['abs_marionette_tests_dir'] = os.path.join(
+            dirs['abs_test_install_dir'], 'marionette', 'tests', 'testing',
+            'marionette', 'harness', 'marionette_harness', 'tests')
+
+        for key in dirs.keys():
+            if key not in abs_dirs:
+                abs_dirs[key] = dirs[key]
+        self.abs_dirs = abs_dirs
+        return self.abs_dirs
+
+    @PreScriptAction('create-virtualenv')
+    def _pre_create_virtualenv(self, action):
+        dirs = self.query_abs_dirs()
+        requirements = None
+        if self.test_suite == 'mochitest-media':
+            # mochitest-media is the only thing that needs this
+            requirements = os.path.join(dirs['abs_mochitest_dir'],
+                                        'websocketprocessbridge',
+                                        'websocketprocessbridge_requirements.txt')
+        elif self.test_suite == 'marionette':
+            requirements = os.path.join(dirs['abs_test_install_dir'],
+                                        'config', 'marionette_requirements.txt')
+        if requirements:
+            self.register_virtualenv_module(requirements=[requirements],
+                                            two_pass=True)
+
+    def _retry(self, max_attempts, interval, func, description, max_time=0):
+        '''
+        Execute func until it returns True, up to max_attempts times, waiting for
+        interval seconds between each attempt. description is logged on each attempt.
+        If max_time is specified, no further attempts will be made once max_time
+        seconds have elapsed; this provides some protection for the case where
+        the run-time for func is long or highly variable.
+        '''
+        status = False
+        attempts = 0
+        if max_time > 0:
+            end_time = datetime.datetime.now() + datetime.timedelta(seconds=max_time)
+        else:
+            end_time = None
+        while attempts < max_attempts and not status:
+            if (end_time is not None) and (datetime.datetime.now() > end_time):
+                self.info("Maximum retry run-time of %d seconds exceeded; "
+                          "remaining attempts abandoned" % max_time)
+                break
+            if attempts != 0:
+                self.info("Sleeping %d seconds" % interval)
+                time.sleep(interval)
+            attempts += 1
+            self.info(">> %s: Attempt #%d of %d" % (description, attempts, max_attempts))
+            status = func()
+        return status
+
+    def _run_with_timeout(self, timeout, cmd, quiet=False):
+        timeout_cmd = ['timeout', '%s' % timeout] + cmd
+        return self._run_proc(timeout_cmd, quiet=quiet)
+
+    def _run_proc(self, cmd, quiet=False):
+        self.info('Running %s' % subprocess.list2cmdline(cmd))
+        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        out, err = p.communicate()
+        if out and not quiet:
+            self.info('%s' % str(out.strip()))
+        if err and not quiet:
+            self.info('stderr: %s' % str(err.strip()))
+        return out, err
+
+    def _verify_adb(self):
+        self.info('Verifying adb connectivity')
+        self._run_with_timeout(180, [self.adb_path,
+                                     '-s',
+                                     self.device_serial,
+                                     'wait-for-device'])
+        return True
+
+    def _verify_adb_device(self):
+        out, _ = self._run_with_timeout(30, [self.adb_path, 'devices'])
+        if (self.device_serial in out) and ("device" in out):
+            return True
+        return False
+
+    def _is_boot_completed(self):
+        boot_cmd = [self.adb_path, '-s', self.device_serial,
+                    'shell', 'getprop', 'sys.boot_completed']
+        out, _ = self._run_with_timeout(30, boot_cmd)
+        if out.strip() == '1':
+            return True
+        return False
+
+    def _verify_device(self):
+        adb_ok = self._verify_adb()
+        if not adb_ok:
+            self.warning('Unable to communicate with adb')
+            return False
+        adb_device_ok = self._retry(4, 30, self._verify_adb_device,
+                                    "Verify device visible to adb")
+        if not adb_device_ok:
+            self.warning('Unable to communicate with device via adb')
+            return False
+        boot_ok = self._retry(30, 10, self._is_boot_completed, "Verify Android boot completed",
+                              max_time=330)
+        if not boot_ok:
+            self.warning('Unable to verify Android boot completion')
+            return False
+        return True
+
+    def _install_fennec_apk(self):
+        package = self._query_package_name()
+        if package:
+            cmd = [self.adb_path, '-s', self.device_serial,
+                   'uninstall', package]
+            out, err = self._run_with_timeout(300, cmd, True)
+        install_ok = False
+        if int(self.sdk_level) >= 23:
+            cmd = [self.adb_path, '-s', self.device_serial, 'install', '-r', '-g',
+                   self.installer_path]
+        else:
+            cmd = [self.adb_path, '-s', self.device_serial, 'install', '-r',
+                   self.installer_path]
+        out, err = self._run_with_timeout(300, cmd, True)
+        if 'Success' in out or 'Success' in err:
+            install_ok = True
+        return install_ok
+
+    def _install_robocop_apk(self):
+        install_ok = False
+        if int(self.sdk_level) >= 23:
+            cmd = [self.adb_path, '-s', self.device_serial, 'install', '-r', '-g',
+                   self.robocop_path]
+        else:
+            cmd = [self.adb_path, '-s', self.device_serial, 'install', '-r',
+                   self.robocop_path]
+        out, err = self._run_with_timeout(300, cmd, True)
+        if 'Success' in out or 'Success' in err:
+            install_ok = True
+        return install_ok
+
+    def _dump_host_state(self):
+        self._run_proc(['ps', '-ef'])
+        self._run_proc(['netstat', '-a', '-p', '-n', '-t', '-u'])
+
+    def _kill_processes(self, process_name):
+        p = subprocess.Popen(['ps', '-A'], stdout=subprocess.PIPE)
+        out, err = p.communicate()
+        self.info("Let's kill every process called %s" % process_name)
+        for line in out.splitlines():
+            if process_name in line:
+                pid = int(line.split(None, 1)[0])
+                self.info("Killing pid %d." % pid)
+                os.kill(pid, signal.SIGKILL)
+
+    def _restart_adbd(self):
+        self._run_with_timeout(30, [self.adb_path, 'kill-server'])
+        self._run_with_timeout(30, [self.adb_path, 'root'])
+
+    def _screenshot(self, prefix):
+        """
+           Save a screenshot of the entire screen to the blob upload directory.
+        """
+        dirs = self.query_abs_dirs()
+        utility = os.path.join(self.xre_path, "screentopng")
+        if not os.path.exists(utility):
+            self.warning("Unable to take screenshot: %s does not exist" % utility)
+            return
+        try:
+            tmpfd, filename = tempfile.mkstemp(prefix=prefix, suffix='.png',
+                                               dir=dirs['abs_blob_upload_dir'])
+            os.close(tmpfd)
+            self.info("Taking screenshot with %s; saving to %s" % (utility, filename))
+            subprocess.call([utility, filename], env=self.query_env())
+        except OSError, err:
+            self.warning("Failed to take screenshot: %s" % err.strerror)
+
+    def _query_package_name(self):
+        if self.app_name is None:
+            # For convenience, assume geckoview.test/geckoview_example when install
+            # target looks like geckoview.
+            if 'androidTest' in self.installer_path:
+                self.app_name = 'org.mozilla.geckoview.test'
+            elif 'geckoview' in self.installer_path:
+                self.app_name = 'org.mozilla.geckoview_example'
+        if self.app_name is None:
+            # Find appname from package-name.txt - assumes download-and-extract
+            # has completed successfully.
+            # The app/package name will typically be org.mozilla.fennec,
+            # but org.mozilla.firefox for release builds, and there may be
+            # other variations. 'aapt dump badging <apk>' could be used as an
+            # alternative to package-name.txt, but introduces a dependency
+            # on aapt, found currently in the Android SDK build-tools component.
+            apk_dir = self.abs_dirs['abs_work_dir']
+            self.apk_path = os.path.join(apk_dir, self.installer_path)
+            unzip = self.query_exe("unzip")
+            package_path = os.path.join(apk_dir, 'package-name.txt')
+            unzip_cmd = [unzip, '-q', '-o',  self.apk_path]
+            self.run_command(unzip_cmd, cwd=apk_dir, halt_on_failure=True)
+            self.app_name = str(self.read_from_file(package_path, verbose=True)).rstrip()
+        return self.app_name
+
+    def preflight_install(self):
+        # in the base class, this checks for mozinstall, but we don't use it
+        pass
+
+    def _build_command(self):
+        c = self.config
+        dirs = self.query_abs_dirs()
+
+        if self.test_suite not in self.config["suite_definitions"]:
+            self.fatal("Key '%s' not defined in the config!" % self.test_suite)
+
+        cmd = [
+            self.query_python_path('python'),
+            '-u',
+            os.path.join(
+                self._query_tests_dir(),
+                self.config["suite_definitions"][self.test_suite]["run_filename"]
+            ),
+        ]
+
+        raw_log_file = os.path.join(dirs['abs_blob_upload_dir'],
+                                    '%s_raw.log' % self.test_suite)
+
+        error_summary_file = os.path.join(dirs['abs_blob_upload_dir'],
+                                          '%s_errorsummary.log' % self.test_suite)
+        str_format_values = {
+            'device_serial': self.device_serial,
+            'remote_webserver': c['remote_webserver'],
+            'xre_path': self.xre_path,
+            'utility_path': self.xre_path,
+            'http_port': '8854',  # starting http port  to use for the mochitest server
+            'ssl_port': '4454',  # starting ssl port to use for the server
+            'certs_path': os.path.join(dirs['abs_work_dir'], 'tests/certs'),
+            # TestingMixin._download_and_extract_symbols() will set
+            # self.symbols_path when downloading/extracting.
+            'symbols_path': self.symbols_path,
+            'modules_dir': dirs['abs_modules_dir'],
+            'installer_path': self.installer_path,
+            'raw_log_file': raw_log_file,
+            'log_tbpl_level': self.log_tbpl_level,
+            'log_raw_level': self.log_raw_level,
+            'error_summary_file': error_summary_file,
+            # marionette options
+            'address': c.get('marionette_address') % {'device_ip': self.device_ip},
+            'gecko_log': os.path.join(dirs["abs_blob_upload_dir"], 'gecko.log'),
+            'test_manifest': os.path.join(
+                dirs['abs_marionette_tests_dir'],
+                self.config.get('marionette_test_manifest', '')
+            ),
+        }
+
+        user_paths = os.environ.get('MOZHARNESS_TEST_PATHS')
+        for option in self.config["suite_definitions"][self.test_suite]["options"]:
+            opt = option.split('=')[0]
+            # override configured chunk options with script args, if specified
+            if opt in ('--this-chunk', '--total-chunks'):
+                if user_paths or getattr(self, opt.replace('-', '_').strip('_'), None) is not None:
+                    continue
+
+            if '%(app)' in option:
+                # only query package name if requested
+                cmd.extend([option % {'app': self._query_package_name()}])
+            else:
+                cmd.extend([option % str_format_values])
+
+        if user_paths:
+            cmd.extend(user_paths.split(':'))
+        elif not self.verify_enabled:
+            if self.this_chunk is not None:
+                cmd.extend(['--this-chunk', self.this_chunk])
+            if self.total_chunks is not None:
+                cmd.extend(['--total-chunks', self.total_chunks])
+
+        try_options, try_tests = self.try_args(self.test_suite)
+        cmd.extend(try_options)
+        if not self.verify_enabled and not self.per_test_coverage:
+            cmd.extend(self.query_tests_args(
+                self.config["suite_definitions"][self.test_suite].get("tests"),
+                None,
+                try_tests))
+
+        return cmd
+
+    def _get_repo_url(self, path):
+        """
+           Return a url for a file (typically a tooltool manifest) in this hg repo
+           and using this revision (or mozilla-central/default if repo/rev cannot
+           be determined).
+
+           :param path specifies the directory path to the file of interest.
+        """
+        if 'GECKO_HEAD_REPOSITORY' in os.environ and 'GECKO_HEAD_REV' in os.environ:
+            # probably taskcluster
+            repo = os.environ['GECKO_HEAD_REPOSITORY']
+            revision = os.environ['GECKO_HEAD_REV']
+        else:
+            # something unexpected!
+            repo = 'https://hg.mozilla.org/mozilla-central'
+            revision = 'default'
+            self.warning('Unable to find repo/revision for manifest; '
+                         'using mozilla-central/default')
+        url = '%s/raw-file/%s/%s' % (
+            repo,
+            revision,
+            path)
+        return url
+
+    def _tooltool_fetch(self, url, dir):
+        c = self.config
+
+        manifest_path = self.download_file(
+            url,
+            file_name='releng.manifest',
+            parent_dir=dir
+        )
+
+        if not os.path.exists(manifest_path):
+            self.fatal("Could not retrieve manifest needed to retrieve "
+                       "artifacts from %s" % manifest_path)
+
+        self.tooltool_fetch(manifest_path,
+                            output_dir=dir,
+                            cache=c.get("tooltool_cache", None))
+
+    ##########################################
+    # Actions for AndroidHardwareTest        #
+    ##########################################
+    def _dump_perf_info(self):
+        '''
+        Dump some host and device performance-related information
+        to an artifact file, to help understand why jobs run slowly
+        sometimes. This is hopefully a temporary diagnostic.
+        See bug 1321605.
+        '''
+        dir = self.query_abs_dirs()['abs_blob_upload_dir']
+        perf_path = os.path.join(dir, "android-performance.log")
+        with open(perf_path, "w") as f:
+
+            f.write('\n\nHost /proc/cpuinfo:\n')
+            out, _ = self._run_proc(['cat', '/proc/cpuinfo'], quiet=True)
+            f.write(out)
+
+            f.write('\n\nHost /proc/meminfo:\n')
+            out, _ = self._run_proc(['cat', '/proc/meminfo'], quiet=True)
+            f.write(out)
+
+            f.write('\n\nHost process list:\n')
+            out, _ = self._run_proc(['ps', '-ef'], quiet=True)
+            f.write(out)
+
+            f.write('\n\nDevice /proc/cpuinfo:\n')
+            cmd = [self.adb_path, '-s', self.device_serial,
+                   'shell', 'cat', '/proc/cpuinfo']
+            out, _ = self._run_with_timeout(30, cmd, quiet=True)
+            f.write(out)
+            cpuinfo = out
+
+            f.write('\n\nDevice /proc/meminfo:\n')
+            cmd = [self.adb_path, '-s', self.device_serial,
+                   'shell', 'cat', '/proc/meminfo']
+            out, _ = self._run_with_timeout(30, cmd, quiet=True)
+            f.write(out)
+
+            f.write('\n\nDevice process list:\n')
+            cmd = [self.adb_path, '-s', self.device_serial,
+                   'shell', 'ps']
+            out, _ = self._run_with_timeout(30, cmd, quiet=True)
+            f.write(out)
+
+        for line in cpuinfo.split('\n'):
+            m = re.match("BogoMIPS.*: (\d*)", line)
+            if m:
+                bogomips = int(m.group(1))
+                self.info("Found Android bogomips: %d" % bogomips)
+                break
+
+    def verify_device(self):
+        '''
+        Check to see if the device can be contacted via adb.
+        '''
+        self.mkdir_p(self.query_abs_dirs()['abs_blob_upload_dir'])
+        self._dump_perf_info()
+        # Start logcat for the device. The adb process runs until the
+        # corresponding device is stopped. Output is written directly to
+        # the blobber upload directory so that it is uploaded automatically
+        # at the end of the job.
+        logcat_filename = 'logcat-%s.log' % self.device_serial
+        logcat_path = os.path.join(self.abs_dirs['abs_blob_upload_dir'],
+                                   logcat_filename)
+        self.logcat_file = open(logcat_path, 'w')
+        logcat_cmd = [self.adb_path, '-s', self.device_serial, 'logcat', '-v',
+                      'threadtime', 'Trace:S', 'StrictMode:S',
+                      'ExchangeService:S']
+        self.info(' '.join(logcat_cmd))
+        self.logcat_proc = subprocess.Popen(logcat_cmd, stdout=self.logcat_file,
+                                            stdin=subprocess.PIPE)
+        # Get a post-boot device process list for diagnostics
+        ps_cmd = [self.adb_path, '-s', self.device_serial, 'shell', 'ps']
+        self._run_with_timeout(30, ps_cmd)
+
+    def download_and_extract(self):
+        """
+        Download and extract fennec APK, tests.zip, host utils, and robocop (if required).
+        """
+        super(AndroidHardwareTest, self).download_and_extract(
+            suite_categories=self._query_suite_categories())
+        dirs = self.query_abs_dirs()
+        if self.test_suite and self.test_suite.startswith('robocop'):
+            robocop_url = self.installer_url[:self.installer_url.rfind('/')] + '/robocop.apk'
+            self.info("Downloading robocop...")
+            self.download_file(robocop_url, 'robocop.apk', dirs['abs_work_dir'], error_level=FATAL)
+        self.rmtree(dirs['abs_xre_dir'])
+        self.mkdir_p(dirs['abs_xre_dir'])
+        if self.config["hostutils_manifest_path"]:
+            url = self._get_repo_url(self.config["hostutils_manifest_path"])
+            self._tooltool_fetch(url, dirs['abs_xre_dir'])
+            for p in glob.glob(os.path.join(dirs['abs_xre_dir'], 'host-utils-*')):
+                if os.path.isdir(p) and os.path.isfile(os.path.join(p, 'xpcshell')):
+                    self.xre_path = p
+            if not self.xre_path:
+                self.fatal("xre path not found in %s" % dirs['abs_xre_dir'])
+        else:
+            self.fatal("configure hostutils_manifest_path!")
+
+    def install(self):
+        """
+        Install APKs on the device.
+        """
+        install_needed = (not self.test_suite) or \
+            self.config["suite_definitions"][self.test_suite].get("install")
+        if install_needed is False:
+            self.info("Skipping apk installation for %s" % self.test_suite)
+            return
+
+        assert self.installer_path is not None, \
+            "Either add installer_path to the config or use --installer-path."
+
+        cmd = [self.adb_path, '-s', self.device_serial, 'shell',
+               'getprop', 'ro.build.version.sdk']
+        self.sdk_level, _ = self._run_with_timeout(30, cmd)
+
+        # Install Fennec
+        install_ok = self._retry(3, 30, self._install_fennec_apk, "Install app APK")
+        if not install_ok:
+            self.fatal('INFRA-ERROR: Failed to install %s on %s' %
+                       (self.installer_path, self.device_name),
+                       EXIT_STATUS_DICT[TBPL_RETRY])
+
+        # Install Robocop if required
+        if self.test_suite and self.test_suite.startswith('robocop'):
+            install_ok = self._retry(3, 30, self._install_robocop_apk, "Install Robocop APK")
+            if not install_ok:
+                self.fatal('INFRA-ERROR: Failed to install %s on %s' %
+                           (self.robocop_path, self.device_name),
+                           EXIT_STATUS_DICT[TBPL_RETRY])
+
+        self.info("Finished installing apps for %s" % self.device_name)
+
+    def _query_suites(self):
+        if self.test_suite:
+            return [(self.test_suite, self.test_suite)]
+        # per-test mode: determine test suites to run
+        all = [('mochitest', {'plain': 'mochitest',
+                              'chrome': 'mochitest-chrome',
+                              'plain-clipboard': 'mochitest-plain-clipboard',
+                              'plain-gpu': 'mochitest-plain-gpu'}),
+               ('reftest', {'reftest': 'reftest', 'crashtest': 'crashtest'}),
+               ('xpcshell', {'xpcshell': 'xpcshell'})]
+        suites = []
+        for (category, all_suites) in all:
+            cat_suites = self.query_per_test_category_suites(category, all_suites)
+            for k in cat_suites.keys():
+                suites.append((k, cat_suites[k]))
+        return suites
+
+    def _query_suite_categories(self):
+        if self.test_suite:
+            categories = [self.test_suite]
+        else:
+            # per-test mode
+            categories = ['mochitest', 'reftest', 'xpcshell']
+        return categories
+
+    def run_tests(self):
+        """
+        Run the tests
+        """
+        self.start_time = datetime.datetime.now()
+        max_per_test_time = datetime.timedelta(minutes=60)
+
+        per_test_args = []
+        suites = self._query_suites()
+        minidump = self.query_minidump_stackwalk()
+        for (per_test_suite, suite) in suites:
+            self.test_suite = suite
+
+            cmd = self._build_command()
+
+            try:
+                cwd = self._query_tests_dir()
+            except Exception:
+                self.fatal("Don't know how to run --test-suite '%s'!" % self.test_suite)
+            env = self.query_env()
+            if minidump:
+                env['MINIDUMP_STACKWALK'] = minidump
+            env['MOZ_UPLOAD_DIR'] = self.query_abs_dirs()['abs_blob_upload_dir']
+            env['MINIDUMP_SAVE_PATH'] = self.query_abs_dirs()['abs_blob_upload_dir']
+            env['RUST_BACKTRACE'] = 'full'
+
+            summary = None
+            for per_test_args in self.query_args(per_test_suite):
+                if (datetime.datetime.now() - self.start_time) > max_per_test_time:
+                    # Running tests has run out of time. That is okay! Stop running
+                    # them so that a task timeout is not triggered, and so that
+                    # (partial) results are made available in a timely manner.
+                    self.info("TinderboxPrint: Running tests took too long: "
+                              "Not all tests were executed.<br/>")
+                    # Signal per-test time exceeded, to break out of suites and
+                    # suite categories loops also.
+                    return False
+
+                final_cmd = copy.copy(cmd)
+                if len(per_test_args) > 0:
+                    # in per-test mode, remove any chunk arguments from command
+                    for arg in final_cmd:
+                        if 'total-chunk' in arg or 'this-chunk' in arg:
+                            final_cmd.remove(arg)
+                final_cmd.extend(per_test_args)
+
+                self.info("Running on %s the command %s" % (self.device_name,
+                          subprocess.list2cmdline(final_cmd)))
+                self.info("##### %s log begins" % self.test_suite)
+
+                suite_category = self.test_suite
+                parser = self.get_test_output_parser(
+                    suite_category,
+                    config=self.config,
+                    log_obj=self.log_obj,
+                    error_list=[])
+                self.run_command(final_cmd, cwd=cwd, env=env, output_parser=parser)
+                tbpl_status, log_level, summary = parser.evaluate_parser(0, summary)
+                parser.append_tinderboxprint_line(self.test_suite)
+
+                self.info("##### %s log ends" % self.test_suite)
+
+                if len(per_test_args) > 0:
+                    self.record_status(tbpl_status, level=log_level)
+                    self.log_per_test_status(per_test_args[-1], tbpl_status, log_level)
+                else:
+                    self.record_status(tbpl_status, level=log_level)
+                    self.log("The %s suite: %s ran with return status: %s" %
+                             (suite_category, suite, tbpl_status), level=log_level)
+
+    @PostScriptAction('run-tests')
+    def stop_device(self, action, success=None):
+        '''
+        Report device health.
+        '''
+        if self.logcat_proc:
+            self.info("Killing logcat pid %d." % self.logcat_proc.pid)
+            self.logcat_proc.kill()
+            self.logcat_file.close()
+
+
+if __name__ == '__main__':
+    hardwareTest = AndroidHardwareTest()
+    hardwareTest.run_and_exit()
--- a/testing/mozharness/scripts/awsy_script.py
+++ b/testing/mozharness/scripts/awsy_script.py
@@ -12,16 +12,18 @@ import copy
 import json
 import os
 import re
 import sys
 
 # load modules from parent dir
 sys.path.insert(1, os.path.dirname(sys.path[0]))
 
+import mozinfo
+
 from mozharness.base.script import PreScriptAction
 from mozharness.base.log import INFO, ERROR
 from mozharness.mozilla.testing.testbase import TestingMixin, testing_config_options
 from mozharness.base.vcs.vcsbase import MercurialScript
 from mozharness.mozilla.tooltool import TooltoolMixin
 from mozharness.mozilla.structuredlog import StructuredOutputParser
 from mozharness.mozilla.testing.codecoverage import (
     CodeCoverageMixin,
@@ -144,16 +146,39 @@ class AWSY(TestingMixin, MercurialScript
         '''
         dirs = self.abs_dirs
         env = {}
         error_summary_file = os.path.join(dirs['abs_blob_upload_dir'],
                                           'marionette_errorsummary.log')
 
         runtime_testvars = {'webRootDir': self.webroot_dir,
                             'resultsDir': self.results_dir}
+
+        # Check if this is a DMD build and if so enable it.
+        dmd_py_lib_dir = os.path.dirname(self.binary_path)
+        if mozinfo.os == 'mac':
+            # On mac binary is in MacOS and dmd.py is in Resources, ie:
+            #   Name.app/Contents/MacOS/libdmd.dylib
+            #   Name.app/Contents/Resources/dmd.py
+            dmd_py_lib_dir = os.path.join(dmd_py_lib_dir, "../Resources/")
+
+        dmd_path = os.path.join(dmd_py_lib_dir, "dmd.py")
+        if os.path.isfile(dmd_path):
+            runtime_testvars['dmd'] = True
+
+            # Allow the child process to import dmd.py
+            python_path = os.environ.get('PYTHONPATH')
+
+            if python_path:
+                os.environ['PYTHONPATH'] = "%s%s%s" % (python_path, os.pathsep, dmd_py_lib_dir)
+            else:
+                os.environ['PYTHONPATH'] = dmd_py_lib_dir
+
+            env['DMD'] = "--mode=dark-matter --stacks=full"
+
         runtime_testvars_path = os.path.join(self.awsy_path, 'runtime-testvars.json')
         runtime_testvars_file = open(runtime_testvars_path, 'wb')
         runtime_testvars_file.write(json.dumps(runtime_testvars, indent=2))
         runtime_testvars_file.close()
 
         cmd = ['marionette']
 
         if self.config['test_about_blank']:
--- a/tools/profiler/lul/LulMain.cpp
+++ b/tools/profiler/lul/LulMain.cpp
@@ -902,18 +902,18 @@ TaggedUWord DerefTUW(TaggedUWord aAddr, 
     = CheckedUWord(aStackImg->mStartAvma) + CheckedUWord(aStackImg->mLen);
   if (!highest_requested_plus_one.isValid()     // overflow?
       || !highest_available_plus_one.isValid()  // overflow?
       || (highest_requested_plus_one.value()
           > highest_available_plus_one.value())) { // in range?
     return TaggedUWord();
   }
 
-  return TaggedUWord(*(uintptr_t*)(aStackImg->mContents + aAddr.Value()
-                                   - aStackImg->mStartAvma));
+  return TaggedUWord(*(uintptr_t*)(
+           &aStackImg->mContents[aAddr.Value() - aStackImg->mStartAvma]));
 }
 
 // RUNS IN NO-MALLOC CONTEXT
 static
 TaggedUWord EvaluateReg(int16_t aReg, const UnwindRegs* aOldRegs,
                         TaggedUWord aCFA)
 {
   switch (aReg) {
--- a/widget/windows/WinCompositorWidget.cpp
+++ b/widget/windows/WinCompositorWidget.cpp
@@ -14,23 +14,25 @@
 #include "WinCompositorWindowThread.h"
 
 #include <ddraw.h>
 
 namespace mozilla {
 namespace widget {
 
 using namespace mozilla::gfx;
+using namespace mozilla;
 
 WinCompositorWidget::WinCompositorWidget(const WinCompositorWidgetInitData& aInitData,
                                          const layers::CompositorOptions& aOptions)
  : CompositorWidget(aOptions)
  , mWidgetKey(aInitData.widgetKey()),
    mWnd(reinterpret_cast<HWND>(aInitData.hWnd())),
    mCompositorWnd(nullptr),
+   mTransparentSurfaceLock("mTransparentSurfaceLock"),
    mTransparencyMode(aInitData.transparencyMode()),
    mMemoryDC(nullptr),
    mCompositeDC(nullptr),
    mLockedBackBufferData(nullptr)
 {
   MOZ_ASSERT(mWnd && ::IsWindow(mWnd));
 
   // mNotDeferEndRemoteDrawing is set on the main thread during init,
@@ -43,16 +45,17 @@ WinCompositorWidget::WinCompositorWidget
 WinCompositorWidget::~WinCompositorWidget()
 {
   DestroyCompositorWindow();
 }
 
 void
 WinCompositorWidget::OnDestroyWindow()
 {
+  MutexAutoLock lock(mTransparentSurfaceLock);
   mTransparentSurface = nullptr;
   mMemoryDC = nullptr;
 }
 
 bool
 WinCompositorWidget::PreRender(WidgetRenderingContext* aContext)
 {
   // This can block waiting for WM_SETTEXT to finish
@@ -79,16 +82,18 @@ WinCompositorWidget::GetClientSize()
   return LayoutDeviceIntSize(
     r.right - r.left,
     r.bottom - r.top);
 }
 
 already_AddRefed<gfx::DrawTarget>
 WinCompositorWidget::StartRemoteDrawing()
 {
+  MutexAutoLock lock(mTransparentSurfaceLock);
+
   MOZ_ASSERT(!mCompositeDC);
 
   RefPtr<gfxASurface> surf;
   if (mTransparencyMode == eTransparencyTransparent) {
     surf = EnsureTransparentSurface();
   }
 
   // Must call this after EnsureTransparentSurface(), since it could update
@@ -239,57 +244,61 @@ void
 WinCompositorWidget::LeavePresentLock()
 {
   mPresentLock.Leave();
 }
 
 RefPtr<gfxASurface>
 WinCompositorWidget::EnsureTransparentSurface()
 {
+  mTransparentSurfaceLock.AssertCurrentThreadOwns();
   MOZ_ASSERT(mTransparencyMode == eTransparencyTransparent);
 
   IntSize size = GetClientSize().ToUnknownSize();
   if (!mTransparentSurface || mTransparentSurface->GetSize() != size) {
     mTransparentSurface = nullptr;
     mMemoryDC = nullptr;
     CreateTransparentSurface(size);
   }
 
   RefPtr<gfxASurface> surface = mTransparentSurface;
   return surface.forget();
 }
 
 void
 WinCompositorWidget::CreateTransparentSurface(const gfx::IntSize& aSize)
 {
+  mTransparentSurfaceLock.AssertCurrentThreadOwns();
   MOZ_ASSERT(!mTransparentSurface && !mMemoryDC);
   RefPtr<gfxWindowsSurface> surface = new gfxWindowsSurface(aSize, SurfaceFormat::A8R8G8B8_UINT32);
   mTransparentSurface = surface;
   mMemoryDC = surface->GetDC();
 }
 
 void
 WinCompositorWidget::UpdateTransparency(nsTransparencyMode aMode)
 {
+  MutexAutoLock lock(mTransparentSurfaceLock);
   if (mTransparencyMode == aMode) {
     return;
   }
 
   mTransparencyMode = aMode;
   mTransparentSurface = nullptr;
   mMemoryDC = nullptr;
 
   if (mTransparencyMode == eTransparencyTransparent) {
     EnsureTransparentSurface();
   }
 }
 
 void
 WinCompositorWidget::ClearTransparentWindow()
 {
+  MutexAutoLock lock(mTransparentSurfaceLock);
   if (!mTransparentSurface) {
     return;
   }
 
   EnsureTransparentSurface();
 
   IntSize size = mTransparentSurface->GetSize();
   if (!size.IsEmpty()) {
--- a/widget/windows/WinCompositorWidget.h
+++ b/widget/windows/WinCompositorWidget.h
@@ -5,16 +5,17 @@
 
 #ifndef widget_windows_WinCompositorWidget_h
 #define widget_windows_WinCompositorWidget_h
 
 #include "CompositorWidget.h"
 #include "gfxASurface.h"
 #include "mozilla/gfx/CriticalSection.h"
 #include "mozilla/gfx/Point.h"
+#include "mozilla/Mutex.h"
 #include "nsIWidget.h"
 
 class nsWindow;
 
 namespace mozilla {
 namespace widget {
 
 class PlatformCompositorWidgetDelegate
@@ -101,16 +102,18 @@ public:
   HWND GetCompositorHwnd() const {
     return mCompositorWnd;
   }
 
   void EnsureCompositorWindow();
   void DestroyCompositorWindow();
   void UpdateCompositorWndSizeIfNecessary();
 
+  mozilla::Mutex& GetTransparentSurfaceLock() { return mTransparentSurfaceLock; }
+
 protected:
 
 private:
   HDC GetWindowSurface();
   void FreeWindowSurface(HDC dc);
 
   void CreateTransparentSurface(const gfx::IntSize& aSize);
 
@@ -119,16 +122,17 @@ private:
   HWND mWnd;
 
   HWND mCompositorWnd;
   LayoutDeviceIntSize mLastCompositorWndSize;
 
   gfx::CriticalSection mPresentLock;
 
   // Transparency handling.
+  mozilla::Mutex mTransparentSurfaceLock;
   nsTransparencyMode mTransparencyMode;
   RefPtr<gfxASurface> mTransparentSurface;
   HDC mMemoryDC;
   HDC mCompositeDC;
 
   // Locked back buffer of BasicCompositor
   uint8_t* mLockedBackBufferData;
 
--- a/widget/windows/nsWindowGfx.cpp
+++ b/widget/windows/nsWindowGfx.cpp
@@ -320,16 +320,18 @@ bool nsWindow::OnPaint(HDC aDC, uint32_t
     switch (GetLayerManager()->GetBackendType()) {
       case LayersBackend::LAYERS_BASIC:
         {
           RefPtr<gfxASurface> targetSurface;
 
 #if defined(MOZ_XUL)
           // don't support transparency for non-GDI rendering, for now
           if (eTransparencyTransparent == mTransparencyMode) {
+            // This mutex needs to be held when EnsureTransparentSurface is called.
+            MutexAutoLock lock(mBasicLayersSurface->GetTransparentSurfaceLock());
             targetSurface = mBasicLayersSurface->EnsureTransparentSurface();
           }
 #endif
 
           RefPtr<gfxWindowsSurface> targetSurfaceWin;
           if (!targetSurface)
           {
             uint32_t flags = (mTransparencyMode == eTransparencyOpaque) ? 0 :
--- a/xpcom/reflect/xptcall/md/win32/xptcstubs.cpp
+++ b/xpcom/reflect/xptcall/md/win32/xptcstubs.cpp
@@ -90,16 +90,21 @@ PrepareAndDispatch(nsXPTCStubBase* self,
 }
 
 } // extern "C"
 
 // declspec(naked) is broken in gcc
 #if !defined(__GNUC__)
 static
 __declspec(naked)
+// Compiler-inserted instrumentation is going to botch our assembly below,
+// so forbid the compiler from doing that.
+#if defined(__clang__)
+__attribute__((no_instrument_function))
+#endif
 void SharedStub(void)
 {
     __asm {
         push ebp            // set up simple stack frame
         mov  ebp, esp       // stack has: ebp/vtbl_index/retaddr/this/args
         push ecx            // make room for a ptr
         lea  eax, [ebp-4]   // pointer to stackBytesToPop
         push eax