Update to the latest changes in Testing API.

Support a list of progress messages. Retrieve tool results
data from structured source. Switch to different matrix
execution states.

Change-Id: I1e0f290f1a6fccd9118572a9775bf5d6f6073157
diff --git a/google-cloud-testing.iml b/google-cloud-testing.iml
index 74ce3f2..0355091 100644
--- a/google-cloud-testing.iml
+++ b/google-cloud-testing.iml
@@ -54,7 +54,7 @@
     <orderEntry type="module-library">
       <library>
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/lib/google-api-services-testing-v1-rev20150305-1.20.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/lib/google-api-services-testing-v1-revsnapshot-1.20.0.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES />
diff --git a/lib/google-api-services-testing-v1-rev20150305-1.20.0.jar b/lib/google-api-services-testing-v1-rev20150305-1.20.0.jar
deleted file mode 100644
index 99db126..0000000
--- a/lib/google-api-services-testing-v1-rev20150305-1.20.0.jar
+++ /dev/null
Binary files differ
diff --git a/lib/google-api-services-testing-v1-revsnapshot-1.20.0.jar b/lib/google-api-services-testing-v1-revsnapshot-1.20.0.jar
new file mode 100644
index 0000000..2cd3076
--- /dev/null
+++ b/lib/google-api-services-testing-v1-revsnapshot-1.20.0.jar
Binary files differ
diff --git a/src/com/google/gct/testing/CloudResultsLoader.java b/src/com/google/gct/testing/CloudResultsLoader.java
index f854e5b..a15b9d4 100644
--- a/src/com/google/gct/testing/CloudResultsLoader.java
+++ b/src/com/google/gct/testing/CloudResultsLoader.java
@@ -16,12 +16,13 @@
 package com.google.gct.testing;
 
 import com.google.api.client.http.HttpHeaders;
+import com.google.api.client.util.Maps;
 import com.google.api.services.storage.Storage;
 import com.google.api.services.storage.model.StorageObject;
 import com.google.api.services.testing.model.AndroidDevice;
-import com.google.api.services.testing.model.ResultStorage;
 import com.google.api.services.testing.model.TestExecution;
 import com.google.api.services.testing.model.TestMatrix;
+import com.google.api.services.testing.model.ToolResultsExecution;
 import com.google.common.base.Function;
 import com.google.common.base.Functions;
 import com.google.common.base.Optional;
