am a5422006: Merge "Support Native Tests in CTS" into ics-mr1

* commit 'a542200653601eccd20b0382162267cd91673bfc':
  Support Native Tests in CTS
diff --git a/Android.mk b/Android.mk
index e3f9ba8..159bda0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -14,6 +14,6 @@
 # limitations under the License.
 #
 
+include cts/CtsNativeTestCase.mk
 include cts/CtsTestCoverage.mk
-
 include $(call all-subdir-makefiles)
diff --git a/CtsNativeTestCase.mk b/CtsNativeTestCase.mk
new file mode 100644
index 0000000..a648fa0
--- /dev/null
+++ b/CtsNativeTestCase.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Include this file to gain access to functions to build native CTS
+# test packages. Replace "include $(BUILD_EXECUTABLE)" with
+# "include $(BUILD_CTS_EXECUTABLE)".
+
+LOCAL_PATH := $(call my-dir)
+BUILD_CTS_EXECUTABLE := $(LOCAL_PATH)/tools/build/test_executable.mk
+
+CTS_NATIVE_XML_OUT := $(HOST_OUT)/cts-native-xml
+
+CTS_NATIVE_XML_GENERATOR := $(HOST_OUT_EXECUTABLES)/cts-native-xml-generator
+
+define cts-get-native-paths
+	$(foreach exe,$(1),$(call intermediates-dir-for,EXECUTABLES,$(1))/$(1))
+endef
+
+define cts-get-native-xmls
+	$(foreach exe,$(1),$(CTS_NATIVE_XML_OUT)/$(exe).xml)
+endef
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 3923aac..060e824 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -75,5 +75,12 @@
 	$(CTS_COVERAGE_TEST_CASE_LIST) \
 	$(CTS_SECURITY_APPS_LIST)
 
+CTS_NATIVE_EXES := \
+	CtsNativeMediaTestCases
+
+CTS_TEST_CASES := $(call cts-get-native-paths,$(CTS_NATIVE_EXES))
+
+CTS_TEST_XMLS := $(call cts-get-native-xmls,$(CTS_NATIVE_EXES))
+
 # The following files will be placed in the tools directory of the CTS distribution
 CTS_TOOLS_LIST :=
diff --git a/development/ide/eclipse/.classpath b/development/ide/eclipse/.classpath
index a38f53a..30c63d8 100644
--- a/development/ide/eclipse/.classpath
+++ b/development/ide/eclipse/.classpath
@@ -58,6 +58,7 @@
     <classpathentry kind="src" path="cts/tests/tests/webkit/src"/>
     <classpathentry kind="src" path="cts/tests/tests/widget/src"/>
     <classpathentry kind="src" path="cts/tools/cts-api-coverage/src"/>
+    <classpathentry kind="src" path="cts/tools/cts-native-xml-generator/src"/>
     <classpathentry kind="src" path="cts/tools/cts-reference-app-lib/src"/>
     <classpathentry kind="src" path="cts/tools/dasm/src"/>
     <classpathentry kind="src" path="cts/tools/device-setup/TestDeviceSetup/src"/>
diff --git a/tests/tests/nativemedia/Android.mk b/tests/tests/nativemedia/Android.mk
index 928ebcd..94bc83e 100644
--- a/tests/tests/nativemedia/Android.mk
+++ b/tests/tests/nativemedia/Android.mk
@@ -3,7 +3,7 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_TAGS := optional
 
 LOCAL_C_INCLUDES:= \
     bionic \
@@ -27,8 +27,9 @@
 
 LOCAL_CFLAGS += -DXP_UNIX
 
-LOCAL_MODULE:= NativeMediaTest
+LOCAL_MODULE := CtsNativeMediaTestCases
 
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
 
