[vm] Enable running FFI tests on Android in JIT-mode.

* Move FFI tests into a separate test suite.
  They never belonged in standalone_2/ since they are not only available in
  the standalone VM. Also, we want to have a separate status file.

* Add new "SharedObjects" option to test files to copy needed shared objects
  to the Android device for testing.

* Add support to compiler/runtime_configuration.dart for testing JIT-mode on Android.

* Add new configurations and builders to test_matrix.json to test JIT-mode on Android.

* Clean up status file entries for FFI (we didn't need to special-case stress & subtype tests).

Change-Id: Ifb32ef7051754f477d00ecd7a0f9b19ca8a66eae
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97334
Commit-Queue: Samir Jindel <sjindel@google.com>
Reviewed-by: William Hesse <whesse@google.com>
Reviewed-by: Daco Harkes <dacoharkes@google.com>
diff --git a/tests/standalone_2/ffi/coordinate.dart b/tests/ffi/coordinate.dart
similarity index 100%
rename from tests/standalone_2/ffi/coordinate.dart
rename to tests/ffi/coordinate.dart
diff --git a/tests/standalone_2/ffi/coordinate_bare.dart b/tests/ffi/coordinate_bare.dart
similarity index 100%
rename from tests/standalone_2/ffi/coordinate_bare.dart
rename to tests/ffi/coordinate_bare.dart
diff --git a/tests/standalone_2/ffi/coordinate_manual.dart b/tests/ffi/coordinate_manual.dart
similarity index 100%
rename from tests/standalone_2/ffi/coordinate_manual.dart
rename to tests/ffi/coordinate_manual.dart
diff --git a/tests/standalone_2/ffi/cstring.dart b/tests/ffi/cstring.dart
similarity index 100%
rename from tests/standalone_2/ffi/cstring.dart
rename to tests/ffi/cstring.dart
diff --git a/tests/standalone_2/ffi/data_not_asan_test.dart b/tests/ffi/data_not_asan_test.dart
similarity index 100%
rename from tests/standalone_2/ffi/data_not_asan_test.dart
rename to tests/ffi/data_not_asan_test.dart
diff --git a/tests/standalone_2/ffi/data_test.dart b/tests/ffi/data_test.dart
similarity index 100%
rename from tests/standalone_2/ffi/data_test.dart
rename to tests/ffi/data_test.dart
diff --git a/tests/standalone_2/ffi/dylib_utils.dart b/tests/ffi/dylib_utils.dart
similarity index 100%
rename from tests/standalone_2/ffi/dylib_utils.dart
rename to tests/ffi/dylib_utils.dart
diff --git a/tests/standalone_2/ffi/dynamic_library_test.dart b/tests/ffi/dynamic_library_test.dart
similarity index 96%
rename from tests/standalone_2/ffi/dynamic_library_test.dart
rename to tests/ffi/dynamic_library_test.dart
index 0a38e9a..3282f4c 100644
--- a/tests/standalone_2/ffi/dynamic_library_test.dart
+++ b/tests/ffi/dynamic_library_test.dart
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 //
 // Dart test program for testing dart:ffi dynamic library loading.
+//
+// SharedObjects=ffi_test_dynamic_library ffi_test_functions
 
 library FfiTest;
 
diff --git a/tests/standalone_2/ffi/enable_ffi_test.dart b/tests/ffi/enable_ffi_test.dart
similarity index 100%
rename from tests/standalone_2/ffi/enable_ffi_test.dart
rename to tests/ffi/enable_ffi_test.dart
diff --git a/tests/ffi/ffi.status b/tests/ffi/ffi.status
new file mode 100644
index 0000000..30f798d
--- /dev/null
+++ b/tests/ffi/ffi.status
@@ -0,0 +1,29 @@
+# Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+[ $runtime != dart_precompiled && $runtime != vm ]
+*: SkipByDesign # FFI is a VM-only feature. (This test suite is part of the default set.)
+
+[ $builder_tag == asan ]
+data_not_asan_test: SkipByDesign # This test tries to allocate too much memory on purpose.
+
+[ $arch == ia32 ]
+function_structs_test: Skip # dartbug.com/35768: Struct alignment rules are broken on 32-bit.
+
+# dartbug.com/35934
+[ $compiler == app_jitk ]
+dynamic_library_test: Skip
+function_callbacks_test: Skip
+function_structs_test: Skip
+function_test: Skip
+negative_function_test: Skip
+
+[ $runtime == dart_precompiled ]
+*: Skip # AOT is not yet supported: dartbug.com/35765
+
+[ $arch != arm64 && $arch != ia32 && $arch != x64 ]
+*: Skip # FFI not yet supported on other architectures.
+
+[ $system != android && $system != linux && $system != macos && $system != windows ]
+*: Skip # FFI not yet supported on other OSes.
diff --git a/tests/standalone_2/ffi/function_callbacks_test.dart b/tests/ffi/function_callbacks_test.dart
similarity index 98%
rename from tests/standalone_2/ffi/function_callbacks_test.dart
rename to tests/ffi/function_callbacks_test.dart
index e9a0a7e..0ad50a9 100644
--- a/tests/standalone_2/ffi/function_callbacks_test.dart
+++ b/tests/ffi/function_callbacks_test.dart
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 //
 // Dart test program for testing dart:ffi function pointers with callbacks.
+//
+// SharedObjects=ffi_test_functions
 
 library FfiTest;
 
diff --git a/tests/standalone_2/ffi/function_stress_test.dart b/tests/ffi/function_stress_test.dart
similarity index 98%
rename from tests/standalone_2/ffi/function_stress_test.dart
rename to tests/ffi/function_stress_test.dart
index 2b0099e..1c0d9f4 100644
--- a/tests/standalone_2/ffi/function_stress_test.dart
+++ b/tests/ffi/function_stress_test.dart
@@ -10,6 +10,8 @@
 //
 // NOTE: This test does not produce useful stderr when it fails because the
 // stderr is redirected to a file for reflection.
+//
+// SharedObjects=ffi_test_functions
 
 import 'dart:ffi' as ffi;
 import 'dylib_utils.dart';
diff --git a/tests/standalone_2/ffi/function_structs_test.dart b/tests/ffi/function_structs_test.dart
similarity index 98%
rename from tests/standalone_2/ffi/function_structs_test.dart
rename to tests/ffi/function_structs_test.dart
index ce7bd11..6f1f906 100644
--- a/tests/standalone_2/ffi/function_structs_test.dart
+++ b/tests/ffi/function_structs_test.dart
@@ -4,6 +4,8 @@
 //
 // Dart test program for testing dart:ffi function pointers with struct
 // arguments.
+//
+// SharedObjects=ffi_test_functions
 
 library FfiTest;
 
diff --git a/tests/standalone_2/ffi/function_test.dart b/tests/ffi/function_test.dart
similarity index 99%
rename from tests/standalone_2/ffi/function_test.dart
rename to tests/ffi/function_test.dart
index 2baf086..07f8fc2 100644
--- a/tests/standalone_2/ffi/function_test.dart
+++ b/tests/ffi/function_test.dart
@@ -6,6 +6,7 @@
 //
 // VMOptions=
 // VMOptions=--deterministic --optimization-counter-threshold=10
+// SharedObjects=ffi_test_functions
 
 library FfiTest;
 
diff --git a/tests/standalone_2/ffi/gc_helper.dart b/tests/ffi/gc_helper.dart
similarity index 83%
rename from tests/standalone_2/ffi/gc_helper.dart
rename to tests/ffi/gc_helper.dart
index 182e1df..abe0454 100644
--- a/tests/standalone_2/ffi/gc_helper.dart
+++ b/tests/ffi/gc_helper.dart
@@ -9,12 +9,15 @@
 
 DynamicLibrary ffiTestFunctions = dlopenPlatformSpecific("ffi_test_functions");
 
+const bool isProduct = const bool.fromEnvironment("dart.vm.product");
+
 abstract class GCWatcher {
   factory GCWatcher() => _GCWatcherImpl();
   factory GCWatcher.dummy() => _MockGCWatcher();
-  factory GCWatcher.ifAvailable() => (Platform.isWindows || Platform.isAndroid)
-      ? GCWatcher.dummy()
-      : GCWatcher();
+  factory GCWatcher.ifAvailable() =>
+      (Platform.isWindows || Platform.isAndroid || isProduct)
+          ? GCWatcher.dummy()
+          : GCWatcher();
 
   Future<int> size();
   void dispose();
diff --git a/tests/standalone_2/ffi/negative_function_test.dart b/tests/ffi/negative_function_test.dart
similarity index 97%
rename from tests/standalone_2/ffi/negative_function_test.dart
rename to tests/ffi/negative_function_test.dart
index a529d67..f851728 100644
--- a/tests/standalone_2/ffi/negative_function_test.dart
+++ b/tests/ffi/negative_function_test.dart
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 //
 // Dart test program for testing error handling with dart:ffi functions.
+//
+// SharedObjects=ffi_test_functions
 
 import 'dart:ffi' as ffi;
 import 'dylib_utils.dart';
diff --git a/tests/standalone_2/ffi/static_checks_test.dart b/tests/ffi/static_checks_test.dart
similarity index 99%
rename from tests/standalone_2/ffi/static_checks_test.dart
rename to tests/ffi/static_checks_test.dart
index 15e162a..e0ab94c 100644
--- a/tests/standalone_2/ffi/static_checks_test.dart
+++ b/tests/ffi/static_checks_test.dart
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 //
 // Dart test program for testing dart:ffi extra checks
+//
+// SharedObjects=ffi_test_dynamic_library
 
 library FfiTest;
 
diff --git a/tests/standalone_2/ffi/structs_test.dart b/tests/ffi/structs_test.dart
similarity index 100%
rename from tests/standalone_2/ffi/structs_test.dart
rename to tests/ffi/structs_test.dart
diff --git a/tests/standalone_2/ffi/subtype_test.dart b/tests/ffi/subtype_test.dart
similarity index 100%
rename from tests/standalone_2/ffi/subtype_test.dart
rename to tests/ffi/subtype_test.dart
diff --git a/tests/standalone_2/ffi/very_large_struct.dart b/tests/ffi/very_large_struct.dart
similarity index 100%
rename from tests/standalone_2/ffi/very_large_struct.dart
rename to tests/ffi/very_large_struct.dart
diff --git a/tests/standalone_2/standalone_2_kernel.status b/tests/standalone_2/standalone_2_kernel.status
index fdbc2ab..ceb859a 100644
--- a/tests/standalone_2/standalone_2_kernel.status
+++ b/tests/standalone_2/standalone_2_kernel.status
@@ -7,11 +7,7 @@
 fragmentation_test: Pass, Slow # GC heavy
 io/process_sync_test: Pass, Slow # Spawns synchronously subprocesses in sequence.
 
-[ $arch == ia32 ]
-ffi/function_structs_test: Skip # Struct alignment rules are broken on 32-bit. # Issue 35768
-
 [ $builder_tag == asan ]
-ffi/data_not_asan_test: Skip # this test tries to allocate too much memory on purpose
 io/file_test: Fail # Issue 34724
 io/http_server_response_test: Fail # Issue 34724
 io/process_sync_test: Pass, Fail # https://github.com/dart-lang/sdk/issues/34724
@@ -19,11 +15,6 @@
 io/test_extension_fail_test: Fail # Issue 32187
 
 [ $compiler == app_jitk ]
-ffi/dynamic_library_test: Skip # https://github.com/dart-lang/sdk/issues/35934
-ffi/function_callbacks_test: Skip # https://github.com/dart-lang/sdk/issues/35934
-ffi/function_structs_test: Skip # https://github.com/dart-lang/sdk/issues/35934
-ffi/function_test: Skip # https://github.com/dart-lang/sdk/issues/35934
-ffi/negative_function_test: Skip # https://github.com/dart-lang/sdk/issues/35934
 io/file_error_test: RuntimeError
 io/file_test: RuntimeError
 io/http_auth_digest_test: RuntimeError
@@ -51,9 +42,6 @@
 io/arguments_test: Fail # Test harness passes runtime arguments to the compiler
 io/test_runner_test: SkipByDesign # Is not relevant for AOT.
 
-[ $runtime == dart_precompiled ]
-ffi: Skip # https://github.com/dart-lang/sdk/issues/35765
-
 [ $system == android ]
 entrypoints_verification_test: Skip # Requires shared objects which the test script doesn't "adb push".
 
@@ -169,9 +157,6 @@
 [ $mode == debug && $hot_reload && ($compiler == dartk || $compiler == dartkb) ]
 io/web_socket_ping_test: Crash, Pass
 
-[ $runtime != dart_precompiled && $runtime != vm ]
-ffi: SkipByDesign # ffi is only supported on vm
-
 [ $runtime == vm && $strong && ($compiler == dartk || $compiler == dartkb) ]
 io/http_client_request_test: Pass, Timeout
 io/secure_builtin_roots_test: Pass, Timeout
@@ -244,12 +229,6 @@
 io/web_socket_compression_test: Skip # Timeout
 io/web_socket_test: Skip # Timeout
 
-[ $compiler != dartk || $mode == product || $arch != arm64 && $arch != ia32 && $arch != x64 || $system != android && $system != linux && $system != macos && $system != windows ]
-ffi/function_stress_test: SkipByDesign # FFI must be supported. Also requires --verbose-gc, which isn't included in product.
-ffi/subtype_test: SkipByDesign # FFI must be supported. Also requires --verbose-gc, which isn't included in product.
-
-[ $arch != arm64 && $arch != ia32 && $arch != x64 || $system != android && $system != linux && $system != macos && $system != windows ]
-ffi: Skip # ffi not yet supported on other systems than linux/macos/windows x64/ia32
 
 [ $compiler != dartk && $compiler != dartkb && $compiler != dartkp || $compiler == dartkp && $system == windows ]
 entrypoints_verification_test: SkipByDesign # Requires VM to run. Cannot run in precompiled Windows because the DLL is linked against dart.exe instead of dart_precompiled_runtime.exe.
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index 45806b7..ac4c492 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -42,6 +42,7 @@
       "tests/search/",
       "tests/standalone/",
       "tests/standalone_2/",
+      "tests/ffi/",
       "third_party/d8/",
       "third_party/observatory_pub_packages/packages/web_components/",
       "third_party/pkg/",
@@ -80,6 +81,7 @@
       "tests/search/",
       "tests/standalone/",
       "tests/standalone_2/",
+      "tests/ffi/",
       "third_party/d8/",
       "third_party/pkg/",
       "third_party/pkg_tested/",
@@ -168,6 +170,7 @@
       "tests/search/",
       "tests/standalone/",
       "tests/standalone_2/",
+      "tests/ffi/",
       "pkg/async_helper/",
       "pkg/build_integration/",
       "pkg/dart_internal/",
@@ -265,6 +268,7 @@
       "options": {
         "use-blobs": true
     }},
+    "dartk-android-(debug|product|release)-(arm|arm64)": {},
     "dartkp-linux-(debug|product|release)-(simarm|simarm64)": {
       "options": {
         "use-blobs": true
@@ -428,7 +432,8 @@
             "language_2",
             "corelib_2",
             "lib_2",
-            "standalone_2"
+            "standalone_2",
+            "ffi"
           ],
           "fileset": "vm-kernel",
           "shards": 10
@@ -440,7 +445,8 @@
             "language_2",
             "corelib_2",
             "lib_2",
-            "standalone_2"
+            "standalone_2",
+            "ffi"
           ],
           "fileset": "vm-kernel",
           "shards": 10
@@ -452,7 +458,8 @@
             "language_2",
             "corelib_2",
             "lib_2",
-            "standalone_2"
+            "standalone_2",
+            "ffi"
           ],
           "fileset": "vm-kernel",
           "shards": 10
