Support Native Test Expectations

Bug 5764723

CTS tests filter out known failures via a text file
containing the "expectations" of tests. This mechanism
was adopted since the libcore tests use the same
system. The recently added native test support didn't
work with the filters though, but now this change adds
that support.

Split the scanning of native tests and XML generation
into separate programs. cts-native-scanner just outputs
test classes and methods it discovers. This is then
piped to cts-xml-generator that takes those tests
and compares them against the test expectations. The
idea is that to support new tests you just need to
add another program that outputs the test classes
and methods. You can then pipe that output to XML
generator and be done.

Having a test expectations is required now, because I
needed to make the XMLs get generated when the test
expectations changed. Before the XMLs were always
regenerated, but now they may not be if nothing has
changed (yay!).

Change-Id: I1feafa3cbcaefdea1b3174ee05026f9c5f58b87e
diff --git a/CtsNativeTestCase.mk b/CtsNativeTestCase.mk
index f70ec0c..cd027e6 100644
--- a/CtsNativeTestCase.mk
+++ b/CtsNativeTestCase.mk
@@ -21,7 +21,11 @@
 
 CTS_NATIVE_XML_OUT := $(HOST_OUT)/cts-native-xml
 
-CTS_NATIVE_XML_GENERATOR := $(HOST_OUT_EXECUTABLES)/cts-native-xml-generator
+CTS_NATIVE_TEST_SCANNER := $(HOST_OUT_EXECUTABLES)/cts-native-scanner
+
+CTS_XML_GENERATOR := $(HOST_OUT_EXECUTABLES)/cts-xml-generator
+
+CTS_EXPECTATIONS := cts/tests/expectations/knownfailures.txt
 
 define cts-get-native-paths
 	$(foreach exe,$(1),$(call intermediates-dir-for,EXECUTABLES,$(exe))/$(exe))
diff --git a/development/ide/eclipse/.classpath b/development/ide/eclipse/.classpath
index 30c63d8..47e0025 100644
--- a/development/ide/eclipse/.classpath
+++ b/development/ide/eclipse/.classpath
@@ -58,8 +58,9 @@
     <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-native-scanner/src"/>
     <classpathentry kind="src" path="cts/tools/cts-reference-app-lib/src"/>
+    <classpathentry kind="src" path="cts/tools/cts-xml-generator/src"/>
     <classpathentry kind="src" path="cts/tools/dasm/src"/>
     <classpathentry kind="src" path="cts/tools/device-setup/TestDeviceSetup/src"/>
     <classpathentry kind="src" path="cts/tools/dex-tools/src"/>
diff --git a/tests/expectations/knownfailures.txt b/tests/expectations/knownfailures.txt
new file mode 100644
index 0000000..0d4f101
--- /dev/null
+++ b/tests/expectations/knownfailures.txt
@@ -0,0 +1,2 @@
+[
+]
diff --git a/tools/build/test_executable.mk b/tools/build/test_executable.mk
index bfd5bee..5ed0e6f 100644
--- a/tools/build/test_executable.mk
+++ b/tools/build/test_executable.mk
@@ -30,9 +30,10 @@
 $(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)
