Sort CTS result lists; add build ID, product type

Bug: 26108323

Change-Id: I7124a7788cf84d35b347c0fb48df0533bdf74b84
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
index 3a222a2..ddbe810 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
@@ -64,6 +64,7 @@
         MODULE_SPLIT_EXCLUSIONS.add("CtsDeqpTestCases");
     }
     private CompatibilityBuildHelper mBuildHelper;
+    private static final int TIMESTAMP_COLUMN = 4;
 
     /**
      * {@inheritDoc}
@@ -304,8 +305,6 @@
     private void listResults() {
         TableFormatter tableFormatter = new TableFormatter();
         List<List<String>> table = new ArrayList<>();
-        table.add(Arrays.asList("Session","Pass", "Fail", "Not Executed", "Start Time", "Test Plan",
-                "Device serial(s)"));
         IInvocationResultRepo testResultRepo = null;
         List<IInvocationResult> results = null;
         try {
@@ -318,14 +317,40 @@
         if (testResultRepo != null && results.size() > 0) {
             for (int i = 0; i < results.size(); i++) {
                 IInvocationResult result = results.get(i);
-                table.add(Arrays.asList(Integer.toString(i),
+                Map<String, String> buildInfo = result.getBuildInfo();
+
+                // build attributes are not always present (e.g. in the case of halted test runs)
+                // replace null entries with the string "Unknown"
+                for (Map.Entry<String, String> entry : buildInfo.entrySet()) {
+                    if (entry.getValue() == null) {
+                        buildInfo.put(entry.getKey(), "Unknown");
+                    }
+                }
+
+                table.add(Arrays.asList(
+                        Integer.toString(i),
                         Integer.toString(result.countResults(TestStatus.PASS)),
                         Integer.toString(result.countResults(TestStatus.FAIL)),
                         Integer.toString(result.countResults(TestStatus.NOT_EXECUTED)),
                         TimeUtil.formatTimeStamp(result.getStartTime()),
                         result.getTestPlan(),
-                        ArrayUtil.join(", ", result.getDeviceSerials())));
+                        ArrayUtil.join(", ", result.getDeviceSerials()),
+                        buildInfo.get("build_id"),
+                        buildInfo.get("build_product")
+                        ));
+
+                // sort the table entries on each entry's formatted timestamp
+                Collections.sort(table, new Comparator<List<String>>() {
+                    public int compare(List<String> firstList, List<String> secondList) {
+                        return firstList.get(TIMESTAMP_COLUMN)
+                                .compareTo(secondList.get(TIMESTAMP_COLUMN));
+                    }
+                });
             }
+
+            // add the table header to the beginning of the list
+            table.add(0, Arrays.asList("Session", "Pass", "Fail", "Not Executed", "Start Time",
+                    "Test Plan", "Device serial(s)", "Build ID", "Product"));
             tableFormatter.displayTable(table, new PrintWriter(System.out, true));
         } else {
             printLine(String.format("No results found"));
diff --git a/common/util/src/com/android/compatibility/common/util/ResultHandler.java b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
index d91763c..8e435d8 100644
--- a/common/util/src/com/android/compatibility/common/util/ResultHandler.java
+++ b/common/util/src/com/android/compatibility/common/util/ResultHandler.java
@@ -49,6 +49,8 @@
     // XML constants
     private static final String ABI_ATTR = "abi";
     private static final String BUGREPORT_TAG = "BugReport";
+    private static final String BUILD_ID = "build_id";
+    private static final String BUILD_PRODUCT = "build_product";
     private static final String BUILD_TAG = "Build";
     private static final String CASE_TAG = "TestCase";
     private static final String DEVICES_ATTR = "devices";
@@ -107,6 +109,7 @@
                 XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
                 XmlPullParser parser = factory.newPullParser();
                 parser.setInput(new FileReader(resultFile));
+
                 parser.nextTag();
                 parser.require(XmlPullParser.START_TAG, NS, RESULT_TAG);
                 invocation.setStartTime(Long.valueOf(
@@ -116,8 +119,12 @@
                 for (String device : deviceList.split(",")) {
                     invocation.addDeviceSerial(device);
                 }
+
                 parser.nextTag();
                 parser.require(XmlPullParser.START_TAG, NS, BUILD_TAG);
+                invocation.addBuildInfo(BUILD_ID, parser.getAttributeValue(NS, BUILD_ID));
+                invocation.addBuildInfo(BUILD_PRODUCT, parser.getAttributeValue(NS, BUILD_PRODUCT));
+
                 // TODO(stuartscott): may want to reload these incase the retry was done with
                 // --skip-device-info flag
                 parser.nextTag();
@@ -130,8 +137,8 @@
                     parser.require(XmlPullParser.START_TAG, NS, MODULE_TAG);
                     String name = parser.getAttributeValue(NS, NAME_ATTR);
                     String abi = parser.getAttributeValue(NS, ABI_ATTR);
-                    String id = AbiUtils.createId(abi, name);
-                    IModuleResult module = invocation.getOrCreateModule(id);
+                    String moduleId = AbiUtils.createId(abi, name);
+                    IModuleResult module = invocation.getOrCreateModule(moduleId);
                     while (parser.nextTag() == XmlPullParser.START_TAG) {
                         parser.require(XmlPullParser.START_TAG, NS, CASE_TAG);
                         String caseName = parser.getAttributeValue(NS, NAME_ATTR);
diff --git a/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java b/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
index 1e9aa33..c88fe8e 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/ResultHandlerTest.java
@@ -25,6 +25,7 @@
 import java.net.UnknownHostException;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -47,6 +48,12 @@
     private static final String ABI = "mips64";
     private static final String ID_A = AbiUtils.createId(ABI, NAME_A);
     private static final String ID_B = AbiUtils.createId(ABI, NAME_B);
+
+    private static final String BUILD_ID = "build_id";
+    private static final String BUILD_PRODUCT = "build_product";
+    private static final String EXAMPLE_BUILD_ID = "XYZ";
+    private static final String EXAMPLE_BUILD_PRODUCT = "wolverine";
+
     private static final String DEVICE_A = "device123";
     private static final String DEVICE_B = "device456";
     private static final String DEVICES = "device456,device123";
@@ -85,8 +92,9 @@
             "java_version=\"%s\" reference_url=\"%s\">\n" +
             "%s%s%s" +
             "</Result>";
-    private static final String XML_DEVICE_INFO =
-            "  <Build build_fingerprint=\"%s\" />\n";
+    private static final String XML_BUILD_INFO =
+            "  <Build build_fingerprint=\"%s\" " + BUILD_ID + "=\"%s\" " +
+               BUILD_PRODUCT + "=\"%s\" />\n";
     private static final String XML_SUMMARY =
             "  <Summary pass=\"%d\" failed=\"%d\" not_executed=\"%d\" />\n";
     private static final String XML_MODULE =
@@ -144,6 +152,8 @@
         result.setTestPlan(SUITE_PLAN);
         result.addDeviceSerial(DEVICE_A);
         result.addDeviceSerial(DEVICE_B);
+        result.addBuildInfo(BUILD_ID, EXAMPLE_BUILD_ID);
+        result.addBuildInfo(BUILD_PRODUCT, EXAMPLE_BUILD_PRODUCT);
         IModuleResult moduleA = result.getOrCreateModule(ID_A);
         ICaseResult moduleACase = moduleA.getOrCreateResult(CLASS_A);
         ITestResult moduleATest1 = moduleACase.getOrCreateResult(METHOD_1);
@@ -186,7 +196,8 @@
             // Create the result file
             File resultFile = new File(resultDir, ResultHandler.TEST_RESULT_FILE_NAME);
             writer = new FileWriter(resultFile);
-            String deviceInfo = String.format(XML_DEVICE_INFO, DEVICE_A);
+            String buildInfo = String.format(XML_BUILD_INFO, DEVICE_A,
+                    EXAMPLE_BUILD_ID, EXAMPLE_BUILD_PRODUCT);
             String summary = String.format(XML_SUMMARY, 2, 1, 1);
             String moduleATest1 = String.format(XML_TEST_PASS, METHOD_1);
             String moduleATest2 = String.format(XML_TEST_NOT_EXECUTED, METHOD_2);
@@ -210,8 +221,8 @@
             } catch (UnknownHostException ignored) {}
             String output = String.format(XML_BASE, START_MS, END_MS, START_DISPLAY, END_DISPLAY,
                     SUITE_NAME, SUITE_VERSION, SUITE_PLAN, SUITE_BUILD, REPORT_VERSION, DEVICES,
-                    hostName, OS_NAME, OS_VERSION, OS_ARCH, JAVA_VENDOR, JAVA_VERSION, REFERENCE_URL,
-                    deviceInfo, summary, modules);
+                    hostName, OS_NAME, OS_VERSION, OS_ARCH, JAVA_VENDOR,
+                    JAVA_VERSION, REFERENCE_URL, buildInfo, summary, modules);
             writer.write(output);
             writer.flush();
 
@@ -227,16 +238,22 @@
     private void checkResult(List<IInvocationResult> results, File resultDir) throws Exception {
         assertEquals("Expected 1 result", 1, results.size());
         IInvocationResult result = results.get(0);
+        assertEquals("Incorrect result dir", resultDir.getAbsolutePath(),
+                result.getResultDir().getAbsolutePath());
         assertEquals("Expected 2 passes", 2, result.countResults(TestStatus.PASS));
         assertEquals("Expected 1 failure", 1, result.countResults(TestStatus.FAIL));
         assertEquals("Expected 1 not executed", 1, result.countResults(TestStatus.NOT_EXECUTED));
+
+        Map<String, String> buildInfo = result.getBuildInfo();
+        assertEquals("Incorrect Build ID", EXAMPLE_BUILD_ID, buildInfo.get(BUILD_ID));
+        assertEquals("Incorrect Build Product",
+            EXAMPLE_BUILD_PRODUCT, buildInfo.get(BUILD_PRODUCT));
+
         Set<String> serials = result.getDeviceSerials();
         assertTrue("Missing device", serials.contains(DEVICE_A));
         assertTrue("Missing device", serials.contains(DEVICE_B));
         assertEquals("Expected 2 devices", 2, serials.size());
         assertTrue("Incorrect devices", serials.contains(DEVICE_A) && serials.contains(DEVICE_B));
-        assertEquals("Incorrect result dir", resultDir.getAbsolutePath(),
-                result.getResultDir().getAbsolutePath());
         assertEquals("Incorrect start time", START_MS, result.getStartTime());
         assertEquals("Incorrect test plan", SUITE_PLAN, result.getTestPlan());