@@ -516,6 +523,37 @@
     },
     {
       "builders": [
+        "vm-ffi-android-debug-arm64",
+        "vm-ffi-android-debug-arm",
+        "vm-ffi-android-release-arm64",
+        "vm-ffi-android-release-arm",
+        "vm-ffi-android-product-arm64",
+        "vm-ffi-android-product-arm"
+      ],
+      "meta": {
+        "description": "This configuration is used for running FFI tests in JIT-mode on Android."
+      },
+      "steps": [
+        {
+          "name": "build dart",
+          "script": "tools/build.py",
+          "arguments": [
+            "runtime_kernel",
+            "--os=android"
+          ]
+        },
+        {
+          "name": "ffi tests",
+          "arguments": [
+            "-ndartk-android-${mode}-${arch}",
+            "ffi"
+          ],
+          "fileset": "vm-kernel"
+        }
+      ]
+    },
+    {
+      "builders": [
         "vm-kernel-precomp-linux-debug-x64",
         "vm-kernel-precomp-linux-product-x64",
         "vm-kernel-precomp-linux-release-simarm",
diff --git a/tools/testing/dart/command.dart b/tools/testing/dart/command.dart
index b91201d..9b41d64 100644
--- a/tools/testing/dart/command.dart
+++ b/tools/testing/dart/command.dart
@@ -81,6 +81,12 @@
         precompiledRunner, processTest, testDirectory, arguments, useBlobs);
   }
 