+$(cts_executable_xml): $(addprefix $(LOCAL_PATH)/,$(LOCAL_SRC_FILES)) $(CTS_EXPECTATIONS) | $(CTS_NATIVE_TEST_SCANNER) $(CTS_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 $@
+	$(hide) $(CTS_NATIVE_TEST_SCANNER) -s $(PRIVATE_PATH) | \
+			$(CTS_XML_GENERATOR) -n $(PRIVATE_EXECUTABLE) \
+						-p $(PRIVATE_TEST_PACKAGE) \
+						-e $(CTS_EXPECTATIONS) \
+						-o $@
diff --git a/tools/cts-native-xml-generator/Android.mk b/tools/cts-native-scanner/Android.mk
similarity index 96%
rename from tools/cts-native-xml-generator/Android.mk
rename to tools/cts-native-scanner/Android.mk
index c02d745..f8b1629 100644
--- a/tools/cts-native-xml-generator/Android.mk
+++ b/tools/cts-native-scanner/Android.mk
@@ -23,7 +23,7 @@
 include $(CLEAR_VARS)
 LOCAL_IS_HOST_MODULE := true
 LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_MODULE := cts-native-xml-generator
+LOCAL_MODULE := cts-native-scanner
 LOCAL_MODULE_TAGS := optional
 
 include $(BUILD_SYSTEM)/base_rules.mk
diff --git a/tools/cts-native-xml-generator/etc/cts-native-xml-generator b/tools/cts-native-scanner/etc/cts-native-scanner
similarity index 94%
copy from tools/cts-native-xml-generator/etc/cts-native-xml-generator
copy to tools/cts-native-scanner/etc/cts-native-scanner
index cae5f4a..bf6138f 100644
--- a/tools/cts-native-xml-generator/etc/cts-native-xml-generator
+++ b/tools/cts-native-scanner/etc/cts-native-scanner
@@ -43,4 +43,4 @@
     shift
 done
 
-exec java $javaOpts -jar $libdir/cts-native-xml-generator.jar "$@"
+exec java $javaOpts -jar $libdir/cts-native-scanner.jar "$@"
diff --git a/tools/cts-native-xml-generator/src/Android.mk b/tools/cts-native-scanner/src/Android.mk
similarity index 91%
rename from tools/cts-native-xml-generator/src/Android.mk
rename to tools/cts-native-scanner/src/Android.mk
index eb9b921..9c97b1d 100644
--- a/tools/cts-native-xml-generator/src/Android.mk
+++ b/tools/cts-native-scanner/src/Android.mk
@@ -15,14 +15,14 @@
 LOCAL_PATH := $(call my-dir)
 
 
-# cts-native-xml-generator java library
+# cts-native-scanner java library
 # ============================================================
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 LOCAL_JAR_MANIFEST := MANIFEST.mf
 
-LOCAL_MODULE := cts-native-xml-generator
+LOCAL_MODULE := cts-native-scanner
 LOCAL_MODULE_TAGS := optional
 
 include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/cts-native-scanner/src/MANIFEST.mf b/tools/cts-native-scanner/src/MANIFEST.mf
new file mode 100644
index 0000000..85eb991
--- /dev/null
+++ b/tools/cts-native-scanner/src/MANIFEST.mf
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: com.android.cts.nativescanner.CtsNativeScanner
diff --git a/tools/cts-native-scanner/src/com/android/cts/nativescanner/CtsNativeScanner.java b/tools/cts-native-scanner/src/com/android/cts/nativescanner/CtsNativeScanner.java
new file mode 100644
index 0000000..0eb757e
--- /dev/null
+++ b/tools/cts-native-scanner/src/com/android/cts/nativescanner/CtsNativeScanner.java
@@ -0,0 +1,62 @@
+/*
+ * 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.nativescanner;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Class that searches a source directory for native gTests and outputs a
+ * list of test classes and methods.
+ */
+public class CtsNativeScanner {
+
+    private static void usage(String[] args) {
+        System.err.println("Arguments: " + Arrays.asList(args));
+        System.err.println("Usage: cts-native-scanner -s SOURCE_DIR");
+        System.exit(1);
+    }
+
+    public static void main(String[] args) throws Exception {
+        File sourceDir = null;
+
+        for (int i = 0; i < args.length; i++) {
+            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 (sourceDir == null) {
+            System.out.println("Source directory is required");
+            usage(args);
+        }
+
+        TestScanner scanner = new TestScanner(sourceDir);
+        List<String> testNames = scanner.getTestNames();
+        for (String name : testNames) {
+            System.out.println(name);
+        }
+    }
+}
diff --git a/tools/cts-native-scanner/src/com/android/cts/nativescanner/TestScanner.java b/tools/cts-native-scanner/src/com/android/cts/nativescanner/TestScanner.java
new file mode 100644
index 0000000..8899111
--- /dev/null
+++ b/tools/cts-native-scanner/src/com/android/cts/nativescanner/TestScanner.java
@@ -0,0 +1,109 @@
+/*
+ * 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.nativescanner;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Scanner of C++ gTest source files.
+ *
+ * It looks for test declarations and outputs a file following this format:
+ *
+ * class:TestClass1
+ * method:testMethod1
+ * method:testMethod2
+ * class:TestClass2
+ * method:testMethod1
+ *
+ */
+class TestScanner {
+
+    /** Directory to recursively scan for gTest test declarations. */
+    private final File mSourceDir;
+
+    TestScanner(File sourceDir) {
+        mSourceDir = sourceDir;
+    }
+
+    public List<String> getTestNames() throws IOException {
+        List<String> testNames = new ArrayList<String>();
+        scanDir(mSourceDir, testNames);
+        return testNames;
+    }
+
+    private void scanDir(File dir, List<String> testNames) 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") || filename.endsWith(".cc")
+                        || new File(dir, filename).isDirectory();
+            }
+        });
+
+        for (int i = 0; i < files.length; i++) {
+            File file = files[i];
+            if (file.isDirectory()) {
+                scanDir(file, testNames);
+            } else {
+                scanFile(file, testNames);
+            }
+        }
+    }
+    // We want to find lines like class SLObjectCreationTest : public ::testing::Test { ...
+    // and extract the "SLObjectCreationTest" as group #1
+    private static final Pattern CLASS_REGEX =
+            Pattern.compile("\\s*class\\s+(\\w+).*");
+
+    // We want to find lines like TEST_F(SLObjectCreationTest, testAudioPlayerFromFdCreation) { ...
+    // and extract the "testAudioPlayerFromFdCreation" as group #1
+    private static final Pattern METHOD_REGEX =
+            Pattern.compile("\\s*TEST_F\\(\\w+,\\s*(\\w+)\\).*");
+
+    private void scanFile(File file, List<String> testNames) throws FileNotFoundException {
+        Scanner scanner = null;
+        try {
+            scanner = new Scanner(file);
+            while (scanner.hasNextLine()) {
+                String line = scanner.nextLine();
+                Matcher matcher = CLASS_REGEX.matcher(line);
+                if (matcher.matches()) {
+                    testNames.add("class:" + matcher.group(1));
+                    continue;
+                }
+
+                matcher = METHOD_REGEX.matcher(line);
+                if (matcher.matches()) {
+                    testNames.add("method:" + matcher.group(1));
+                    continue;
+                }
+            }
+        } finally {
+            if (scanner != null) {
+                scanner.close();
+            }
+        }
+    }
+}
diff --git a/tools/cts-native-xml-generator/src/MANIFEST.mf b/tools/cts-native-xml-generator/src/MANIFEST.mf
deleted file mode 100644
index b8bde72..0000000
--- a/tools/cts-native-xml-generator/src/MANIFEST.mf
+++ /dev/null
@@ -1,2 +0,0 @@
-Manifest-Version: 1.0
-Main-Class: com.android.cts.nativexml.CtsNativeXmlGenerator
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
deleted file mode 100644
index 7c074b8..0000000
--- a/tools/cts-native-xml-generator/src/com/android/cts/nativexml/Generator.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * 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();
-                if (!outputDir.exists()) {
-                    System.err.println("Couldn't make output directory: " + outputDir);
-                    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/cts-native-xml-generator/Android.mk b/tools/cts-xml-generator/Android.mk
similarity index 96%
copy from tools/cts-native-xml-generator/Android.mk
copy to tools/cts-xml-generator/Android.mk
index c02d745..5842dd8 100644
--- a/tools/cts-native-xml-generator/Android.mk
+++ b/tools/cts-xml-generator/Android.mk
@@ -23,7 +23,7 @@
 include $(CLEAR_VARS)
 LOCAL_IS_HOST_MODULE := true
 LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_MODULE := cts-native-xml-generator
+LOCAL_MODULE := cts-xml-generator
 LOCAL_MODULE_TAGS := optional
 
 include $(BUILD_SYSTEM)/base_rules.mk
diff --git a/tools/cts-native-xml-generator/etc/cts-native-xml-generator b/tools/cts-xml-generator/etc/cts-xml-generator
similarity index 94%
rename from tools/cts-native-xml-generator/etc/cts-native-xml-generator
rename to tools/cts-xml-generator/etc/cts-xml-generator
index cae5f4a..5f05af6 100644
--- a/tools/cts-native-xml-generator/etc/cts-native-xml-generator
+++ b/tools/cts-xml-generator/etc/cts-xml-generator
@@ -43,4 +43,4 @@
     shift
 done
 
-exec java $javaOpts -jar $libdir/cts-native-xml-generator.jar "$@"
+exec java $javaOpts -jar $libdir/cts-xml-generator.jar "$@"
diff --git a/tools/cts-native-xml-generator/src/Android.mk b/tools/cts-xml-generator/src/Android.mk
similarity index 88%
copy from tools/cts-native-xml-generator/src/Android.mk
copy to tools/cts-xml-generator/src/Android.mk
index eb9b921..62f8692 100644
--- a/tools/cts-native-xml-generator/src/Android.mk
+++ b/tools/cts-xml-generator/src/Android.mk
@@ -15,14 +15,16 @@
 LOCAL_PATH := $(call my-dir)
 
 
-# cts-native-xml-generator java library
+# cts-xml-generator java library
 # ============================================================
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := $(call all-subdir-java-files)
 LOCAL_JAR_MANIFEST := MANIFEST.mf
 
-LOCAL_MODULE := cts-native-xml-generator
+LOCAL_MODULE := cts-xml-generator
 LOCAL_MODULE_TAGS := optional
 
+LOCAL_STATIC_JAVA_LIBRARIES := vogarexpectlib
+
 include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/cts-xml-generator/src/MANIFEST.mf b/tools/cts-xml-generator/src/MANIFEST.mf
new file mode 100644
index 0000000..ce4178e
--- /dev/null
+++ b/tools/cts-xml-generator/src/MANIFEST.mf
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: com.android.cts.xmlgenerator.CtsXmlGenerator
diff --git a/tools/cts-native-xml-generator/src/com/android/cts/nativexml/CtsNativeXmlGenerator.java b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/CtsXmlGenerator.java
similarity index 79%
rename from tools/cts-native-xml-generator/src/com/android/cts/nativexml/CtsNativeXmlGenerator.java
rename to tools/cts-xml-generator/src/com/android/cts/xmlgenerator/CtsXmlGenerator.java
index 1a436dd..5d5d8ff 100644
--- a/tools/cts-native-xml-generator/src/com/android/cts/nativexml/CtsNativeXmlGenerator.java
+++ b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/CtsXmlGenerator.java
@@ -13,29 +13,34 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.nativexml;
+package com.android.cts.xmlgenerator;
+
+import vogar.ExpectationStore;
+import vogar.ModeId;
 
 import java.io.File;
 import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Class that searches a source directory for native gTests and outputs a
  * test package xml.
  */
-public class CtsNativeXmlGenerator {
+public class CtsXmlGenerator {
 
     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]");
+                + " [-e EXPECTATION_FILE] [-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;
+        Set<File> expectationFiles = new HashSet<File>();
 
         for (int i = 0; i < args.length; i++) {
             if ("-p".equals(args[i])) {
@@ -52,6 +57,13 @@
                     System.err.println("Missing value for executable name");
                     usage(args);
                 }
+            } else if ("-e".equals(args[i])) {
+                if (i + 1 < args.length) {
+                    expectationFiles.add(new File(args[++i]));
+                } else {
+                    System.err.println("Missing value for expectation store");
+                    usage(args);
+                }
             } else if ("-o".equals(args[i])) {
                 if (i + 1 < args.length) {
                     outputPath = args[++i];
@@ -59,13 +71,6 @@
                     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);
@@ -78,12 +83,10 @@
         } 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);
+        ExpectationStore store = ExpectationStore.parse(expectationFiles, ModeId.DEVICE);
+        NativeXmlGenerator generator = new NativeXmlGenerator(store, appPackageName, name, outputPath);
         generator.writePackageXml();
     }
 }
