Support running a CTS test plan of instrumentation tests.

Change-Id: I30473d74c9d1030b22d3aad66fa1ee317681ff97
diff --git a/tools/tradefed-host/.classpath b/tools/tradefed-host/.classpath
index 67d42b5..b96e0da 100644
--- a/tools/tradefed-host/.classpath
+++ b/tools/tradefed-host/.classpath
@@ -1,8 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
 	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="res"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/tradefed-prebuilt"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/ddmlib-prebuilt"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/tradefederation"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/tools/tradefed-host/Android.mk b/tools/tradefed-host/Android.mk
index a05ed40..f2a740d 100644
--- a/tools/tradefed-host/Android.mk
+++ b/tools/tradefed-host/Android.mk
@@ -18,6 +18,7 @@
 
 # Only compile source java files in this lib.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_JAVA_RESOURCE_DIRS := res
 
 LOCAL_MODULE := cts-tradefed
 LOCAL_MODULE_TAGS := optional
diff --git a/tools/tradefed-host/res/config/cts.xml b/tools/tradefed-host/res/config/cts.xml
new file mode 100644
index 0000000..2ada89e
--- /dev/null
+++ b/tools/tradefed-host/res/config/cts.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.
+-->
+<configuration
+    description="Runs a CTS plan from a pre-existing CTS installation">
+
+    <build_provider class="com.android.cts.tradefed.targetsetup.CtsBuildProvider" />
+    <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
+    <target_preparer class="com.android.cts.tradefed.targetsetup.CtsSetup" />
+    <test class="com.android.cts.tradefed.testtype.PlanTest" />
+    <logger class="com.android.tradefed.log.FileLogger" />
+    <!--  TODO: change this to the CTS XML result reporter when available -->
+    <result_reporter class="com.android.tradefed.result.XmlResultReporter" />
+
+</configuration>
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsBuildHelper.java b/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsBuildHelper.java
index d8aabc5..9597109 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsBuildHelper.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsBuildHelper.java
@@ -67,7 +67,7 @@
      * @throws FileNotFoundException if file does not exist
      */
     public File getTestApp(String appFileName) throws FileNotFoundException {
-        File apkFile = new File(new File(getRepositoryDir(), "testcases"), appFileName);
+        File apkFile = new File(getTestCasesDir(), appFileName);
         if (!apkFile.exists()) {
             throw new FileNotFoundException(String.format("CTS test app file %s does not exist",
                     apkFile.getAbsolutePath()));
@@ -80,22 +80,35 @@
     }
 
     /**
-     * @return a {@link File} representing the test plan file with given name
-     * @throws FileNotFoundException if file does not exist
-     */
-    public File getTestPlan(String testPlanFileName) throws FileNotFoundException {
-        File planFile = new File(new File(getRepositoryDir(), "plans"), testPlanFileName);
-        if (!planFile.exists()) {
-            throw new FileNotFoundException(String.format("CTS test plan file %s does not exist",
-                    planFile.getAbsolutePath()));
-        }
-        return planFile;
-    }
-
-    /**
      * @return a {@link File} representing the results directory.
      */
     public File getResultsDir() {
         return new File(getRepositoryDir(), "results");
     }
+
+    /**
+     * @return a {@link File} representing the test cases directory
+     * @throws FileNotFoundException if dir does not exist
+     */
+    public File getTestCasesDir() throws FileNotFoundException {
+        File dir = new File(getRepositoryDir(), "testcases");
+        if (!dir.exists()) {
+            throw new FileNotFoundException(String.format(
+                    "CTS test cases directory %s does not exist", dir.getAbsolutePath()));
+        }
+        return dir;
+    }
+
+    /**
+     * @return a {@link File} representing the test plan directory
+     * @throws FileNotFoundException if dir does not exist
+     */
+    public File getTestPlansDir() throws FileNotFoundException {
+        File dir = new File(getRepositoryDir(), "plans");
+        if (!dir.exists()) {
+            throw new FileNotFoundException(String.format(
+                    "CTS test plans directory %s does not exist", dir.getAbsolutePath()));
+        }
+        return dir;
+    }
 }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsDeviceSetup.java b/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsDeviceSetup.java