+  static Command adbDartk(String precompiledRunner, String processTest,
+      String script, List<String> arguments, List<String> extraLibraries) {
+    return new AdbDartkCommand._(
+        precompiledRunner, processTest, script, arguments, extraLibraries);
+  }
+
   static Command jsCommandLine(
       String displayName, String executable, List<String> arguments,
       [Map<String, String> environment]) {
@@ -604,6 +610,40 @@
       'to an attached device. Uses (and requires) adb.';
 }
 
+class AdbDartkCommand extends Command {
+  final String buildPath;
+  final String processTestFilename;
+  final String kernelFile;
+  final List<String> arguments;
+  final List<String> extraLibraries;
+
+  AdbDartkCommand._(this.buildPath, this.processTestFilename, this.kernelFile,
+      this.arguments, this.extraLibraries,
+      {int index = 0})
+      : super._("adb_precompilation", index: index);
+
+  AdbDartkCommand indexedCopy(int index) => AdbDartkCommand._(
+      buildPath, processTestFilename, kernelFile, arguments, extraLibraries,
+      index: index);
+  _buildHashCode(HashCodeBuilder builder) {
+    super._buildHashCode(builder);
+    builder.add(buildPath);
+    builder.add(kernelFile);
+    builder.add(arguments);
+    builder.add(extraLibraries);
+  }
+
+  bool _equal(AdbDartkCommand other) =>
+      super._equal(other) &&
+      buildPath == other.buildPath &&
+      arguments == other.arguments &&
+      extraLibraries == other.extraLibraries &&
+      kernelFile == other.kernelFile;
+
+  String toString() => 'Steps to push Dart VM and Dill file '
+      'to an attached device. Uses (and requires) adb.';
+}
+
 class JSCommandlineCommand extends ProcessCommand {
   JSCommandlineCommand._(
       String displayName, String executable, List<String> arguments,
diff --git a/tools/testing/dart/compiler_configuration.dart b/tools/testing/dart/compiler_configuration.dart
index 0e9f1f3..7411a16 100644
--- a/tools/testing/dart/compiler_configuration.dart
+++ b/tools/testing/dart/compiler_configuration.dart
@@ -81,7 +81,8 @@
       case Compiler.dartk:
         if (configuration.architecture == Architecture.simdbc64 ||
             configuration.architecture == Architecture.simarm ||
-            configuration.architecture == Architecture.simarm64) {
+            configuration.architecture == Architecture.simarm64 ||
+            configuration.system == System.android) {
           return new VMKernelCompilerConfiguration(configuration);
         }
         return new NoneCompilerConfiguration(configuration);
@@ -260,11 +261,17 @@
     } else if (_configuration.hotReloadRollback) {
       args.add('--hot-reload-rollback-test-mode');
     }
+    var filename = artifact.filename;
+    if (runtimeConfiguration is DartkAdbRuntimeConfiguration) {
+      // On Android the Dill file will be pushed to a different directory on the
+      // device. Use that one instead.
+      filename = "${DartkAdbRuntimeConfiguration.DeviceTestDir}/out.dill";
+    }
     return args
       ..addAll(vmOptions)
       ..addAll(sharedOptions)
       ..addAll(_configuration.sharedOptions)
-      ..addAll(_replaceDartFiles(originalArguments, artifact.filename))
+      ..addAll(_replaceDartFiles(originalArguments, filename))
       ..addAll(dartOptions);
   }
 }