-include $(BUILD_EXECUTABLE)
\ No newline at end of file
+LOCAL_CTS_TEST_PACKAGE := android.nativemedia
+include $(BUILD_CTS_EXECUTABLE)
diff --git a/tools/build/test_executable.mk b/tools/build/test_executable.mk
new file mode 100644
index 0000000..bfd5bee
--- /dev/null
+++ b/tools/build/test_executable.mk
@@ -0,0 +1,38 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Builds an executable and defines a rule to generate the associated test
+# package XML needed by CTS.
+#
+# 1. Replace "include $(BUILD_EXECUTABLE)"
+#    with "include $(BUILD_CTS_EXECUTABLE)"
+#
+# 2. Define LOCAL_CTS_TEST_PACKAGE to group the tests under a package
+#    as needed by CTS.
+#
+
+include $(BUILD_EXECUTABLE)
+
+cts_executable_xml := $(CTS_NATIVE_XML_OUT)/$(LOCAL_MODULE).xml
+
+$(cts_executable_xml): PRIVATE_PATH := $(LOCAL_PATH)
+$(cts_executable_xml): PRIVATE_TEST_PACKAGE := $(LOCAL_CTS_TEST_PACKAGE)
+$(cts_executable_xml): PRIVATE_EXECUTABLE := $(LOCAL_MODULE)
+$(cts_executable_xml): $(addprefix $(LOCAL_PATH)/,$(LOCAL_SRC_FILES)) | $(CTS_NATIVE_XML_GENERATOR)
+	$(hide) echo Generating test description for native package $(PRIVATE_TEST_PACKAGE)
+	$(hide) $(CTS_NATIVE_XML_GENERATOR) -p $(PRIVATE_TEST_PACKAGE) \
+			-n $(PRIVATE_EXECUTABLE) \
+			-s $(PRIVATE_PATH) \
+			-o $@
diff --git a/tools/cts-native-xml-generator/Android.mk b/tools/cts-native-xml-generator/Android.mk
new file mode 100644
index 0000000..c02d745
--- /dev/null
+++ b/tools/cts-native-xml-generator/Android.mk
@@ -0,0 +1,43 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# We use copy-file-to-new-target so that the installed
+# script file's timestamp is at least as new as the
+# .jar file it wraps.
+
+# the hat script
+# ============================================================
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE := cts-native-xml-generator
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_SYSTEM)/base_rules.mk
+
+$(LOCAL_BUILT_MODULE): $(HOST_OUT_JAVA_LIBRARIES)/$(LOCAL_MODULE)$(COMMON_JAVA_PACKAGE_SUFFIX)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/$(LOCAL_MODULE) | $(ACP)
+	@echo "Copy: $(PRIVATE_MODULE) ($@)"
+	$(copy-file-to-new-target)
+	$(hide) chmod 755 $@
+
+# the other stuff
+# ============================================================
+subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \
+		src \
+	))
+
+include $(subdirs)
diff --git a/tools/cts-native-xml-generator/etc/cts-native-xml-generator b/tools/cts-native-xml-generator/etc/cts-native-xml-generator
new file mode 100644
index 0000000..cae5f4a
--- /dev/null
+++ b/tools/cts-native-xml-generator/etc/cts-native-xml-generator
@@ -0,0 +1,46 @@
+#!/bin/bash
+#
+# Copyright (C) 2011, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+    newProg=`/bin/ls -ld "${prog}"`
+    newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+    if expr "x${newProg}" : 'x/' >/dev/null; then
+        prog="${newProg}"
+    else
+        progdir=`dirname "${prog}"`
+        prog="${progdir}/${newProg}"
+    fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+cd "${oldwd}"
+
+libdir=`dirname $progdir`/framework
+
+javaOpts=""
+while expr "x$1" : 'x-J' >/dev/null; do
+    opt=`expr "$1" : '-J\(.*\)'`
+    javaOpts="${javaOpts} -${opt}"
+    shift
+done
+
+exec java $javaOpts -jar $libdir/cts-native-xml-generator.jar "$@"
diff --git a/tools/cts-native-xml-generator/src/Android.mk b/tools/cts-native-xml-generator/src/Android.mk
new file mode 100644
index 0000000..70b9f49
--- /dev/null
+++ b/tools/cts-native-xml-generator/src/Android.mk
@@ -0,0 +1,29 @@
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+
+# cts-native-xml-generator java library
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_JAVA_RESOURCE_DIRS := res
+LOCAL_JAR_MANIFEST := MANIFEST.mf
+
+LOCAL_MODULE := cts-native-xml-generator
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/cts-native-xml-generator/src/MANIFEST.mf b/tools/cts-native-xml-generator/src/MANIFEST.mf
new file mode 100644
index 0000000..b8bde72
--- /dev/null
+++ b/tools/cts-native-xml-generator/src/MANIFEST.mf
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: com.android.cts.nativexml.CtsNativeXmlGenerator
diff --git a/tools/cts-native-xml-generator/src/com/android/cts/nativexml/CtsNativeXmlGenerator.java b/tools/cts-native-xml-generator/src/com/android/cts/nativexml/CtsNativeXmlGenerator.java
new file mode 100644
index 0000000..1a436dd
--- /dev/null
+++ b/tools/cts-native-xml-generator/src/com/android/cts/nativexml/CtsNativeXmlGenerator.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.nativexml;
+
+import java.io.File;
+import java.util.Arrays;
+
+/**
+ * Class that searches a source directory for native gTests and outputs a
+ * test package xml.
+ */
+public class CtsNativeXmlGenerator {
+
+    private static void usage(String[] args) {
+        System.err.println("Arguments: " + Arrays.asList(args));
+        System.err.println("Usage: cts-native-xml-generator -p PACKAGE_NAME -n EXECUTABLE_NAME "
+                + "-s SOURCE_DIR [-o OUTPUT_FILE]");
+        System.exit(1);
+    }
+
+    public static void main(String[] args) throws Exception {
+        String appPackageName = null;
+        String name = null;
+        File sourceDir = null;
+        String outputPath = null;
+
+        for (int i = 0; i < args.length; i++) {
+            if ("-p".equals(args[i])) {
+                if (i + 1 < args.length) {
+                    appPackageName = args[++i];
+                } else {
+                    System.err.println("Missing value for test package");
+                    usage(args);
+                }
+            } else if ("-n".equals(args[i])) {
+                if (i + 1 < args.length) {
+                    name = args[++i];
+                } else {
+                    System.err.println("Missing value for executable name");
+                    usage(args);
+                }
+            } else if ("-o".equals(args[i])) {
+                if (i + 1 < args.length) {
+                    outputPath = args[++i];
+                } else {
+                    System.err.println("Missing value for output file");
+                    usage(args);
+                }
+            } else if ("-s".equals(args[i])) {
+                if (i + 1 < args.length) {
+                    sourceDir = new File(args[++i]);
+                } else {
+                    System.err.println("Missing value for source directory");
+                    usage(args);
+                }
+            } else {
+                System.err.println("Unsupported flag: " + args[i]);
+                usage(args);
+            }
+        }
+
+        if (appPackageName == null) {
+            System.out.println("Package name is required");
+            usage(args);
+        } else if (name == null) {
+            System.out.println("Executable name is required");
+            usage(args);
+        } else if (sourceDir == null) {
+            System.out.println("Source directory is required");
+            usage(args);
+        }
+
+        Generator generator = new Generator(appPackageName, name, sourceDir, outputPath);
+        generator.writePackageXml();
+    }
+}
diff --git a/tools/cts-native-xml-generator/src/com/android/cts/nativexml/Generator.java b/tools/cts-native-xml-generator/src/com/android/cts/nativexml/Generator.java
new file mode 100644
index 0000000..3a75e49
--- /dev/null
+++ b/tools/cts-native-xml-generator/src/com/android/cts/nativexml/Generator.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.nativexml;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Scanner;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Generator of TestPackage XML files for native gTests.
+ *
+ * It scours all the C++ source files in a given source directory looking
+ * for test declarations and outputs a XML test listing.
+ */
+class Generator {
+
+    /** Test package name like "android.nativemedia" to group the tests. */
+    private final String mAppPackageName;
+
+    /** Name of the native executable. */
+    private final String mName;
+
+    /** Directory to recursively scan for gTest test declarations. */
+    private final File mSourceDir;
+
+    /** Path to output file or null to just dump to standard out. */
+    private final String mOutputPath;
+
+    Generator(String appPackageName, String name, File sourceDir, String outputPath) {
+        mAppPackageName = appPackageName;
+        mName = name;
+        mSourceDir = sourceDir;
+        mOutputPath = outputPath;
+    }
+
+    public void writePackageXml() throws IOException {
+        OutputStream output = System.out;
+        if (mOutputPath != null) {
+            File outputFile = new File(mOutputPath);
+            File outputDir = outputFile.getParentFile();
+            if (!outputDir.exists() && !outputDir.mkdirs()) {
+                System.err.println("Couldn't make output directory: " + mOutputPath);
+                System.exit(1);
+            }
+            output = new FileOutputStream(outputFile);
+        }
+
+        PrintWriter writer = null;
+        try {
+            writer = new PrintWriter(output);
+            writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+            writeTestPackage(writer);
+        } finally {
+            if (writer != null) {
+                writer.close();
+            }
+        }
+    }
+
+    private void writeTestPackage(PrintWriter writer) throws FileNotFoundException {
+        writer.append("<TestPackage appPackageName=\"")
+                .append(mAppPackageName)
+                .append("\" name=\"")
+                .append(mName)
+                .println("\" testType=\"native\" version=\"1.0\">");
+        writeTestSuite(writer);
+        writer.println("</TestPackage>");
+    }
+
+    private void writeTestSuite(PrintWriter writer) throws FileNotFoundException {
+        /*
+         * Given "android.foo.bar.baz"...
+         *
+         * <TestSuite name="android">
+         *   <TestSuite name="foo">
+         *     <TestSuite name="bar">
+         *       <TestSuite name="baz">
+         */
+        Scanner scanner = null;
+        try {
+            scanner = new Scanner(mAppPackageName);
+            scanner.useDelimiter("\\.");
+
+            int numLevels = 0;
+            for (; scanner.hasNext(); numLevels++) {
+                String packagePart = scanner.next();
+                writer.append("<TestSuite name=\"").append(packagePart).println("\">");
+            }
+
+            writeTestCases(writer, mSourceDir);
+
+            for (; numLevels > 0; numLevels--) {
+                writer.println("</TestSuite>");
+            }
+        } finally {
+            if (scanner != null) {
+                scanner.close();
+            }
+        }
+    }
+
+    private void writeTestCases(PrintWriter writer, File dir) throws FileNotFoundException {
+        // Find both C++ files to find tests and directories to look for more tests!
+        File[] files = dir.listFiles(new FilenameFilter() {
+            @Override
+            public boolean accept(File dir, String filename) {
+                return filename.endsWith(".cpp") || new File(dir, filename).isDirectory();
+            }
+        });
+
+        for (int i = 0; i < files.length; i++) {
+            File file = files[i];
+            if (file.isDirectory()) {
+                writeTestCases(writer, file);
+            } else {
+                // Take the test name from the name of the file. It's probably
+                // more accurate to take the name from inside the file...
+                String fileName = file.getName();
+                int extension = fileName.lastIndexOf('.');
+                if (extension != -1) {
+                    fileName = fileName.substring(0, extension);
+                }
+
+                writer.append("<TestCase name=\"").append(fileName).println("\">");
+                writeTests(writer, file);
+                writer.println("</TestCase>");
+            }
+        }
+    }
+
+    // We want to find lines like TEST_F(SLObjectCreationTest, testAudioPlayerFromFdCreation) { ...
+    // and extract the "testAudioPlayerFromFdCreation" as group #1
+    private static final Pattern TEST_REGEX = Pattern.compile("\\s*TEST_F\\(\\w+,\\s*(\\w+)\\).*");
+
+    private void writeTests(PrintWriter writer, File file) throws FileNotFoundException {
+        Scanner scanner = null;
+        try {
+            scanner = new Scanner(file);
+            while (scanner.hasNextLine()) {
+                String line = scanner.nextLine();
+                Matcher matcher = TEST_REGEX.matcher(line);
+                if (matcher.matches()) {
+                    String name = matcher.group(1);
+                    writer.append("<Test name=\"").append(name).println("\" />");
+                }
+            }
+        } finally {
+            if (scanner != null) {
+                scanner.close();
+            }
+        }
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/GeeTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/GeeTest.java
new file mode 100644
index 0000000..5cfafc6
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/GeeTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.tradefed.testtype;
+
+import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.ddmlib.testrunner.ITestRunListener;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import java.io.File;
+
+/**
+ * Test runner for native gTests.
+ *
+ * TODO: This is similar to Tradefed's existing GTest, but it doesn't confirm
+ *       each directory segment exists using ddmlib's file service. This was
+ *       a problem since /data is not visible on a user build, but it is
+ *       executable. It's also a lot more verbose when it comes to errors.
+ */
+public class GeeTest implements IBuildReceiver, IDeviceTest, IRemoteTest {
+
+    private static final String NATIVE_TESTS_DIRECTORY = "/data/local/tmp/cts-native-tests";
+
+    private int mMaxTestTimeMs = 1 * 60 * 1000;
+
+    private CtsBuildHelper mCtsBuild;
+    private ITestDevice mDevice;
+
+    private final String mPackageName;
+    private final String mExeName;
+
+    public GeeTest(String packageName, String exeName) {
+        mPackageName = packageName;
+        mExeName = exeName;
+    }
+
+    @Override
+    public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
+        if (installTest()) {
+            runTest(listener);
+        } else {
+            CLog.e("Failed to install native tests");
+        }
+    }
+
+    private boolean installTest() throws DeviceNotAvailableException {
+        if (!createRemoteDir(NATIVE_TESTS_DIRECTORY)) {
+            CLog.e("Could not create directory for native tests: " + NATIVE_TESTS_DIRECTORY);
+            return false;
+        }
+
+        File nativeExe = new File(mCtsBuild.getTestCasesDir(), mExeName);
+        if (!nativeExe.exists()) {
+            CLog.e("Native test not found: " + nativeExe);
+            return false;
+        }
+
+        File devicePath = new File(NATIVE_TESTS_DIRECTORY, mExeName);
+        if (!mDevice.pushFile(nativeExe, devicePath.toString())) {
+            CLog.e("Failed to push native test to device");
+            return false;
+        }
+        return true;
+    }
+
+    private boolean createRemoteDir(String remoteFilePath) throws DeviceNotAvailableException {
+        if (mDevice.doesFileExist(remoteFilePath)) {
+            return true;
+        }
+        File remoteFile = new File(remoteFilePath);
+        String parentPath = remoteFile.getParent();
+        if (parentPath != null) {
+            if (!createRemoteDir(parentPath)) {
+                return false;
+            }
+        }
+        mDevice.executeShellCommand(String.format("mkdir %s", remoteFilePath));
+        return mDevice.doesFileExist(remoteFilePath);
+    }
+
+    void runTest(ITestRunListener listener) throws DeviceNotAvailableException {
+        GeeTestResultParser resultParser = new GeeTestResultParser(mPackageName, listener);
+        resultParser.setFakePackagePrefix(mPackageName + ".");
+
+        String fullPath = NATIVE_TESTS_DIRECTORY + File.separator + mExeName;
+        String flags = "";
+        CLog.v("Running gtest %s %s on %s", fullPath, flags, mDevice.getSerialNumber());
+        // force file to be executable
+        CLog.v("%s", mDevice.executeShellCommand(String.format("chmod 755 %s", fullPath)));
+
+        try {
+            mDevice.executeShellCommand(String.format("%s %s", fullPath, flags), resultParser,
+                    mMaxTestTimeMs /* maxTimeToShellOutputResponse */,
+                    0 /* retryAttempts */);
+        } catch (DeviceNotAvailableException e) {
+            resultParser.flush();
+            throw e;
+        } catch (RuntimeException e) {
+            resultParser.flush();
+            throw e;
+        }
+    }
+
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
+    }
+
+    @Override
+    public void setDevice(ITestDevice device) {
+        mDevice = device;
+    }
+
+    @Override
+    public ITestDevice getDevice() {
+        return mDevice;
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/GeeTestResultParser.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/GeeTestResultParser.java
new file mode 100644
index 0000000..c01da20
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/GeeTestResultParser.java
@@ -0,0 +1,670 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.tradefed.testtype;
+
+import com.android.ddmlib.IShellOutputReceiver;
+import com.android.ddmlib.Log;
+import com.android.ddmlib.MultiLineReceiver;
+import com.android.ddmlib.testrunner.ITestRunListener;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.testtype.testdefs.XmlDefsTest;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * TODO: Merge change to add package prefix to tradefed's GTestResultParser.
+ *       Otherwise this file should be the same as TradeFed's version.
+ *
+ * Parses the 'raw output mode' results of native tests using GTest that run from shell, and informs
+ * a ITestRunListener of the results.
+ * <p>Sample format of output expected:
+ *
+ * <pre>
+ * [==========] Running 15 tests from 1 test case.
+ * [----------] Global test environment set-up.
+ * [----------] 15 tests from MessageTest
+ * [ RUN      ] MessageTest.DefaultConstructor
+ * [       OK ] MessageTest.DefaultConstructor (1 ms)
+ * [ RUN      ] MessageTest.CopyConstructor
+ * external/gtest/test/gtest-message_test.cc:67: Failure
+ * Value of: 5
+ * Expected: 2
+ * external/gtest/test/gtest-message_test.cc:68: Failure
+ * Value of: 1 == 1
+ * Actual: true
+ * Expected: false
+ * [  FAILED  ] MessageTest.CopyConstructor (2 ms)
+ *  ...
+ * [ RUN      ] MessageTest.DoesNotTakeUpMuchStackSpace
+ * [       OK ] MessageTest.DoesNotTakeUpMuchStackSpace (0 ms)
+ * [----------] 15 tests from MessageTest (26 ms total)
+ *
+ * [----------] Global test environment tear-down
+ * [==========] 15 tests from 1 test case ran. (26 ms total)
+ * [  PASSED  ] 6 tests.
+ * [  FAILED  ] 9 tests, listed below:
+ * [  FAILED  ] MessageTest.CopyConstructor
+ * [  FAILED  ] MessageTest.ConstructsFromCString
+ * [  FAILED  ] MessageTest.StreamsCString
+ * [  FAILED  ] MessageTest.StreamsNullCString
+ * [  FAILED  ] MessageTest.StreamsString
+ * [  FAILED  ] MessageTest.StreamsStringWithEmbeddedNUL
+ * [  FAILED  ] MessageTest.StreamsNULChar
+ * [  FAILED  ] MessageTest.StreamsInt
+ * [  FAILED  ] MessageTest.StreamsBasicIoManip
+ * 9 FAILED TESTS
+ * </pre>
+ *
+ * <p>where the following tags are used to signal certain events:
+ * <pre>
+ * [==========]: the first occurrence indicates a new run started, including the number of tests
+ *                  to be expected in this run
+ * [ RUN      ]: indicates a new test has started to run; a series of zero or more lines may
+ *                  follow a test start, and will be captured in case of a test failure or error
+ * [       OK ]: the preceding test has completed successfully, optionally including the time it
+ *                  took to run (in ms)
+ * [  FAILED  ]: the preceding test has failed, optionally including the time it took to run (in ms)
+ * [==========]: the preceding test run has completed, optionally including the time it took to run
+ *                  (in ms)
+ * </pre>
+ *
+ * All other lines are ignored.
+ */
+public class GeeTestResultParser extends MultiLineReceiver {
+    private static final String LOG_TAG = "GTestResultParser";
+
+    // Variables to keep track of state
+    private TestResult mCurrentTestResult = null;
+    private int mNumTestsRun = 0;
+    private int mNumTestsExpected = 0;
+    private long mTotalRunTime = 0;
+    private boolean mTestInProgress = false;
+    private boolean mTestRunInProgress = false;
+    private final String mTestRunName;
+    private final Collection<ITestRunListener> mTestListeners;
+
+    /** Fake adding a package prefix if the test listener needs it. */
+    private String mFakePackagePrefix = "";
+
+    /** True if start of test has already been reported to listener. */
+    private boolean mTestRunStartReported = false;
+
+    /** True if current test run has been canceled by user. */
+    private boolean mIsCancelled = false;
+
+    private String mCoverageTarget = null;
+
+    /**
+     * Test result data
+     */
+    private static class TestResult {
+        private String mTestName = null;
+        private String mTestClass = null;
+        private StringBuilder mStackTrace = null;
+        @SuppressWarnings("unused")
+        private Long mRunTime = null;
+
+        /** Returns whether expected values have been parsed
+         *
+         * @return true if all expected values have been parsed
+         */
+        boolean isComplete() {
+            return mTestName != null && mTestClass != null;
+        }
+
+        /** Returns whether there is currently a stack trace
+         *
+         * @return true if there is currently a stack trace, false otherwise
+         */
+        boolean hasStackTrace() {
+            return mStackTrace != null;
+        }
+
+        /**
+         * Returns the stack trace of the current test.
+         *
+         * @return a String representation of the current test's stack trace; if there is not
+         * a current stack trace, it returns an error string. Use {@link TestResult#hasStackTrace}
+         * if you need to know whether there is a stack trace.
+         */
+        String getTrace() {
+            if (hasStackTrace()) {
+                return mStackTrace.toString();
+            } else {
+                Log.e(LOG_TAG, "Could not find stack trace for failed test");
+                return new Throwable("Unknown failure").toString();
+            }
+        }
+
+        /** Provides a more user readable string for TestResult, if possible */
+        @Override
+        public String toString() {
+            StringBuilder output = new StringBuilder();
+            if (mTestClass != null ) {
+                output.append(mTestClass);
+                output.append('#');
+            }
+            if (mTestName != null) {
+                output.append(mTestName);
+            }
+            if (output.length() > 0) {
+                return output.toString();
+            }
+            return "unknown result";
+        }
+    }
+
+    /** Internal helper struct to store parsed test info. */
+    private static class ParsedTestInfo {
+        String mTestName = null;
+        String mTestClassName = null;
+        String mTestRunTime = null;
+
+        public ParsedTestInfo(String testName, String testClassName, String testRunTime) {
+            mTestName = testName;
+            mTestClassName = testClassName;
+            mTestRunTime = testRunTime;
+        }
+    }
+
+    /** Prefixes used to demarcate and identify output. */
+    private static class Prefixes {
+        @SuppressWarnings("unused")
+        private static final String INFORMATIONAL_MARKER = "[----------]";
+        private static final String START_TEST_RUN_MARKER = "[==========] Running";
+        private static final String TEST_RUN_MARKER = "[==========]";
+        private static final String START_TEST_MARKER = "[ RUN      ]";
+        private static final String OK_TEST_MARKER = "[       OK ]";
+        private static final String FAILED_TEST_MARKER = "[  FAILED  ]";
+    }
+
+    /**
+     * Creates the GTestResultParser.
+     *
+     * @param testRunName the test run name to provide to
+     *            {@link ITestRunListener#testRunStarted(String, int)}
+     * @param listeners informed of test results as the tests are executing
+     */
+    public GeeTestResultParser(String testRunName, Collection<ITestRunListener> listeners) {
+        mTestRunName = testRunName;
+        mTestListeners = new ArrayList<ITestRunListener>(listeners);
+    }
+
+    /**
+     * Creates the GTestResultParser for a single listener.
+     *
+     * @param testRunName the test run name to provide to
+     *            {@link ITestRunListener#testRunStarted(String, int)}
+     * @param listener informed of test results as the tests are executing
+     */
+    public GeeTestResultParser(String testRunName, ITestRunListener listener) {
+        mTestRunName = testRunName;
+        mTestListeners = new ArrayList<ITestRunListener>(1);
+        mTestListeners.add(listener);
+    }
+
+    /**
+     * Package prefix to be added to test names when they are reported like
+     * "android.nativemedia." You may need to add the dot if you need it.
+     */
+    public void setFakePackagePrefix(String prefix) {
+        mFakePackagePrefix = prefix;
+    }
+
+    /**
+     * Returns the current TestResult for test in progress, or a new default one.
+     *
+     * @return The TestResult for the current test run
+     */
+    private TestResult getCurrentTestResult() {
+        if (mCurrentTestResult == null) {
+            mCurrentTestResult = new TestResult();
+        }
+        return mCurrentTestResult;
+    }
+
+
+    /**
+     * Clears out the current TestResult.
+     */
+    private void clearCurrentTestResult() {
+        mCurrentTestResult = null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void processNewLines(String[] lines) {
+        for (String line : lines) {
+            parse(line);
+        }
+    }
+
+    /**
+     * Parse an individual output line.
+     *
+     * @param line  Text output line
+     */
+    private void parse(String line) {
+        String message = null;
+
+        if (mTestRunInProgress || line.startsWith(Prefixes.TEST_RUN_MARKER)) {
+            if (line.startsWith(Prefixes.START_TEST_MARKER)) {
+                // Individual test started
+                message = line.substring(Prefixes.START_TEST_MARKER.length()).trim();
+                processTestStartedTag(message);
+            }
+            else if (line.startsWith(Prefixes.OK_TEST_MARKER)) {
+                // Individual test completed successfully
+                message = line.substring(Prefixes.OK_TEST_MARKER.length()).trim();
+                processOKTag(message);
+                clearCurrentTestResult();
+            }
+            else if (line.startsWith(Prefixes.FAILED_TEST_MARKER)) {
+                // Individual test completed with failure
+                message = line.substring(Prefixes.FAILED_TEST_MARKER.length()).trim();
+                processFailedTag(message);
+                clearCurrentTestResult();
+            }
+            else if (line.startsWith(Prefixes.START_TEST_RUN_MARKER)) {
+                // Test run started
+                // Make sure to leave the "Running" in the string
+                message = line.substring(Prefixes.TEST_RUN_MARKER.length()).trim();
+                processRunStartedTag(message);
+            }
+            else if (line.startsWith(Prefixes.TEST_RUN_MARKER)) {
+                // Test run ended
+                // This is for the end of the test suite run, so make sure this else-if is after the
+                // check for START_TEST_SUITE_MARKER
+                message = line.substring(Prefixes.TEST_RUN_MARKER.length()).trim();
+                processRunCompletedTag(message);
+            }
+            else if (testInProgress()) {
+                // Note this does not handle the case of an error outside an actual test run
+                appendTestOutputLine(line);
+            }
+        }
+    }
+
+    /**
+     * Returns true if test run canceled.
+     *
+     * @see IShellOutputReceiver#isCancelled()
+     */
+    public boolean isCancelled() {
+        return mIsCancelled;
+    }
+
+    /**
+     * Requests cancellation of test run.
+     */
+    public void cancel() {
+        mIsCancelled = true;
+    }
+
+    /**
+     * Returns whether we're in the middle of running a test.
+     *
+     * @return True if a test was started, false otherwise
+     */
+    private boolean testInProgress() {
+        return mTestInProgress;
+    }
+
+    /**
+     * Set state to indicate we've started running a test.
+     *
+     */
+    private void setTestStarted() {
+        mTestInProgress = true;
+    }
+
+    /**
+     * Set state to indicate we've started running a test.
+     *
+     */
+    private void setTestEnded() {
+        mTestInProgress = false;
+    }
+
+    /**
+     * Reports the start of a test run, and the total test count, if it has not been previously
+     * reported.
+     */
+    private void reportTestRunStarted() {
+        // if start test run not reported yet
+        if (!mTestRunStartReported) {
+            for (ITestRunListener listener : mTestListeners) {
+                listener.testRunStarted(mTestRunName, mNumTestsExpected);
+            }
+            mTestRunStartReported = true;
+        }
+    }
+
+    /**
+     * Reports the end of a test run, and resets that test
+     */
+    private void reportTestRunEnded() {
+        for (ITestRunListener listener : mTestListeners) {
+            listener.testRunEnded(mTotalRunTime, getRunMetrics());
+        }
+        mTestRunStartReported = false;
+    }
+
+    /**
+     * Create the run metrics {@link Map} to report.
+     *
+     * @return a {@link Map} of run metrics data
+     */
+    private Map<String, String> getRunMetrics() {
+        Map<String, String> metricsMap = new HashMap<String, String>();
+        if (mCoverageTarget != null) {
+            metricsMap.put(XmlDefsTest.COVERAGE_TARGET_KEY, mCoverageTarget);
+        }
+        return metricsMap;
+    }
+
+    /**
+     * Parse the test identifier (class and test name), and optional time info.
+     *
+     * @param identifier Raw identifier of the form classname.testname, with an optional time
+     *          element in the format of (XX ms) at the end
+     * @return A ParsedTestInfo representing the parsed info from the identifier string.
+     *
+     *          If no time tag was detected, then the third element in the array (time_in_ms) will
+     *          be null. If the line failed to parse properly (eg: could not determine name of
+     *          test/class) then an "UNKNOWN" string value will be returned for the classname and
+     *          testname. This method guarantees a string will always be returned for the class and
+     *          test names (but not for the time value).
+     */
+    private ParsedTestInfo parseTestIdentifier(String identifier) {
+        ParsedTestInfo returnInfo = new ParsedTestInfo("UNKNOWN_CLASS", "UNKNOWN_TEST", null);
+
+        Pattern timePattern = Pattern.compile(".*(\\((\\d+) ms\\))");  // eg: (XX ms)
+        Matcher time = timePattern.matcher(identifier);
+
+        // Try to find a time
+        if (time.find()) {
+            String timeString = time.group(2);  // the "XX" in "(XX ms)"
+            String discardPortion = time.group(1);  // everything after the test class/name
+            identifier = identifier.substring(0, identifier.lastIndexOf(discardPortion)).trim();
+            returnInfo.mTestRunTime = timeString;
+        }
+
+        String[] testId = identifier.split("\\.");
+        if (testId.length < 2) {
+            Log.e(LOG_TAG, "Could not detect the test class and test name, received: " +
+                    identifier);
+        }
+        else {
+            returnInfo.mTestClassName = testId[0];
+            returnInfo.mTestName = testId[1];
+        }
+        return returnInfo;
+    }
+
+    /**
+     * Parses and stores the test identifier (class and test name).
+     *
+     * @param identifier Raw identifier
+     */
+    private void processRunStartedTag(String identifier) {
+        // eg: (Running XX tests from 1 test case.)
+        Pattern numTestsPattern = Pattern.compile("Running (\\d+) test[s]? from .*");
+        Matcher numTests = numTestsPattern.matcher(identifier);
+
+        // Try to find number of tests
+        if (numTests.find()) {
+            try {
+                mNumTestsExpected = Integer.parseInt(numTests.group(1));
+            }
+            catch (NumberFormatException e) {
+                Log.e(LOG_TAG, "Unable to determine number of tests expected, received: " +
+                        numTests.group(1));
+            }
+        }
+        if (mNumTestsExpected > 0) {
+          reportTestRunStarted();
+          mNumTestsRun = 0;
+          mTestRunInProgress = true;
+        }
+    }
+
+    /**
+     * Processes and informs listener when we encounter a tag indicating that a test suite is done.
+     *
+     * @param identifier Raw log output from the suite ended tag
+     */
+    private void processRunCompletedTag(String identifier) {
+        Pattern timePattern = Pattern.compile(".*\\((\\d+) ms total\\)");  // eg: (XX ms total)
+        Matcher time = timePattern.matcher(identifier);
+
+        // Try to find the total run time
+        if (time.find()) {
+            try {
+                mTotalRunTime = Long.parseLong(time.group(1));
+            }
+            catch (NumberFormatException e) {
+                Log.e(LOG_TAG, "Unable to determine the total running time, received: " +
+                        time.group(1));
+            }
+        }
+        reportTestRunEnded();
+        mTestRunInProgress = false;
+    }
+
+    /**
+     * Processes and informs listener when we encounter a tag indicating that a test has started.
+     *
+     * @param identifier Raw log output of the form classname.testname, with an optional time (x ms)
+     */
+    private void processTestStartedTag(String identifier) {
+        ParsedTestInfo parsedResults = parseTestIdentifier(identifier);
+        TestResult testResult = getCurrentTestResult();
+        testResult.mTestClass = parsedResults.mTestClassName;
+        testResult.mTestName = parsedResults.mTestName;
+
+        TestIdentifier testId = new TestIdentifier(mFakePackagePrefix + testResult.mTestClass,
+                testResult.mTestName);
+
+        for (ITestRunListener listener : mTestListeners) {
+            listener.testStarted(testId);
+        }
+        setTestStarted();
+    }
+
+    /**
+     * Helper method to do the work necessary when a test has ended.
+     *
+     * @param identifier Raw log output of the form "classname.testname" with an optional (XX ms)
+     *          at the end indicating the running time.
+     * @param testPassed Indicates whether the test passed or failed (set to true if passed, false
+     *          if failed)
+     */
+    private void doTestEnded(String identifier, boolean testPassed) {
+        ParsedTestInfo parsedResults = parseTestIdentifier(identifier);
+        TestResult testResult = getCurrentTestResult();
+        TestIdentifier testId = new TestIdentifier(mFakePackagePrefix + testResult.mTestClass,
+                testResult.mTestName);
+
+        // Error - trying to end a test when one isn't in progress
+        if (!testInProgress()) {
+            Log.e(LOG_TAG, "Test currently not in progress when trying to end test: " + identifier);
+            return;
+        }
+
+        // Save the run time for this test if one exists
+        if (parsedResults.mTestRunTime != null) {
+            try {
+                testResult.mRunTime = new Long(parsedResults.mTestRunTime);
+            }
+            catch (NumberFormatException e) {
+                Log.e(LOG_TAG, "Test run time value is invalid, received: " +
+                        parsedResults.mTestRunTime);
+            }
+        }
+
+        // Check that the test result is for the same test/class we're expecting it to be for
+        boolean encounteredUnexpectedTest = false;
+        if (!testResult.isComplete()) {
+            Log.e(LOG_TAG, "No test/class name is currently recorded as running!");
+        }
+        else {
+            if (testResult.mTestClass.compareTo(parsedResults.mTestClassName) != 0) {
+                Log.e(LOG_TAG, "Name for current test class does not match class we started " +
+                        "with, expected: " + testResult.mTestClass + " but got: " +
+                        parsedResults.mTestClassName);
+                encounteredUnexpectedTest = true;
+            }
+            if (testResult.mTestName.compareTo(parsedResults.mTestName) != 0) {
+                Log.e(LOG_TAG, "Name for current test does not match test we started with," +
+                        "expected: " + testResult.mTestName + " bug got: " +
+                        parsedResults.mTestName);
+                encounteredUnexpectedTest = true;
+            }
+        }
+
+        if (encounteredUnexpectedTest) {
+            // If the test name of the result changed from what we started with, report that
+            // the last known test failed, regardless of whether we received a pass or fail tag.
+            for (ITestRunListener listener : mTestListeners) {
+                listener.testFailed(ITestRunListener.TestFailure.ERROR, testId,
+                                mCurrentTestResult.getTrace());
+            }
+        }
+        else if (!testPassed) {  // test failed
+            for (ITestRunListener listener : mTestListeners) {
+                listener.testFailed(ITestRunListener.TestFailure.FAILURE, testId,
+                                mCurrentTestResult.getTrace());
+            }
+        }
+        // For all cases (pass or fail), we ultimately need to report test has ended
+        Map <String, String> emptyMap = Collections.emptyMap();
+        for (ITestRunListener listener : mTestListeners) {
+            // @TODO: Add reporting of test run time to ITestRunListener
+            listener.testEnded(testId, emptyMap);
+        }
+
+        setTestEnded();
+        ++mNumTestsRun;
+    }
+
+    /**
+     * Processes and informs listener when we encounter the OK tag.
+     *
+     * @param identifier Raw log output of the form "classname.testname" with an optional (XX ms)
+     *          at the end indicating the running time.
+     */
+    private void processOKTag(String identifier) {
+        doTestEnded(identifier, true);
+    }
+
+    /**
+     * Processes and informs listener when we encounter the FAILED tag.
+     *
+     * @param identifier Raw log output of the form "classname.testname" with an optional (XX ms)
+     *          at the end indicating the running time.
+     */
+    private void processFailedTag(String identifier) {
+        doTestEnded(identifier, false);
+    }
+
+    /**
+     * Appends the test output to the current TestResult.
+     *
+     * @param line Raw test result line of output.
+     */
+    private void appendTestOutputLine(String line) {
+        TestResult testResult = getCurrentTestResult();
+        if (testResult.mStackTrace == null) {
+            testResult.mStackTrace = new StringBuilder();
+        }
+        else {
+            testResult.mStackTrace.append("\r\n");
+        }
+        testResult.mStackTrace.append(line);
+    }
+
+    /**
+     * Process an instrumentation run failure
+     *
+     * @param errorMsg The message to output about the nature of the error
+     */
+    private void handleTestRunFailed(String errorMsg) {
+        errorMsg = (errorMsg == null ? "Unknown error" : errorMsg);
+        Log.i(LOG_TAG, String.format("Test run failed: %s", errorMsg));
+        String testRunStackTrace = "";
+
+        // Report that the last known test failed
+        if ((mCurrentTestResult != null) && (mCurrentTestResult.isComplete())) {
+            // current test results are cleared out after every complete test run,
+            // if it's not null, assume the last test caused this and report as a test failure
+            TestIdentifier testId = new TestIdentifier(mCurrentTestResult.mTestClass,
+                    mCurrentTestResult.mTestName);
+
+            // If there was any stack trace during the test run, append it to the "test failed"
+            // error message so we have an idea of what caused the crash/failure.
+            Map<String, String> emptyMap = Collections.emptyMap();
+            if (mCurrentTestResult.hasStackTrace()) {
+                testRunStackTrace = mCurrentTestResult.getTrace();
+            }
+            for (ITestRunListener listener : mTestListeners) {
+                listener.testFailed(ITestRunListener.TestFailure.ERROR, testId,
+                        "No test results.\r\n" + testRunStackTrace);
+                listener.testEnded(testId, emptyMap);
+            }
+            clearCurrentTestResult();
+        }
+        // Report the test run failed
+        for (ITestRunListener listener : mTestListeners) {
+            listener.testRunFailed(errorMsg);
+            listener.testRunEnded(mTotalRunTime, getRunMetrics());
+        }
+    }
+
+    /**
+     * Called by parent when adb session is complete.
+     */
+    @Override
+    public void done() {
+        super.done();
+        if (mNumTestsExpected > mNumTestsRun) {
+            handleTestRunFailed(String.format("Test run incomplete. Expected %d tests, received %d",
+                    mNumTestsExpected, mNumTestsRun));
+        }
+        else if (mTestRunInProgress) {
+            handleTestRunFailed("No test results");
+        }
+    }
+
+    /**
+     * Sets the coverage target for this test.
+     * <p/>
+     * Will be sent as a metric to test listeners.
+     *
+     * @param coverageTarget the coverage target
+     */
+    public void setCoverageTarget(String coverageTarget) {
+        mCoverageTarget = coverageTarget;
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
index 8844667..8cccab0 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
@@ -50,6 +50,7 @@
     private String mRunner = null;
     private boolean mIsHostSideTest = false;
     private boolean mIsVMHostTest = false;
+    private String mTestType = null;
     private String mJarPath = null;
     private boolean mIsSignatureTest = false;
     private boolean mIsReferenceAppTest = false;
@@ -78,6 +79,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public String getUri() {
         return mUri;
     }
@@ -128,6 +130,10 @@
         return mIsVMHostTest;
     }
 
+    void setTestType(String testType) {
+        mTestType = testType;
+    }
+
     void setJarPath(String jarPath) {
         mJarPath = jarPath;
     }
@@ -229,6 +235,8 @@
             vmHostTest.setTests(mTests);
             mDigest = generateDigest(testCaseDir, mJarPath);
             return vmHostTest;
+        } else if ("native".equals(mTestType)) {
+            return new GeeTest(mUri, mName);
         } else if (mIsSignatureTest) {
             // TODO: hardcode the runner/class/method for now, since current package xml points to
             // specialized instrumentation. Eventually this special case for signatureTest can be
@@ -302,6 +310,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public boolean isKnownTest(TestIdentifier testDef) {
         return mTests.contains(testDef);
     }
@@ -309,6 +318,7 @@
     /**
      * {@inheritDoc}
      */
+    @Override
     public boolean isKnownTestClass(String className) {
         return mTestClasses.contains(className);
     }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageXmlParser.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageXmlParser.java
index 64706a2..f1b6ed0 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageXmlParser.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageXmlParser.java
@@ -68,6 +68,7 @@
                 final String runnerName = attributes.getValue("runner");
                 final String hostSideTest = attributes.getValue("hostSideOnly");
                 final String vmHostTest = attributes.getValue("vmHostTest");
+                final String testType = attributes.getValue("testType");
                 final String jarPath = attributes.getValue("jarPath");
                 final String signatureCheck = attributes.getValue("signatureCheck");
                 final String referenceApp = attributes.getValue("referenceAppTest");
@@ -84,6 +85,7 @@
                 mPackageDef.setRunner(runnerName);
                 mPackageDef.setIsHostSideTest(parseBoolean(hostSideTest));
                 mPackageDef.setIsVMHostTest(parseBoolean(vmHostTest));
+                mPackageDef.setTestType(testType);
                 mPackageDef.setJarPath(jarPath);
                 mPackageDef.setIsSignatureCheck(parseBoolean(signatureCheck));
                 mPackageDef.setIsReferenceApp(parseBoolean(referenceApp));