@@ -77,10 +78,10 @@
         if (optionalBytes.isPresent()) {
           String progressLine = new String(optionalBytes.get());
           String encodedConfigurationInstance = file.getEncodedConfigurationInstance();
-          String previousProgressLine = getPreviousProgress(encodedConfigurationInstance);
-          if (!progressLine.equals(previousProgressLine)) {
+          List<String> previousProgress = getPreviousProgress(encodedConfigurationInstance);
+          if (!previousProgress.contains(progressLine)) {
             newDataReceived = true;
-            configurationProgress.put(encodedConfigurationInstance, progressLine);
+            previousProgress.add(progressLine);
             testRunListener.testConfigurationProgress(
               ConfigurationInstance.parseFromEncodedString(encodedConfigurationInstance).getResultsViewerDisplayString(), progressLine);
           }
@@ -102,7 +103,7 @@
   private boolean webLinkReported = false;
 
   // Encoded configuration instance -> progress accumulated so far.
-  private final Map<String, String> configurationProgress = new HashMap<String, String>();
+  private final Map<String, List<String>> configurationProgress = Maps.newHashMap();
 
 
   public CloudResultsLoader(String cloudProjectId, IGoogleCloudTestRunListener testRunListener, ProcessHandler processHandler,
@@ -226,8 +227,7 @@
       webLinkReported = true;
       if (!testMatrixState.equals("INVALID")) {
         processHandler.notifyTextAvailable("You can also view test results, along with other runs against this app, on the web:\n" +
-                                           getTuxLink(cloudProjectId, testMatrix.getResultStorage()) + " \n\n\n",
-                                           ProcessOutputTypes.STDOUT);
+                                           getWebResultsLink(testMatrix) + " \n\n\n", ProcessOutputTypes.STDOUT);
       }
     }
     for (TestExecution testExecution : testMatrix.getTestExecutions()) {
@@ -243,25 +243,27 @@
     String testExecutionState = testExecution.getState();
     if (testExecutionState.equals("UNSUPPORTED_ENVIRONMENT")) {
       handleTriggeringError(results, encodedConfigurationInstance,
-                            "Skipped triggering the test execution: Incompatible API level for requested model\n");
+                            "Skipped triggering the test execution: Incompatible API level for requested model");
     } else if (testExecutionState.equals("INCOMPATIBLE_ENVIRONMENT")) {
       // It is not expected to happen for Android Studio client.
-      handleTriggeringError(results, encodedConfigurationInstance, "The given APK is not compatible with this configuration\n");
+      handleTriggeringError(results, encodedConfigurationInstance, "The given APK is not compatible with this configuration");
     } else if (testExecutionState.equals("INVALID")) {
       // It is not expected to happen for Android Studio client.
-      handleTriggeringError(results, encodedConfigurationInstance, "The provided APK is invalid\n");
-    } else if (!testExecutionState.equals("QUEUED")) {
+      handleTriggeringError(results, encodedConfigurationInstance, "The provided APK is invalid");
+    } else if (!testExecutionState.equals("PENDING")) {
       if (testExecutionState.equals("ERROR")) {
-        String diffProgress = INFRASTRUCTURE_FAILURE_PREFIX + " " + testExecution.getTestDetails().getErrorDetails() + "\n";
-        String previousProgress = getPreviousProgress(encodedConfigurationInstance);
-        if (!previousProgress.endsWith(diffProgress)) {
-          reportNewProgress(encodedConfigurationInstance, previousProgress, previousProgress + diffProgress);
+        String newProgress = INFRASTRUCTURE_FAILURE_PREFIX + " " + testExecution.getTestDetails().getErrorMessage();
+        List<String> previousProgress = getPreviousProgress(encodedConfigurationInstance);
+        if (previousProgress.isEmpty() || !previousProgress.get(previousProgress.size() - 1).endsWith(newProgress)) {
+          reportNewProgress(encodedConfigurationInstance, newProgress);
         }
-      } else if (testExecutionState.equals("IN_PROGRESS")) {
-        String newProgress = testExecution.getTestDetails().getProgressDetails();
-        String previousProgress = getPreviousProgress(encodedConfigurationInstance);
-        if (newProgress != null && !newProgress.equals(previousProgress)) {
-          reportNewProgress(encodedConfigurationInstance, previousProgress, newProgress);
+      } else if (testExecutionState.equals("RUNNING")) {
+        List<String> progressMessages = testExecution.getTestDetails().getProgressMessages();
+        List<String> previousProgress = getPreviousProgress(encodedConfigurationInstance);
+        if (previousProgress.size() < progressMessages.size()) {
+          for (int i = previousProgress.size(); i < progressMessages.size(); i++) {
+            reportNewProgress(encodedConfigurationInstance, progressMessages.get(i));
+          }
         }
       }
       ConfigurationResult result = getOrCreateConfigurationResult(encodedConfigurationInstance, results);
@@ -273,19 +275,24 @@
     }
   }
 
-  private void handleTriggeringError(Map<String, ConfigurationResult> results, String encodedConfigurationInstance, String diffProgress) {
-    // Probably, no previous progress could be expected in this scenario, but it would not hurt considering it anyway.
-    String previousProgress = getPreviousProgress(encodedConfigurationInstance);
-    reportNewProgress(encodedConfigurationInstance, previousProgress, previousProgress + diffProgress);
+  private void handleTriggeringError(Map<String, ConfigurationResult> results, String encodedConfigurationInstance, String newProgress) {
+    reportNewProgress(encodedConfigurationInstance, newProgress);
     ConfigurationResult result = getOrCreateConfigurationResult(encodedConfigurationInstance, results);
     result.setTriggeringError(true);
     finishedConfigurationInstances.add(encodedConfigurationInstance);
   }
 
-  private static String getTuxLink(String cloudProjectId, ResultStorage resultStorage) {
+  /**
+   * Invoke only after the APK validation is finished. Otherwise, result storage data is not available yet.
+   */
+  private String getWebResultsLink(@NotNull TestMatrix testMatrix) {
+    ToolResultsExecution toolResultsExecution = testMatrix.getResultStorage().getToolResultsExecution();
+    if (toolResultsExecution == null) { // Should never happen if the APK validation is finished.
+      return "";
+    }
     return "https://console.developers.google.com/project/" + cloudProjectId
-           + "/testlab/mobile/histories/" + resultStorage.getToolResultsHistoryId()
-           + "/executions/" + resultStorage.getToolResultsExecutionId();
+           + "/testlab/mobile/histories/" + toolResultsExecution.getHistoryId()
+           + "/executions/" + toolResultsExecution.getExecutionId();
   }
 
   private String getEncodedConfigurationNameForTestExecution(TestExecution testExecution) {
@@ -296,16 +303,11 @@
            + androidDevice.getOrientation();
   }
 
-  private boolean failedToTriggerTest(String testExecutionId) {
-    return testExecutionId.startsWith("Error ") || testExecutionId.startsWith("Skipped ");
-  }
-
-  private void reportNewProgress(String encodedConfigurationInstance, String previousProgress, String newProgress) {
+  private void reportNewProgress(String encodedConfigurationInstance, String newProgress) {
     newDataReceived = true;
-    configurationProgress.put(encodedConfigurationInstance, newProgress);
-    String diffProgress = newProgress.substring(previousProgress.length());
+    getPreviousProgress(encodedConfigurationInstance).add(newProgress);
     testRunListener.testConfigurationProgress(
-      ConfigurationInstance.parseFromEncodedString(encodedConfigurationInstance).getResultsViewerDisplayString(), diffProgress);
+      ConfigurationInstance.parseFromEncodedString(encodedConfigurationInstance).getResultsViewerDisplayString(), newProgress);
   }
 
   private ConfigurationResult getOrCreateConfigurationResult(String encodedConfigurationInstance,
@@ -319,14 +321,20 @@
     return result;
   }
 
-  private static boolean isInfrastructureFailure(String progress) {
-    String[] progressLines = progress.split("\n");
-    return progressLines[progressLines.length - 1].startsWith(INFRASTRUCTURE_FAILURE_PREFIX);
+  private static boolean isInfrastructureFailure(List<String> progressMessages) {
+    if (progressMessages.isEmpty()) {
+      return false;
+    }
+    return progressMessages.get(progressMessages.size() - 1).startsWith(INFRASTRUCTURE_FAILURE_PREFIX);
   }
 
-  private String getPreviousProgress(String encodedConfigurationInstance) {
-    String progress = configurationProgress.get(encodedConfigurationInstance);
-    return progress == null ? "" : progress;
+  private List<String> getPreviousProgress(String encodedConfigurationInstance) {
+    List<String> progressMessages = configurationProgress.get(encodedConfigurationInstance);
+    if (progressMessages == null) {
+      progressMessages = new LinkedList<String>();
+      configurationProgress.put(encodedConfigurationInstance, progressMessages);
+    }
+    return progressMessages;
   }
 
   private void loadResultFiles(Map<String, ConfigurationResult> results) {
diff --git a/src/com/google/gct/testing/results/GoogleCloudTestListener.java b/src/com/google/gct/testing/results/GoogleCloudTestListener.java
index da22fa2..bcd58a6 100644
--- a/src/com/google/gct/testing/results/GoogleCloudTestListener.java
+++ b/src/com/google/gct/testing/results/GoogleCloudTestListener.java
@@ -114,8 +114,8 @@
 
   private String prepareProgressString(String progressMessage) {
     return CloudTestingUtils.shouldShowProgressTimestamps()
-      ? progressMessage.substring(0, progressMessage.length() - 1) + "\t" + System.currentTimeMillis() + "\n"
-      : progressMessage;
+           ? progressMessage + "\t" + System.currentTimeMillis() + "\n"
+           : progressMessage + "\n";
   }
 
   @Override