diff --git a/tools/testing/dart/options.dart b/tools/testing/dart/options.dart
index 439ae3e..67f5b27 100644
--- a/tools/testing/dart/options.dart
+++ b/tools/testing/dart/options.dart
@@ -25,7 +25,8 @@
   'analyze_library',
   'service',
   'kernel',
-  'observatory_ui'
+  'observatory_ui',
+  'ffi'
 ];
 
 /// Specifies a single command line option.
diff --git a/tools/testing/dart/runtime_configuration.dart b/tools/testing/dart/runtime_configuration.dart
index af21399..c523839 100644
--- a/tools/testing/dart/runtime_configuration.dart
+++ b/tools/testing/dart/runtime_configuration.dart
@@ -42,6 +42,9 @@
         return new NoneRuntimeConfiguration();
 
       case Runtime.vm:
+        if (configuration.system == System.android) {
+          return new DartkAdbRuntimeConfiguration();
+        }
         return new StandaloneDartRuntimeConfiguration();
 
       case Runtime.dartPrecompiled:
@@ -79,6 +82,7 @@
       CommandArtifact artifact,
       List<String> arguments,
       Map<String, String> environmentOverrides,
+      List<String> extraLibs,
       bool isCrashExpected) {
     // TODO(ahe): Make this method abstract.
     throw "Unimplemented runtime '$runtimeType'";
@@ -98,6 +102,7 @@
       CommandArtifact artifact,
       List<String> arguments,
       Map<String, String> environmentOverrides,
+      List<String> extraLibs,
       bool isCrashExpected) {
     return <Command>[];
   }