diff --git a/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/NativeXmlGenerator.java b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/NativeXmlGenerator.java
new file mode 100644
index 0000000..746b4db
--- /dev/null
+++ b/tools/cts-xml-generator/src/com/android/cts/xmlgenerator/NativeXmlGenerator.java
@@ -0,0 +1,165 @@
+/*
+ * 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.xmlgenerator;
+
+import vogar.Expectation;
+import vogar.ExpectationStore;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Scanner;
+
+/**
+ * Generator of TestPackage XML files for native tests.
+ *
+ * It takes in an input of the following form:
+ *
+ * class:TestClass1
+ * method:testMethod1
+ * method:testMethod2
+ * class:TestClass2
+ * method:testMethod1
+ */
+class NativeXmlGenerator {
+
+    /** Test package name like "android.nativemedia" to group the tests. */
+    private final String mAppPackageName;
+
+    /** Name of the native executable. */
+    private final String mName;
+
+    /** Path to output file or null to just dump to standard out. */
+    private final String mOutputPath;
+
+    /** ExpectationStore to filter out known failures. */
+    private final ExpectationStore mExpectations;
+
+    NativeXmlGenerator(ExpectationStore expectations, String appPackageName, String name,
+            String outputPath) {
+        mAppPackageName = appPackageName;
+        mName = name;
+        mOutputPath = outputPath;
+        mExpectations = expectations;
+    }
+
+    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();
+                if (!outputDir.exists()) {
+                    System.err.println("Couldn't make output directory: " + outputDir);
+                    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) {
+        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) {
+        /*
+         * 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);
+
+            for (; numLevels > 0; numLevels--) {
+                writer.println("</TestSuite>");
+            }
+        } finally {
+            if (scanner != null) {
+                scanner.close();
+            }
+        }
+    }
+
+    private void writeTestCases(PrintWriter writer) {
+        String currentClassName = null;
+        Scanner scanner = new Scanner(System.in);
+        while (scanner.hasNextLine()) {
+            String line = scanner.nextLine();
+            String[] tokens = line.split(":");
+            if (tokens.length > 1) {
+                String type = tokens[0];
+                String value = tokens[1];
+                if ("class".equals(type)) {
+                    if (currentClassName != null) {
+                        writer.append("</TestCase>");
+                    }
+                    currentClassName = value;
+                    writer.append("<TestCase name=\"").append(value).println("\">");
+                } else if ("method".equals(type)) {
+                    String fullClassName = mAppPackageName + "." + currentClassName;
+                    if (!isKnownFailure(mExpectations, fullClassName, value)) {
+                        writer.append("<Test name=\"").append(value).println("\" />");
+                    }
+                }
+            }
+        }
+        if (currentClassName != null) {
+            writer.println("</TestCase>");
+        }
+    }
+
+    public static boolean isKnownFailure(ExpectationStore expectationStore,
+            String className, String methodName) {
+        String testName = String.format("%s#%s", className, methodName);
+        return expectationStore != null && expectationStore.get(testName) != Expectation.SUCCESS;
+    }
+}
+