index 65f09f3..121c02a 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsDeviceSetup.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsDeviceSetup.java
@@ -16,6 +16,8 @@
 package com.android.cts.tradefed.targetsetup;
 
 import com.android.ddmlib.Log;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationReceiver;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.targetsetup.BuildError;
@@ -35,7 +37,7 @@
  * This class is NOT intended for 'official' CTS runs against a production device as the steps
  * performed by this class require a debug build (aka 'adb root' must succeed).
  */
-public class CtsDeviceSetup extends DeviceSetup {
+public class CtsDeviceSetup extends DeviceSetup implements IConfigurationReceiver {
 
     private static final String LOG_TAG = "CtsDeviceSetup";
 
@@ -43,6 +45,15 @@
     private static final String ACCESSIBILITY_SERVICE_APK_FILE_NAME =
         "CtsDelegatingAccessibilityService.apk";
 
+    private IConfiguration mConfiguration = null;
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setConfiguration(IConfiguration configuration) {
+        mConfiguration  = configuration;
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -65,6 +76,7 @@
 
             // TODO: turn on mock locations
             CtsSetup ctsSetup = new CtsSetup();
+            ctsSetup.setConfiguration(mConfiguration);
             enableAccessibilityService(device, buildHelper, ctsSetup);
 
             // end root setup steps
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsSetup.java b/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsSetup.java
index 7af703b..1ac2ab9 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsSetup.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/targetsetup/CtsSetup.java
@@ -15,6 +15,10 @@
  */
 package com.android.cts.tradefed.targetsetup;
 
+import com.android.cts.tradefed.testtype.PlanTest;
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IConfigurationReceiver;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.targetsetup.BuildError;
@@ -31,12 +35,14 @@
  * <p/>
  * All the actions performed in this class must work on a production device.
  */
-public class CtsSetup implements ITargetPreparer {
+public class CtsSetup implements ITargetPreparer, IConfigurationReceiver {
 
     private static final String RUNNER_APK_NAME = "android.core.tests.runner.apk";
     // TODO: read this from configuration file rather than hardcoding
     private static final String TEST_STUBS_APK = "CtsTestStubs.apk";
 
+    private IConfiguration mConfiguration = null;
+
     /**
      * Factory method to create a {@link CtsBuildHelper}.
      * <p/>
@@ -49,18 +55,35 @@
     /**
      * {@inheritDoc}
      */
+    public void setConfiguration(IConfiguration configuration) {
+        mConfiguration = configuration;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
             BuildError, DeviceNotAvailableException {
         if (!(buildInfo instanceof IFolderBuildInfo)) {
             throw new IllegalArgumentException("Provided buildInfo is not a IFolderBuildInfo");
         }
+        if (mConfiguration == null) {
+            throw new IllegalStateException("setConfiguration() was not called before setUp");
+        }
         IFolderBuildInfo ctsBuildInfo = (IFolderBuildInfo)buildInfo;
         try {
             CtsBuildHelper buildHelper = createBuildHelper(ctsBuildInfo.getRootDir());
+            // pass necessary build information to the other config objects
+            mConfiguration.injectOptionValue(PlanTest.TEST_CASES_DIR_OPTION,
+                    buildHelper.getTestCasesDir().getAbsolutePath());
+            mConfiguration.injectOptionValue(PlanTest.TEST_PLANS_DIR_OPTION,
+                    buildHelper.getTestPlansDir().getAbsolutePath());
             installCtsPrereqs(device, buildHelper);
             gatherDeviceStats(device, buildHelper);
         } catch (FileNotFoundException e) {
             throw new TargetSetupError("Invalid CTS installation", e);
+        } catch (ConfigurationException e) {
+            throw new TargetSetupError("Failed to set repository directory options", e);
         }
     }
 
@@ -98,7 +121,7 @@
 
     private void gatherDeviceStats(ITestDevice device, CtsBuildHelper ctsBuild) {
         // TODO: implement this
-        // install TestDeviceSetup.apk, run its instrumentation, parse results, store them in the
-        // CtsBuildInfo, then uninstall TestDeviceSetup.apk
+        // install DeviceInfoCollector.apk, run its instrumentation, parse results, store them in
+        // the ctsBuild, then uninstall TestDeviceSetup.apk
     }
 }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/IPlanXmlParser.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/IPlanXmlParser.java
new file mode 100644
index 0000000..f7d4ab1
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/IPlanXmlParser.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 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.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import java.io.InputStream;
+import java.util.Collection;
+
+/**
+ * Interface for accessing test plan data.
+ */
+interface IPlanXmlParser {
+
+    /**
+     * Parse the test plan data from given stream.
+     *
+     * @param xmlStream the {@link InputStream} that contains the test plan xml.
+     */
+    public void parse(InputStream xmlStream) throws ParseException;
+
+    /**
+     * Gets the list of test uris parsed from the plan.
+     * <p/>
+     * Must be called after {@link IPlanXmlParser#parse(InputStream)}.
+     */
+    public Collection<String> getTestUris();
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestCaseRepo.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestCaseRepo.java
new file mode 100644
index 0000000..18ea7cf
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestCaseRepo.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2010 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.tradefed.testtype.IRemoteTest;
+
+import java.util.Collection;
+
+/**
+ * Interface for accessing tests from the CTS repository.
+ */
+interface ITestCaseRepo {
+
+    /**
+     * Gets a list of tests identified by given list of uris
+     *
+     * @param testUris the string uris
+     * @return a {@link Collection} of {@link IRemoteTest}
+     */
+    public Collection<IRemoteTest> getTests(Collection<String> testUris);
+
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PlanTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PlanTest.java
new file mode 100644
index 0000000..65932b8
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PlanTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2010 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.Log;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.AbstractRemoteTest;
+import com.android.tradefed.testtype.IDeviceTest;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.List;
+
+import junit.framework.Test;
+
+/**
+ * A {@link Test} that runs all the tests in the CTS test plan with given name
+ */
+public class PlanTest extends AbstractRemoteTest implements IDeviceTest, IRemoteTest {
+
+    private static final String LOG_TAG = "PlanTest";
+
+    public static final String TEST_CASES_DIR_OPTION = "test-cases-path";
+    public static final String TEST_PLANS_DIR_OPTION = "test-plans-path";
+
+    private ITestDevice mDevice;
+
+    @Option(name = "plan", description = "the test plan to run")
+    private String mPlanName = null;
+
+    @Option(name = TEST_CASES_DIR_OPTION, description =
+        "file path to directory containing CTS test cases")
+    private File mTestCaseDir = null;
+
+    @Option(name = TEST_PLANS_DIR_OPTION, description =
+        "file path to directory containing CTS test plans")
+    private File mTestPlanDir = null;
+
+    /**
+     * {@inheritDoc}
+     */
+    public ITestDevice getDevice() {
+        return mDevice;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setDevice(ITestDevice device) {
+        mDevice = device;
+    }
+
+    /**
+     * Set the test plan directory.
+     * <p/>
+     * Exposed for unit testing
+     */
+    void setTestPlanDir(File planDir) {
+        mTestPlanDir = planDir;
+    }
+
+    /**
+     * Set the test case directory.
+     * <p/>
+     * Exposed for unit testing
+     */
+    void setTestCaseDir(File testCaseDir) {
+        mTestCaseDir = testCaseDir;
+    }
+
+    /**
+     * Set the plan name to run.
+     * <p/>
+     * Exposed for unit testing
+     */
+    void setPlanName(String planName) {
+        mPlanName = planName;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void run(List<ITestInvocationListener> listeners) throws DeviceNotAvailableException {
+        if (mPlanName == null) {
+            throw new IllegalArgumentException("missing --plan option");
+        }
+        if (getDevice() == null) {
+            throw new IllegalArgumentException("missing device");
+        }
+        if (mTestCaseDir == null) {
+            throw new IllegalArgumentException(String.format("missing %s option",
+                    TEST_CASES_DIR_OPTION));
+        }
+        if (mTestPlanDir == null) {
+            throw new IllegalArgumentException(String.format("missing %s", TEST_PLANS_DIR_OPTION));
+        }
+
+        Log.i(LOG_TAG, String.format("Executing CTS test plan %s", mPlanName));
+
+        try {
+            String ctsPlanRelativePath = String.format("%s.xml", mPlanName);
+            File ctsPlanFile = new File(mTestPlanDir, ctsPlanRelativePath);
+            IPlanXmlParser parser = createXmlParser();
+            parser.parse(createXmlStream(ctsPlanFile));
+            Collection<String> testUris = parser.getTestUris();
+            ITestCaseRepo testRepo = createTestCaseRepo();
+            Collection<IRemoteTest> tests = testRepo.getTests(testUris);
+            for (IRemoteTest test : tests) {
+                if (test instanceof IDeviceTest) {
+                    ((IDeviceTest)test).setDevice(getDevice());
+                }
+                test.run(listeners);
+            }
+        } catch (FileNotFoundException e) {
+            throw new IllegalArgumentException("failed to find CTS plan file", e);
+        } catch (ParseException e) {
+            throw new IllegalArgumentException("failed to parse CTS plan file", e);
+        }
+    }
+
+    /**
+     * Factory method for creating a {@link ITestCaseRepo}.
+     * <p/>
+     * Exposed for unit testing
+     */
+    ITestCaseRepo createTestCaseRepo() {
+        return new TestCaseRepo(mTestCaseDir);
+    }
+
+    /**
+     * Factory method for creating a {@link PlanXmlParser}.
+     * <p/>
+     * Exposed for unit testing
+     */
+    IPlanXmlParser createXmlParser() {
+        return new PlanXmlParser();
+    }
+
+    /**
+     * Factory method for creating a {@link InputStream} from a plan xml file.
+     * <p/>
+     * Exposed for unit testing
+     */
+    InputStream createXmlStream(File xmlFile) throws FileNotFoundException {
+        return new BufferedInputStream(new FileInputStream(xmlFile));
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PlanXmlParser.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PlanXmlParser.java
new file mode 100644
index 0000000..fd3dcb8
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PlanXmlParser.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 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.tradefed.util.xml.AbstractXmlParser;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Parses a test plan xml file.
+ */
+class PlanXmlParser extends AbstractXmlParser implements IPlanXmlParser {
+
+    private Set<String> mUris;
+
+    /**
+     * SAX callback object. Handles parsing data from the xml tags.
+     */
+    private class EntryHandler extends DefaultHandler {
+
+        private static final String ENTRY_TAG = "Entry";
+
+        @Override
+        public void startElement(String uri, String localName, String name, Attributes attributes)
+                throws SAXException {
+            if (ENTRY_TAG.equals(localName)) {
+                final String entryUriValue = attributes.getValue("uri");
+                mUris.add(entryUriValue);
+            }
+        }
+    }
+
+    PlanXmlParser() {
+        // Uses a LinkedHashSet to have predictable iteration order
+        mUris = new LinkedHashSet<String>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection<String> getTestUris() {
+        return mUris;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected DefaultHandler createXmlHandler() {
+        return new EntryHandler();
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestCaseRepo.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestCaseRepo.java
new file mode 100644
index 0000000..a171dc9
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestCaseRepo.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2010 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.Log;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.Map;
+
+/**
+ * Retrieves CTS test case definitions from the repository.
+ */
+class TestCaseRepo implements ITestCaseRepo {
+
+    private static final String LOG_TAG = "TestCaseRepo";
+
+    private File mTestCaseDir;
+
+    /** mapping of uri to test definition */
+    private Map<String, TestPackageDef> mTestMap;
+
+    /**
+     * Creates a {@link TestCaseRepo}, initialized from provided repo files
+     *
+     * @param testCaseDir directory containing all test case definition xml and build files
+     */
+    public TestCaseRepo(File testCaseDir) {
+        mTestCaseDir = testCaseDir;
+        mTestMap = new Hashtable<String, TestPackageDef>();
+        parse(mTestCaseDir);
+    }
+
+    /**
+     * Builds mTestMap based on directory contents
+     */
+    private void parse(File dir) {
+        File[] xmlFiles = dir.listFiles(new XmlFilter());
+        for (File xmlFile : xmlFiles) {
+            parseTestFromXml(xmlFile);
+        }
+    }
+
+    /**
+     * @param xmlFile
+     * @throws ParseException
+     */
+    private void parseTestFromXml(File xmlFile)  {
+        TestCaseXmlParser parser = new TestCaseXmlParser();
+        try {
+            parser.parse(createStreamFromFile(xmlFile));
+            TestPackageDef def = parser.getTestPackageDef();
+            if (def != null) {
+                mTestMap.put(def.getUri(), def);
+            } else {
+                Log.w(LOG_TAG, String.format("Could not find test package info in xml file %s",
+                        xmlFile.getAbsolutePath()));
+            }
+        } catch (FileNotFoundException e) {
+            Log.e(LOG_TAG, String.format("Could not find test case xml file %s",
+                    xmlFile.getAbsolutePath()));
+            Log.e(LOG_TAG, e);
+        } catch (ParseException e) {
+            Log.e(LOG_TAG, String.format("Failed to parse test case xml file %s",
+                    xmlFile.getAbsolutePath()));
+            Log.e(LOG_TAG, e);
+        }
+    }
+
+    /**
+     * Helper method to create a stream to read data from given file
+     * <p/>
+     * Exposed for unit testing
+     *
+     * @param xmlFile
+     * @return
+     *
+     */
+    InputStream createStreamFromFile(File xmlFile) throws FileNotFoundException {
+        return new BufferedInputStream(new FileInputStream(xmlFile));
+    }
+
+    private static class XmlFilter implements FilenameFilter {
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean accept(File dir, String name) {
+            return name.endsWith(".xml");
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection<IRemoteTest> getTests(Collection<String> testUris) {
+        Collection<IRemoteTest> tests = new ArrayList<IRemoteTest>(testUris.size());
+        for (String uri : testUris) {
+            TestPackageDef def = mTestMap.get(uri);
+            if (def != null) {
+                IRemoteTest test = def.createTest(mTestCaseDir);
+                if (test != null) {
+                    tests.add(test);
+                } else {
+                    Log.w(LOG_TAG, String.format("Failed to create test from package uri %s", uri));
+                }
+            } else {
+                Log.w(LOG_TAG, String.format("Could not find test with uri %s", uri));
+            }
+        }
+        return tests;
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestCaseXmlParser.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestCaseXmlParser.java
new file mode 100644
index 0000000..636d8c1
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestCaseXmlParser.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2010 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.tradefed.util.xml.AbstractXmlParser;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Parser for CTS test case XML.
+ * <p/>
+ * Dumb parser that just retrieves data from in the test case xml and stuff it into a
+ * {@link TestPackageDef}. Currently performs limited error checking.
+ */
+public class TestCaseXmlParser extends AbstractXmlParser {
+
+    private TestPackageDef mDef;
+
+    /**
+     * SAX callback object. Handles parsing 'TestPackage' data from the xml tags.
+     */
+    private class TestPackageHandler extends DefaultHandler {
+
+        private static final String TEST_PACKAGE_TAG = "TestPackage";
+
+        @Override
+        public void startElement(String uri, String localName, String name, Attributes attributes)
+                throws SAXException {
+            if (TEST_PACKAGE_TAG.equals(localName)) {
+                // appPackageName is used as the uri
+                final String entryUriValue = attributes.getValue("appPackageName");
+                final String testPackageNameSpace = attributes.getValue("appNameSpace");
+                final String packageName = attributes.getValue("name");
+                final String runnerName = attributes.getValue("runner");
+                final String hostSideTest = attributes.getValue("hostSideOnly");
+                final String jarPath = attributes.getValue("jarPath");
+                final String signatureCheck = attributes.getValue("signatureCheck");
+                final String referenceApp = attributes.getValue("referenceAppTest");
+
+                mDef = new TestPackageDef();
+                mDef.setUri(entryUriValue);
+                mDef.setAppNameSpace(testPackageNameSpace);
+                mDef.setName(packageName);
+                mDef.setRunner(runnerName);
+                mDef.setIsHostSideTest(parseBoolean(hostSideTest));
+                mDef.setJarPath(jarPath);
+                mDef.setIsSignatureCheck(parseBoolean(signatureCheck));
+                mDef.setIsReferenceApp(parseBoolean(referenceApp));
+            }
+        }
+
+        /**
+         * Parse a boolean attribute value
+]         */
+        private boolean parseBoolean(final String stringValue) {
+            return stringValue != null &&
+                    Boolean.parseBoolean(stringValue);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected DefaultHandler createXmlHandler() {
+        return new TestPackageHandler();
+    }
+
+    /**
+     * @returns the {@link TestPackageDef} containing data parsed from xml or <code>null</code> if
+     *          xml did not contain the correct information.
+     */
+    public TestPackageDef getTestPackageDef() {
+        return mDef;
+    }
+}
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
new file mode 100644
index 0000000..879a5f9
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2010 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.Log;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.InstrumentationTest;
+
+import java.io.File;
+
+/**
+ * Container for CTS test info.
+ * <p/>
+ * Knows how to translate this info into a runnable {@link IRemoteTest}.
+ */
+public class TestPackageDef {
+
+    private static final String LOG_TAG = "TestPackageDef";
+
+    private String mUri = null;
+    private String mAppNameSpace = null;
+    private String mName = null;
+    private String mRunner = null;
+    private boolean mIsHostSideTest = false;
+    private String mJarPath = null;
+    private boolean mIsSignatureTest = false;
+    private boolean mIsReferenceAppTest = false;
+
+    void setUri(String uri) {
+        mUri = uri;
+    }
+
+    /**
+     * Get the unique URI of the test package.
+     * @return the {@link String} uri
+     */
+    public String getUri() {
+        return mUri;
+    }
+
+    void setAppNameSpace(String appNameSpace) {
+        mAppNameSpace = appNameSpace;
+    }
+
+    String getAppNameSpace() {
+        return mAppNameSpace;
+    }
+
+    void setName(String name) {
+        mName = name;
+    }
+
+    String getName() {
+        return mName;
+    }
+
+    void setRunner(String runnerName) {
+        mRunner = runnerName;
+    }
+
+    String getRunner() {
+        return mRunner;
+    }
+
+    void setIsHostSideTest(boolean hostSideTest) {
+        mIsHostSideTest = hostSideTest;
+
+    }
+
+    boolean isHostSideTest() {
+        return mIsHostSideTest;
+    }
+
+    void setJarPath(String jarPath) {
+        mJarPath = jarPath;
+    }
+
+    String getJarPath() {
+        return mJarPath;
+    }
+
+    void setIsSignatureCheck(boolean isSignatureCheckTest) {
+        mIsSignatureTest = isSignatureCheckTest;
+    }
+
+    boolean isSignatureCheck() {
+        return mIsSignatureTest;
+    }
+
+    void setIsReferenceApp(boolean isReferenceApp) {
+        mIsReferenceAppTest = isReferenceApp;
+    }
+
+    boolean isReferenceApp() {
+        return mIsReferenceAppTest;
+    }
+
+    /**
+     * Creates a runnable {@link IRemoteTest} from info stored in this definition.
+     *
+     * @param testCaseDir {@link File} representing directory of test case data
+     * @return a {@link IRemoteTest} with all necessary data populated to run the test or
+     *         <code>null</code> if test could not be created
+     */
+    public IRemoteTest createTest(File testCaseDir) {
+        if (mIsHostSideTest) {
+            // TODO: implement this
+            Log.w(LOG_TAG, String.format("Skipping currently unsupported host side test %s",
+                    mName));
+            return null;
+        } else if (mIsSignatureTest) {
+            // TODO: implement this
+            Log.w(LOG_TAG, String.format("Skipping currently unsupported signature test %s",
+                    mName));
+            return null;
+        } else if (mIsReferenceAppTest) {
+            // TODO: implement this
+            Log.w(LOG_TAG, String.format("Skipping currently unsupported reference app test %s",
+                    mName));
+            return null;
+        } else {
+            Log.d(LOG_TAG, String.format("Creating instrumentation test for %s", mName));
+            InstrumentationTest instrTest = new InstrumentationTest();
+            instrTest.setPackageName(mAppNameSpace);
+            instrTest.setRunnerName(mRunner);
+            // mName means 'apk file name' for instrumentation tests
+            File apkFile = new File(testCaseDir, String.format("%s.apk", mName));
+            if (!apkFile.exists()) {
+                Log.w(LOG_TAG, String.format("Could not find apk file %s",
+                        apkFile.getAbsolutePath()));
+                return null;
+            }
+            instrTest.setInstallFile(apkFile);
+            return instrTest;
+        }
+    }
+}
diff --git a/tools/tradefed-host/tests/.classpath b/tools/tradefed-host/tests/.classpath
index d14e02b..1fefb7b 100644
--- a/tools/tradefed-host/tests/.classpath
+++ b/tools/tradefed-host/tests/.classpath
@@ -5,7 +5,7 @@
 	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/cts-tradefed-host"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/easymock"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/ddmlib-prebuilt"/>
-	<classpathentry combineaccessrules="false" kind="src" path="/tradefed-prebuilt"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/tradefederation"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/targetsetup/StubCtsBuildHelper.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/targetsetup/StubCtsBuildHelper.java
index f17bd5a..53a79be 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/targetsetup/StubCtsBuildHelper.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/targetsetup/StubCtsBuildHelper.java
@@ -43,7 +43,12 @@
     }
 
     @Override
-    public File getTestPlan(String testPlanFileName) throws FileNotFoundException {
+    public File getTestPlansDir() throws FileNotFoundException {
+        return new File("tmp");
+    }
+
+    @Override
+    public File getTestCasesDir() {
         return new File("tmp");
     }
 }
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/PlanTestTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/PlanTestTest.java
new file mode 100644
index 0000000..1c2b4b6
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/PlanTestTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2010 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.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import org.easymock.EasyMock;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link PlanTest}.
+ */
+public class PlanTestTest extends TestCase {
+
+    /** the test fixture under test, with all external dependencies mocked out */
+    private PlanTest mPlanTest;
+    private ITestCaseRepo mMockRepo;
+    private IPlanXmlParser mMockPlanParser;
+    private ITestDevice mMockDevice;
+    private ITestInvocationListener mMockListener;
+
+    private static final String PLAN_NAME = "CTS";
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mMockRepo = EasyMock.createMock(ITestCaseRepo.class);
+        mMockPlanParser = EasyMock.createMock(IPlanXmlParser.class);
+        mMockDevice = EasyMock.createMock(ITestDevice.class);
+        mMockListener = EasyMock.createNiceMock(ITestInvocationListener.class);
+
+        mPlanTest = new PlanTest() {
+            @Override
+            ITestCaseRepo createTestCaseRepo() {
+                return mMockRepo;
+            }
+
+            @Override
+            IPlanXmlParser createXmlParser() {
+                return mMockPlanParser;
+            }
+
+            @Override
+            InputStream createXmlStream(File xmlFile) throws FileNotFoundException {
+                // return empty stream, not used
+                return new ByteArrayInputStream(new byte[0]);
+            }
+        };
+        mPlanTest.setDevice(mMockDevice);
+        // not used, but needs to be non-null
+        mPlanTest.setTestCaseDir(new File("tmp"));
+        mPlanTest.setTestPlanDir(new File("tmp"));
+        mPlanTest.setPlanName(PLAN_NAME);
+    }
+
+    /**
+     * Test normal case {@link PlanTest#run(java.util.List)}.
+     * <p/>
+     * Not that interesting of a test in its current form, but sets the stage for testing more
+     * complicated scenarios.
+     */
+    @SuppressWarnings("unchecked")
+    public void testRun() throws DeviceNotAvailableException, ParseException {
+        // expect
+        mMockPlanParser.parse((InputStream)EasyMock.anyObject());
+        Collection<String> uris = new ArrayList<String>(1);
+        uris.add("test-uri");
+        EasyMock.expect(mMockPlanParser.getTestUris()).andReturn(uris);
+
+        IRemoteTest mockTest = EasyMock.createMock(IRemoteTest.class);
+        Collection<IRemoteTest> tests = new ArrayList<IRemoteTest>(1);
+        tests.add(mockTest);
+        EasyMock.expect(mMockRepo.getTests(uris)).andReturn(tests);
+
+        // expect
+        mockTest.run((List<ITestInvocationListener>)EasyMock.anyObject());
+
+        replayMocks();
+        EasyMock.replay(mockTest);
+        mPlanTest.run(mMockListener);
+        verifyMocks();
+    }
+
+    private void replayMocks() {
+        EasyMock.replay(mMockRepo, mMockPlanParser, mMockDevice, mMockListener);
+    }
+
+    private void verifyMocks() {
+        EasyMock.verify(mMockRepo, mMockPlanParser, mMockDevice, mMockListener);
+    }
+}
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/PlanXmlParserTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/PlanXmlParserTest.java
new file mode 100644
index 0000000..803b230
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/PlanXmlParserTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 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.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Iterator;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link PlanXmlParser}.
+ */
+public class PlanXmlParserTest extends TestCase {
+
+    private static final String TEST_URI1 = "foo";
+    private static final String TEST_URI2 = "foo2";
+
+    static final String TEST_DATA =
+        "<TestPlan version=\"1.0\">" +
+            String.format("<Entry uri=\"%s\" />", TEST_URI1) +
+            String.format("<Entry uri=\"%s\" />", TEST_URI2) +
+        "</TestPlan>";
+
+    /**
+     * Simple test for parsing a plan containing two uris
+     */
+    public void testParse() throws ParseException  {
+        PlanXmlParser parser = new PlanXmlParser();
+        parser.parse(getStringAsStream(TEST_DATA));
+        assertEquals(2, parser.getTestUris().size());
+        Iterator<String> iter = parser.getTestUris().iterator();
+        // assert uris in order
+        assertEquals(TEST_URI1, iter.next());
+        assertEquals(TEST_URI2, iter.next());
+    }
+
+    private InputStream getStringAsStream(String input) {
+        return new ByteArrayInputStream(input.getBytes());
+    }
+}
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestCaseXmlParserTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestCaseXmlParserTest.java
new file mode 100644
index 0000000..5b96dbb
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestCaseXmlParserTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2010 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.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link TestCaseXmlParser}.
+ */
+public class TestCaseXmlParserTest extends TestCase {
+
+    private static String INSTR_TEST_DATA =
+        "<TestPackage AndroidFramework=\"Android 1.0\" appNameSpace=\"com.android.cts.gesture\" " +
+        "appPackageName=\"android.gesture\" name=\"CtsGestureTestCases\" " +
+        "runner=\"android.test.InstrumentationTestRunner\" version=\"1.0\">" +
+        "</TestPackage>";
+
+    private static String HOST_TEST_DATA =
+        "<TestPackage hostSideOnly=\"true\" >" +
+        "</TestPackage>";
+
+    private static String BAD_HOST_TEST_DATA =
+        "<TestPackage hostSideOnly=\"blah\" >" +
+        "</TestPackage>";
+
+    private static String NO_TEST_DATA =
+        "<invalid />";
+
+    /**
+     * Test parsing test case xml containing an instrumentation test definition.
+     */
+    public void testParse_instrPackage() throws ParseException  {
+        TestCaseXmlParser parser = new TestCaseXmlParser();
+        parser.parse(getStringAsStream(INSTR_TEST_DATA));
+        TestPackageDef def = parser.getTestPackageDef();
+        assertEquals("com.android.cts.gesture", def.getAppNameSpace());
+        assertEquals("android.gesture", def.getUri());
+        assertEquals("android.test.InstrumentationTestRunner", def.getRunner());
+    }
+
+    /**
+     * Test parsing test case xml containing an host test attribute.
+     */
+    public void testParse_hostTest() throws ParseException  {
+        TestCaseXmlParser parser = new TestCaseXmlParser();
+        parser.parse(getStringAsStream(HOST_TEST_DATA));
+        TestPackageDef def = parser.getTestPackageDef();
+        assertTrue(def.isHostSideTest());
+    }
+
+    /**
+     * Test parsing test case xml containing an invalid host test attribute.
+     */
+    public void testParse_badHostTest() throws ParseException  {
+        TestCaseXmlParser parser = new TestCaseXmlParser();
+        parser.parse(getStringAsStream(BAD_HOST_TEST_DATA));
+        TestPackageDef def = parser.getTestPackageDef();
+        assertFalse(def.isHostSideTest());
+    }
+
+    /**
+     * Test parsing a test case xml with no test package data.
+     */
+    public void testParse_noData() throws ParseException  {
+        TestCaseXmlParser parser = new TestCaseXmlParser();
+        parser.parse(getStringAsStream(NO_TEST_DATA));
+        assertNull(parser.getTestPackageDef());
+    }
+
+    private InputStream getStringAsStream(String input) {
+        return new ByteArrayInputStream(input.getBytes());
+    }
+}