@@ -125,6 +130,7 @@
       CommandArtifact artifact,
       List<String> arguments,
       Map<String, String> environmentOverrides,
+      List<String> extraLibs,
       bool isCrashExpected) {
     // TODO(ahe): Avoid duplication of this method between d8 and jsshell.
     checkArtifact(artifact);
@@ -148,6 +154,7 @@
       CommandArtifact artifact,
       List<String> arguments,
       Map<String, String> environmentOverrides,
+      List<String> extraLibs,
       bool isCrashExpected) {
     checkArtifact(artifact);
     return [
@@ -207,6 +214,7 @@
       CommandArtifact artifact,
       List<String> arguments,
       Map<String, String> environmentOverrides,
+      List<String> extraLibs,
       bool isCrashExpected) {
     String script = artifact.filename;
     String type = artifact.mimeType;
@@ -237,6 +245,7 @@
       CommandArtifact artifact,
       List<String> arguments,
       Map<String, String> environmentOverrides,
+      List<String> extraLibs,
       bool isCrashExpected) {
     String script = artifact.filename;
     String type = artifact.mimeType;
@@ -251,6 +260,31 @@
   }
 }
 
+class DartkAdbRuntimeConfiguration extends DartVmRuntimeConfiguration {
+  static const String DeviceDir = '/data/local/tmp/testing';
+  static const String DeviceTestDir = '/data/local/tmp/testing/test';
+
+  List<Command> computeRuntimeCommands(
+      TestSuite suite,
+      CommandArtifact artifact,
+      List<String> arguments,
+      Map<String, String> environmentOverrides,
+      List<String> extraLibs,
+      bool isCrashExpected) {
+    final String script = artifact.filename;
+    final String type = artifact.mimeType;
+    if (script != null && type != 'application/kernel-ir-fully-linked') {
+      throw "dart cannot run files of type '$type'.";
+    }
+
+    final String buildPath = suite.buildDir;
+    final String processTest = suite.processTestBinaryFileName;
+    return [
+      Command.adbDartk(buildPath, processTest, script, arguments, extraLibs)
+    ];
+  }
+}
+
 class DartPrecompiledAdbRuntimeConfiguration
     extends DartVmRuntimeConfiguration {
   static const String DeviceDir = '/data/local/tmp/precompilation-testing';
@@ -265,6 +299,7 @@
       CommandArtifact artifact,
       List<String> arguments,
       Map<String, String> environmentOverrides,
+      List<String> extraLibs,
       bool isCrashExpected) {
     String script = artifact.filename;
     String type = artifact.mimeType;
@@ -302,6 +337,7 @@
       CommandArtifact artifact,
       List<String> arguments,
       Map<String, String> environmentOverrides,
+      List<String> extraLibs,
       bool isCrashExpected) {
     String executable = suite.dartVmBinaryFileName;
     return selfCheckers
@@ -324,6 +360,7 @@
       CommandArtifact artifact,
       List<String> arguments,
       Map<String, String> environmentOverrides,
+      List<String> extraLibs,
       bool isCrashExpected) {
     throw "Unimplemented runtime '$runtimeType'";
   }
diff --git a/tools/testing/dart/test_configurations.dart b/tools/testing/dart/test_configurations.dart
index d20f969..32b9559 100644
--- a/tools/testing/dart/test_configurations.dart
+++ b/tools/testing/dart/test_configurations.dart
@@ -41,6 +41,7 @@
   new Path('tests/lib_2'),
   new Path('tests/standalone'),
   new Path('tests/standalone_2'),
+  new Path('tests/ffi'),
   new Path('utils/tests/peg'),
 ];
 
@@ -235,8 +236,7 @@
   // make a pool of all available adb devices.
   AdbDevicePool adbDevicePool;
   var needsAdbDevicePool = configurations.any((conf) {
-    return conf.runtime == Runtime.dartPrecompiled &&
-        conf.system == System.android;
+    return conf.system == System.android;
   });
   if (needsAdbDevicePool) {
     adbDevicePool = await AdbDevicePool.create();
diff --git a/tools/testing/dart/test_runner.dart b/tools/testing/dart/test_runner.dart
index 1b1d03a..6deb34a 100644
--- a/tools/testing/dart/test_runner.dart
+++ b/tools/testing/dart/test_runner.dart
@@ -1217,13 +1217,20 @@
           .runCommand(command.displayName, command, timeout, command.arguments);
     } else if (command is ScriptCommand) {
       return command.run();
-    } else if (command is AdbPrecompilationCommand) {
+    } else if (command is AdbPrecompilationCommand ||
+        command is AdbDartkCommand) {
       assert(adbDevicePool != null);
-      return adbDevicePool.acquireDevice().then((AdbDevice device) {
-        return _runAdbPrecompilationCommand(device, command, timeout)
-            .whenComplete(() {
-          adbDevicePool.releaseDevice(device);
-        });
+      return adbDevicePool.acquireDevice().then((AdbDevice device) async {
+        try {
+          if (command is AdbPrecompilationCommand) {
+            return await _runAdbPrecompilationCommand(device, command, timeout);
+          } else {
+            return await _runAdbDartkCommand(
+                device, command as AdbDartkCommand, timeout);
+          }
+        } finally {
+          await adbDevicePool.releaseDevice(device);
+        }
       });
     } else if (command is VmBatchCommand) {
       var name = command.displayName;
@@ -1313,6 +1320,70 @@
         utf8.encode('$writer'), [], stopwatch.elapsed, false);
   }
 
+  Future<CommandOutput> _runAdbDartkCommand(
+      AdbDevice device, AdbDartkCommand command, int timeout) async {
+    final String buildPath = command.buildPath;
+    final String processTest = command.processTestFilename;
+    final String hostKernelFile = command.kernelFile;
+    final List<String> arguments = command.arguments;
+    final String devicedir = DartkAdbRuntimeConfiguration.DeviceDir;
+    final String deviceTestDir = DartkAdbRuntimeConfiguration.DeviceTestDir;
+
+    final timeoutDuration = new Duration(seconds: timeout);
+
+    final steps = <StepFunction>[];
+
+    steps.add(() => device.runAdbShellCommand(['rm', '-Rf', deviceTestDir]));
+    steps.add(() => device.runAdbShellCommand(['mkdir', '-p', deviceTestDir]));
+    steps.add(
+        () => device.pushCachedData("${buildPath}/dart", '$devicedir/dart'));
+    steps.add(() => device
+        .runAdbCommand(['push', hostKernelFile, '$deviceTestDir/out.dill']));
+
+    for (final String lib in command.extraLibraries) {
+      final String libname = "lib${lib}.so";
+      steps.add(() => device.runAdbCommand(
+          ['push', '${buildPath}/$libname', '$deviceTestDir/$libname']));
+    }
+
+    steps.add(() => device.runAdbShellCommand(
+        [
+          'export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:$deviceTestDir;'
+              '$devicedir/dart',
+        ]..addAll(arguments),
+        timeout: timeoutDuration));
+
+    final stopwatch = new Stopwatch()..start();
+    final writer = new StringBuffer();
+
+    await device.waitForBootCompleted();
+    await device.waitForDevice();
+
+    AdbCommandResult result;
+    for (var i = 0; i < steps.length; i++) {
+      var step = steps[i];
+      var commandStopwatch = new Stopwatch()..start();
+      result = await step();
+
+      writer.writeln("Executing ${result.command}");
+      if (result.stdout.length > 0) {
+        writer.writeln("Stdout:\n${result.stdout.trim()}");
+      }
+      if (result.stderr.length > 0) {
+        writer.writeln("Stderr:\n${result.stderr.trim()}");
+      }
+      writer.writeln("ExitCode: ${result.exitCode}");
+      writer.writeln("Time: ${commandStopwatch.elapsed}");
+      writer.writeln("");
+
+      // If one command fails, we stop processing the others and return
+      // immediately.
+      if (result.exitCode != 0) break;
+    }
+    return createCommandOutput(command, result.exitCode, result.timedOut,
+        utf8.encode('$writer'), [], stopwatch.elapsed, false);
+  }
+
   BatchRunnerProcess _getBatchRunner(String identifier) {
     // Start batch processes if needed
     var runners = _batchProcesses[identifier];
diff --git a/tools/testing/dart/test_suite.dart b/tools/testing/dart/test_suite.dart
index f551202..ba84c67 100644
--- a/tools/testing/dart/test_suite.dart
+++ b/tools/testing/dart/test_suite.dart
@@ -883,8 +883,13 @@
     }
 
     return commands
-      ..addAll(configuration.runtimeConfiguration.computeRuntimeCommands(this,
-          compilationArtifact, runtimeArguments, environment, isCrashExpected));
+      ..addAll(configuration.runtimeConfiguration.computeRuntimeCommands(
+          this,
+          compilationArtifact,
+          runtimeArguments,
+          environment,
+          info.optionsFromFile["sharedObjects"] as List<String>,
+          isCrashExpected));
   }
 
   CreateTest makeTestCaseCreator(Map<String, dynamic> optionsFromFile) {
@@ -1200,6 +1205,11 @@
    *   .html instead of .dart exists, the test was intended to be a web test
    *   and no wrapping is necessary.
    *
+   *     // SharedObjects=foobar
+   *
+   *   - This test requires libfoobar.so, libfoobar.dylib or foobar.dll to be
+   *   in the system linker path of the VM.
+   *
    *   - 'test.dart' assumes tests fail if
    *   the process returns a non-zero exit code (in the case of web tests, we
    *   check for PASS/FAIL indications in the test output).
@@ -1215,6 +1225,7 @@
     RegExp environmentRegExp = new RegExp(r"// Environment=(.*)");
     RegExp otherScriptsRegExp = new RegExp(r"// OtherScripts=(.*)");
     RegExp otherResourcesRegExp = new RegExp(r"// OtherResources=(.*)");
+    RegExp sharedObjectsRegExp = new RegExp(r"// SharedObjects=(.*)");
     RegExp packageRootRegExp = new RegExp(r"// PackageRoot=(.*)");
     RegExp packagesRegExp = new RegExp(r"// Packages=(.*)");
     RegExp isolateStubsRegExp = new RegExp(r"// IsolateStubs=(.*)");
@@ -1317,6 +1328,12 @@
       otherResources.addAll(wordSplit(match[1]));
     }
 
+    var sharedObjects = <String>[];
+    matches = sharedObjectsRegExp.allMatches(contents);
+    for (var match in matches) {
+      sharedObjects.addAll(wordSplit(match[1]));
+    }
+
     var isMultitest = multiTestRegExp.hasMatch(contents);
     var isMultiHtmlTest = multiHtmlTestRegExp.hasMatch(contents);
     var isolateMatch = isolateStubsRegExp.firstMatch(contents);
@@ -1374,6 +1391,7 @@
       "hasStaticWarning": hasStaticWarning,
       "otherScripts": otherScripts,
       "otherResources": otherResources,
+      "sharedObjects": sharedObjects,
       "isMultitest": isMultitest,
       "isMultiHtmlTest": isMultiHtmlTest,
       "subtestNames": subtestNames,