Revert "Revert "CP: Ensure we report an invocation if dynamic download fails""

This reverts commit 95a59f56299ffe1eb507feaf30d0b652f8b3464b.

Reason for revert: prepared ag/9863058 to fix right away the other branch

Change-Id: Ib66ead6a6913bc57813a2685520848b33c23a30f
diff --git a/global_configuration/com/android/tradefed/config/GlobalConfiguration.java b/global_configuration/com/android/tradefed/config/GlobalConfiguration.java
index ba135f4..54f87b4 100644
--- a/global_configuration/com/android/tradefed/config/GlobalConfiguration.java
+++ b/global_configuration/com/android/tradefed/config/GlobalConfiguration.java
@@ -16,6 +16,7 @@
 
 package com.android.tradefed.config;
 
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.command.CommandScheduler;
 import com.android.tradefed.command.ICommandScheduler;
 import com.android.tradefed.config.gcs.GCSConfigurationFactory;
@@ -767,7 +768,11 @@
         CLog.d("Resolve and remote files from @Option");
         // Setup and validate the GCS File paths, they will be deleted when TF ends
         List<File> remoteFiles = new ArrayList<>();
-        remoteFiles.addAll(argsParser.validateRemoteFilePath());
+        try {
+            remoteFiles.addAll(argsParser.validateRemoteFilePath());
+        } catch (BuildRetrievalError e) {
+            throw new ConfigurationException(e.getMessage(), e);
+        }
         remoteFiles.forEach(File::deleteOnExit);
     }
 
diff --git a/src/com/android/tradefed/build/BuildInfo.java b/src/com/android/tradefed/build/BuildInfo.java
index 217a1f8..538dc84 100644
--- a/src/com/android/tradefed/build/BuildInfo.java
+++ b/src/com/android/tradefed/build/BuildInfo.java
@@ -19,7 +19,6 @@
 import com.android.tradefed.build.proto.BuildInformation;
 import com.android.tradefed.build.proto.BuildInformation.BuildFile;
 import com.android.tradefed.build.proto.BuildInformation.KeyBuildFilePair;
-import com.android.tradefed.config.ConfigurationException;
 import com.android.tradefed.config.DynamicRemoteFileResolver;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.invoker.logger.InvocationMetricLogger;
@@ -726,7 +725,7 @@
                 new DynamicRemoteFileResolver()
                         .resolvePartialDownloadZip(
                                 workingDir, file.toString(), includeFilters, null);
-            } catch (ConfigurationException e) {
+            } catch (BuildRetrievalError e) {
                 throw new RuntimeException(e);
             }
 
diff --git a/src/com/android/tradefed/config/Configuration.java b/src/com/android/tradefed/config/Configuration.java
index 7a7adbf..9fe10ee 100644
--- a/src/com/android/tradefed/config/Configuration.java
+++ b/src/com/android/tradefed/config/Configuration.java
@@ -16,6 +16,7 @@
 
 package com.android.tradefed.config;
 
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.build.IBuildProvider;
 import com.android.tradefed.command.CommandOptions;
 import com.android.tradefed.command.ICommandOptions;
@@ -1158,7 +1159,7 @@
 
     /** {@inheritDoc} */
     @Override
-    public void resolveDynamicOptions() throws ConfigurationException {
+    public void resolveDynamicOptions() throws ConfigurationException, BuildRetrievalError {
         // Resolve regardless of sharding if we are in remote environment because we know that's
         // where the execution will occur.
         if (!isRemoteEnvironment()) {
diff --git a/src/com/android/tradefed/config/DynamicRemoteFileResolver.java b/src/com/android/tradefed/config/DynamicRemoteFileResolver.java
index f3f1561..0723182 100644
--- a/src/com/android/tradefed/config/DynamicRemoteFileResolver.java
+++ b/src/com/android/tradefed/config/DynamicRemoteFileResolver.java
@@ -16,6 +16,7 @@
 package com.android.tradefed.config;
 
 import com.android.annotations.VisibleForTesting;
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.config.OptionSetter.OptionFieldsForName;
 import com.android.tradefed.config.remote.GcsRemoteFileResolver;
 import com.android.tradefed.config.remote.HttpRemoteFileResolver;
@@ -79,9 +80,9 @@
      * Runs through all the {@link File} option type and check if their path should be resolved.
      *
      * @return The list of {@link File} that was resolved that way.
-     * @throws ConfigurationException
+     * @throws BuildRetrievalError
      */
-    public final Set<File> validateRemoteFilePath() throws ConfigurationException {
+    public final Set<File> validateRemoteFilePath() throws BuildRetrievalError {
         Set<File> downloadedFiles = new HashSet<>();
         try {
             Map<Field, Object> fieldSeen = new HashMap<>();
@@ -104,7 +105,7 @@
                             continue;
                         }
                     } catch (IllegalAccessException e) {
-                        throw new ConfigurationException(
+                        throw new BuildRetrievalError(
                                 String.format("internal error: %s", e.getMessage()));
                     }
 
@@ -124,7 +125,7 @@
                                 field.set(obj, downloadedFile);
                             } catch (IllegalAccessException e) {
                                 CLog.e(e);
-                                throw new ConfigurationException(
+                                throw new BuildRetrievalError(
                                         String.format(
                                                 "Failed to download %s due to '%s'",
                                                 consideredFile.getPath(), e.getMessage()),
@@ -202,7 +203,7 @@
                     }
                 }
             }
-        } catch (ConfigurationException e) {
+        } catch (BuildRetrievalError e) {
             // Clean up the files before throwing
             for (File f : downloadedFiles) {
                 FileUtil.recursiveDelete(f);
@@ -225,14 +226,14 @@
      *     matching any filter will be downloaded.
      * @param excludeFilters a list of regex strings to skip downloading matching files. A file's
      *     path matching any filter will not be downloaded.
-     * @throws ConfigurationException if files could not be downloaded.
+     * @throws BuildRetrievalError if files could not be downloaded.
      */
     public void resolvePartialDownloadZip(
             File destDir,
             String remoteZipFilePath,
             List<String> includeFilters,
             List<String> excludeFilters)
-            throws ConfigurationException {
+            throws BuildRetrievalError {
         Map<String, String> queryArgs;
         String protocol;
         try {
@@ -240,7 +241,7 @@
             protocol = uri.getScheme();
             queryArgs = parseQuery(uri.getQuery());
         } catch (URISyntaxException e) {
-            throw new ConfigurationException(
+            throw new BuildRetrievalError(
                     String.format(
                             "Failed to parse the remote zip file path: %s", remoteZipFilePath),
                     e);
@@ -257,7 +258,7 @@
         // Downloaded individual files should be saved to destDir, return value is not needed.
         try {
             resolver.resolveRemoteFiles(new File(remoteZipFilePath), null, queryArgs);
-        } catch (ConfigurationException e) {
+        } catch (BuildRetrievalError e) {
             if (isOptional(queryArgs)) {
                 CLog.d(
                         "Failed to partially download '%s' but marked optional so skipping: %s",
@@ -318,8 +319,7 @@
         return downloadedFile;
     }
 
-    private File resolveRemoteFiles(File consideredFile, Option option)
-            throws ConfigurationException {
+    private File resolveRemoteFiles(File consideredFile, Option option) throws BuildRetrievalError {
         File fileToResolve;
         String path = consideredFile.getPath();
         String protocol;
@@ -337,7 +337,7 @@
         if (resolver != null) {
             try {
                 return resolver.resolveRemoteFiles(fileToResolve, option, query);
-            } catch (ConfigurationException e) {
+            } catch (BuildRetrievalError e) {
                 if (isOptional(query)) {
                     CLog.d(
                             "Failed to resolve '%s' but marked optional so skipping: %s",
diff --git a/src/com/android/tradefed/config/IConfiguration.java b/src/com/android/tradefed/config/IConfiguration.java
index 4359d24..bde88b9 100644
--- a/src/com/android/tradefed/config/IConfiguration.java
+++ b/src/com/android/tradefed/config/IConfiguration.java
@@ -16,6 +16,7 @@
 
 package com.android.tradefed.config;
 
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.build.IBuildProvider;
 import com.android.tradefed.command.ICommandOptions;
 import com.android.tradefed.device.IDeviceRecovery;
@@ -501,9 +502,10 @@
      * Resolve options of {@link File} pointing to a remote location. This requires {@link
      * #cleanConfigurationData()} to be called to clean up the files.
      *
+     * @throws BuildRetrievalError
      * @throws ConfigurationException
      */
-    public void resolveDynamicOptions() throws ConfigurationException;
+    public void resolveDynamicOptions() throws ConfigurationException, BuildRetrievalError;
 
     /** Delete any files that was downloaded to resolved Option fields of remote files. */
     public void cleanConfigurationData();
diff --git a/src/com/android/tradefed/config/OptionSetter.java b/src/com/android/tradefed/config/OptionSetter.java
index 8c97dac..671b5fd 100644
--- a/src/com/android/tradefed/config/OptionSetter.java
+++ b/src/com/android/tradefed/config/OptionSetter.java
@@ -17,6 +17,7 @@
 package com.android.tradefed.config;
 
 import com.android.annotations.VisibleForTesting;
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.util.ArrayUtil;
 import com.android.tradefed.util.MultiMap;
@@ -756,9 +757,9 @@
      * Runs through all the {@link File} option type and check if their path should be resolved.
      *
      * @return The list of {@link File} that was resolved that way.
-     * @throws ConfigurationException
+     * @throws BuildRetrievalError
      */
-    public final Set<File> validateRemoteFilePath() throws ConfigurationException {
+    public final Set<File> validateRemoteFilePath() throws BuildRetrievalError {
         DynamicRemoteFileResolver resolver = createResolver();
         resolver.setOptionMap(mOptionMap);
         return resolver.validateRemoteFilePath();
diff --git a/src/com/android/tradefed/config/remote/GcsRemoteFileResolver.java b/src/com/android/tradefed/config/remote/GcsRemoteFileResolver.java
index 8d1ecbd..45a6ed1 100644
--- a/src/com/android/tradefed/config/remote/GcsRemoteFileResolver.java
+++ b/src/com/android/tradefed/config/remote/GcsRemoteFileResolver.java
@@ -18,7 +18,6 @@
 import com.android.annotations.VisibleForTesting;
 import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.build.gcs.GCSDownloaderHelper;
-import com.android.tradefed.config.ConfigurationException;
 import com.android.tradefed.config.DynamicRemoteFileResolver;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -38,7 +37,7 @@
 
     @Override
     public File resolveRemoteFiles(File consideredFile, Option option, Map<String, String> query)
-            throws ConfigurationException {
+            throws BuildRetrievalError {
         // Don't use absolute path as it would not start with gs:
         String path = consideredFile.getPath();
         CLog.d("Considering option '%s' with path: '%s' for download.", option.name(), path);
@@ -49,7 +48,7 @@
             return DynamicRemoteFileResolver.unzipIfRequired(downloadedFile, query);
         } catch (BuildRetrievalError | IOException e) {
             CLog.e(e);
-            throw new ConfigurationException(
+            throw new BuildRetrievalError(
                     String.format("Failed to download %s due to: %s", path, e.getMessage()), e);
         }
     }
diff --git a/src/com/android/tradefed/config/remote/HttpRemoteFileResolver.java b/src/com/android/tradefed/config/remote/HttpRemoteFileResolver.java
index b92c6c4..d3dc9f9 100644
--- a/src/com/android/tradefed/config/remote/HttpRemoteFileResolver.java
+++ b/src/com/android/tradefed/config/remote/HttpRemoteFileResolver.java
@@ -16,7 +16,7 @@
 package com.android.tradefed.config.remote;
 
 import com.android.annotations.VisibleForTesting;
-import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.util.FileUtil;
@@ -38,7 +38,7 @@
     @Override
     public File resolveRemoteFiles(
             File consideredFile, Option option, Map<String, String> queryArgs)
-            throws ConfigurationException {
+            throws BuildRetrievalError {
         // Don't use absolute path as it would not start with gs:
         String path = consideredFile.getPath();
         CLog.d("Considering option '%s' with path: '%s' for download.", option.name(), path);
@@ -55,7 +55,7 @@
             downloader.doGet(path, new FileOutputStream(downloadedFile));
         } catch (IOException | RuntimeException e) {
             FileUtil.deleteFile(downloadedFile);
-            throw new ConfigurationException(
+            throw new BuildRetrievalError(
                     String.format("Failed to download %s due to: %s", path, e.getMessage()), e);
         }
         return downloadedFile;
diff --git a/src/com/android/tradefed/config/remote/IRemoteFileResolver.java b/src/com/android/tradefed/config/remote/IRemoteFileResolver.java
index f1413bd..6494fe3 100644
--- a/src/com/android/tradefed/config/remote/IRemoteFileResolver.java
+++ b/src/com/android/tradefed/config/remote/IRemoteFileResolver.java
@@ -15,7 +15,7 @@
  */
 package com.android.tradefed.config.remote;
 
-import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.config.Option;
 
 import java.io.File;
@@ -35,11 +35,11 @@
      * @param consideredFile {@link File} evaluated as remote.
      * @param option The original option configuring the file.
      * @return The resolved local file.
-     * @throws ConfigurationException if something goes wrong.
+     * @throws BuildRetrievalError if something goes wrong.
      */
     public default @Nonnull File resolveRemoteFiles(File consideredFile, Option option)
-            throws ConfigurationException {
-        throw new ConfigurationException("Should not have been called");
+            throws BuildRetrievalError {
+        throw new BuildRetrievalError("Should not have been called");
     }
 
     /**
@@ -49,11 +49,11 @@
      * @param option The original option configuring the file.
      * @param queryArgs The arguments passed as a query to the URL.
      * @return The resolved local file.
-     * @throws ConfigurationException if something goes wrong.
+     * @throws BuildRetrievalError if something goes wrong.
      */
     public default @Nonnull File resolveRemoteFiles(
             File consideredFile, Option option, Map<String, String> queryArgs)
-            throws ConfigurationException {
+            throws BuildRetrievalError {
         return resolveRemoteFiles(consideredFile, option);
     }
 
diff --git a/src/com/android/tradefed/config/remote/LocalFileResolver.java b/src/com/android/tradefed/config/remote/LocalFileResolver.java
index 432098f..1a30c3f 100644
--- a/src/com/android/tradefed/config/remote/LocalFileResolver.java
+++ b/src/com/android/tradefed/config/remote/LocalFileResolver.java
@@ -15,7 +15,7 @@
  */
 package com.android.tradefed.config.remote;
 
-import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.log.LogUtil.CLog;
 
@@ -29,8 +29,7 @@
     public static final String PROTOCOL = "file";
 
     @Override
-    public File resolveRemoteFiles(File consideredFile, Option option)
-            throws ConfigurationException {
+    public File resolveRemoteFiles(File consideredFile, Option option) throws BuildRetrievalError {
         // Don't use absolute path as it would not start with gs:
         String path = consideredFile.getPath();
         CLog.d("Considering option '%s' with path: '%s' for download.", option.name(), path);
@@ -39,7 +38,7 @@
         if (localFile.exists()) {
             return localFile;
         }
-        throw new ConfigurationException(String.format("Failed to find local file %s.", localFile));
+        throw new BuildRetrievalError(String.format("Failed to find local file %s.", localFile));
     }
 
     @Override
diff --git a/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java b/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java
index fdf80df..2aedf27 100644
--- a/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java
+++ b/src/com/android/tradefed/device/cloud/ManagedRemoteDevice.java
@@ -16,6 +16,7 @@
 package com.android.tradefed.device.cloud;
 
 import com.android.ddmlib.IDevice;
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Configuration;
 import com.android.tradefed.config.ConfigurationException;
@@ -220,7 +221,7 @@
             mValidationConfig.setDeviceOptions(mCopiedOptions);
             try {
                 mValidationConfig.resolveDynamicOptions();
-            } catch (ConfigurationException e) {
+            } catch (BuildRetrievalError | ConfigurationException e) {
                 throw new RuntimeException(e);
             }
         }
diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java
index 1b63d9c..81d2e92 100644
--- a/src/com/android/tradefed/invoker/TestInvocation.java
+++ b/src/com/android/tradefed/invoker/TestInvocation.java
@@ -16,6 +16,7 @@
 package com.android.tradefed.invoker;
 
 import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.build.BuildInfo;
 import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.command.CommandRunner.ExitCode;
@@ -659,7 +660,11 @@
                 config.resolveDynamicOptions();
             }
             return true;
-        } catch (RuntimeException | ConfigurationException e) {
+        } catch (RuntimeException | BuildRetrievalError | ConfigurationException e) {
+            // We don't have a reporting buildInfo at this point
+            IBuildInfo info = new BuildInfo();
+            context.addDeviceBuildInfo(context.getDeviceConfigNames().get(0), info);
+
             // Report an empty invocation, so this error is sent to listeners
             startInvocation(config, context, listener);
             // Don't want to use #reportFailure, since that will call buildNotTested
diff --git a/src/com/android/tradefed/invoker/shard/ShardHelper.java b/src/com/android/tradefed/invoker/shard/ShardHelper.java
index f24986e..14a933c 100644
--- a/src/com/android/tradefed/invoker/shard/ShardHelper.java
+++ b/src/com/android/tradefed/invoker/shard/ShardHelper.java
@@ -16,6 +16,7 @@
 package com.android.tradefed.invoker.shard;
 
 import com.android.annotations.VisibleForTesting;
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.config.Configuration;
 import com.android.tradefed.config.ConfigurationDescriptor;
 import com.android.tradefed.config.ConfigurationException;
@@ -203,7 +204,8 @@
 
     /** Runs the {@link IConfiguration#validateOptions()} on the config. */
     @VisibleForTesting
-    protected void validateOptions(IConfiguration config) throws ConfigurationException {
+    protected void validateOptions(IConfiguration config)
+            throws ConfigurationException, BuildRetrievalError {
         config.validateOptions();
         config.resolveDynamicOptions();
     }
@@ -240,8 +242,7 @@
                     .addMetadata(ConfigurationDescriptor.LOCAL_SHARDED_KEY, "true");
             // Validate and download the dynamic options
             validateOptions(clonedConfig);
-        } catch (ConfigurationException e) {
-            // should not happen
+        } catch (ConfigurationException | BuildRetrievalError e) {
             throw new RuntimeException(
                     String.format("failed to deep copy a configuration: %s", e.getMessage()), e);
         }
diff --git a/src/com/android/tradefed/invoker/shard/TestsPoolPoller.java b/src/com/android/tradefed/invoker/shard/TestsPoolPoller.java
index a3299d4..5f0c314 100644
--- a/src/com/android/tradefed/invoker/shard/TestsPoolPoller.java
+++ b/src/com/android/tradefed/invoker/shard/TestsPoolPoller.java
@@ -17,6 +17,7 @@
 
 import com.android.annotations.VisibleForTesting;
 import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Configuration;
 import com.android.tradefed.config.ConfigurationException;
@@ -238,7 +239,7 @@
                     CLog.w("Proceeding to the next test.");
                 } catch (DeviceNotAvailableException dnae) {
                     HandleDeviceNotAvailable(listener, dnae, test);
-                } catch (ConfigurationException e) {
+                } catch (ConfigurationException | BuildRetrievalError e) {
                     CLog.w(
                             "Failed to validate the @options of test: %s. Proceeding to next test.",
                             test.getClass());
diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java
index 7ff6fab..28da062 100644
--- a/src/com/android/tradefed/testtype/suite/ITestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java
@@ -17,6 +17,7 @@
 
 import com.android.annotations.VisibleForTesting;
 import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.build.IDeviceBuildInfo;
 import com.android.tradefed.config.ConfigurationException;
@@ -466,7 +467,7 @@
             try {
                 mDynamicResolver.resolvePartialDownloadZip(
                         getTestsDir(), remoteFile.toString(), includeFilters, excludeFilters);
-            } catch (ConfigurationException | FileNotFoundException e) {
+            } catch (BuildRetrievalError | FileNotFoundException e) {
                 CLog.e(
                         String.format(
                                 "Failed to download partial zip from %s for modules: %s",
diff --git a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
index df23c57..b09b759 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
@@ -16,6 +16,7 @@
 package com.android.tradefed.testtype.suite;
 
 import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Configuration;
 import com.android.tradefed.config.ConfigurationDescriptor;
@@ -1018,7 +1019,7 @@
             CLog.d("Attempting to resolve dynamic files from %s", getId());
             moduleConfiguration.resolveDynamicOptions();
             return null;
-        } catch (RuntimeException | ConfigurationException e) {
+        } catch (RuntimeException | ConfigurationException | BuildRetrievalError e) {
             mIsFailedModule = true;
             return e;
         }
diff --git a/test_framework/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java b/test_framework/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java
index b69cf75..0d6959a 100644
--- a/test_framework/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java
+++ b/test_framework/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java
@@ -15,6 +15,7 @@
  */
 package com.android.tradefed.testtype;
 
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.ConfigurationException;
 import com.android.tradefed.config.Option;
@@ -179,7 +180,7 @@
         try {
             OptionSetter setter = createOptionSetter(obj);
             return setter.validateRemoteFilePath();
-        } catch (ConfigurationException e) {
+        } catch (BuildRetrievalError | ConfigurationException e) {
             throw new RuntimeException(e);
         }
     }
diff --git a/test_framework/com/android/tradefed/testtype/HostTest.java b/test_framework/com/android/tradefed/testtype/HostTest.java
index c6144b4..1901fad 100644
--- a/test_framework/com/android/tradefed/testtype/HostTest.java
+++ b/test_framework/com/android/tradefed/testtype/HostTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.tradefed.testtype;
 
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.build.IDeviceBuildInfo;
 import com.android.tradefed.config.ConfigurationException;
@@ -1270,7 +1271,7 @@
         try {
             OptionSetter setter = createOptionSetter(obj);
             return setter.validateRemoteFilePath();
-        } catch (ConfigurationException e) {
+        } catch (BuildRetrievalError | ConfigurationException e) {
             throw new RuntimeException(e);
         }
     }
diff --git a/tests/src/com/android/tradefed/config/ConfigurationTest.java b/tests/src/com/android/tradefed/config/ConfigurationTest.java
index 9cfecbc..e0c3952 100644
--- a/tests/src/com/android/tradefed/config/ConfigurationTest.java
+++ b/tests/src/com/android/tradefed/config/ConfigurationTest.java
@@ -559,7 +559,7 @@
      * sharding. If that was the case, the downloaded files would be cleaned up right after the
      * shards are kicked-off in new invocations.
      */
-    public void testValidateOptions_localSharding_skipDownload() throws ConfigurationException {
+    public void testValidateOptions_localSharding_skipDownload() throws Exception {
         mConfig =
                 new Configuration(CONFIG_NAME, CONFIG_DESCRIPTION) {
                     @Override
diff --git a/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java b/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java
index f82bcf7..81471a4 100644
--- a/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java
+++ b/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.config.remote.GcsRemoteFileResolver;
 import com.android.tradefed.config.remote.IRemoteFileResolver;
 import com.android.tradefed.util.FileUtil;
@@ -192,7 +193,7 @@
                                 EasyMock.eq(new File("gs:/fake/path")),
                                 EasyMock.anyObject(),
                                 EasyMock.eq(testMap)))
-                .andThrow(new ConfigurationException("Failed to download"));
+                .andThrow(new BuildRetrievalError("Failed to download"));
         EasyMock.replay(mMockResolver);
 
         Set<File> downloadedFile = setter.validateRemoteFilePath();
@@ -284,12 +285,12 @@
                                 EasyMock.eq(new File("gs://failure/test")),
                                 EasyMock.anyObject(),
                                 EasyMock.anyObject()))
-                .andThrow(new ConfigurationException("retrieval error"));
+                .andThrow(new BuildRetrievalError("retrieval error"));
         EasyMock.replay(mMockResolver);
         try {
             setter.validateRemoteFilePath();
             fail("Should have thrown an exception");
-        } catch (ConfigurationException expected) {
+        } catch (BuildRetrievalError expected) {
             // Only when we reach failure/test it fails
             assertTrue(expected.getMessage().contains("retrieval error"));
         }
@@ -470,7 +471,7 @@
                                         @Override
                                         public @Nonnull File resolveRemoteFiles(
                                                 File consideredFile, Option option)
-                                                throws ConfigurationException {
+                                                throws BuildRetrievalError {
                                             return mMockResolver.resolveRemoteFiles(
                                                     consideredFile, option);
                                         }
@@ -560,7 +561,7 @@
                                 EasyMock.eq(new File("gs:/fake/path?optional=true")),
                                 EasyMock.eq(null),
                                 EasyMock.eq(queryArgs)))
-                .andThrow(new ConfigurationException("should not throw this exception."));
+                .andThrow(new BuildRetrievalError("should not throw this exception."));
         EasyMock.replay(mMockResolver);
 
         mResolver.resolvePartialDownloadZip(
diff --git a/tests/src/com/android/tradefed/config/remote/GcsRemoteFileResolverTest.java b/tests/src/com/android/tradefed/config/remote/GcsRemoteFileResolverTest.java
index 5d9e007..1c67b29 100644
--- a/tests/src/com/android/tradefed/config/remote/GcsRemoteFileResolverTest.java
+++ b/tests/src/com/android/tradefed/config/remote/GcsRemoteFileResolverTest.java
@@ -22,7 +22,6 @@
 
 import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.build.gcs.GCSDownloaderHelper;
-import com.android.tradefed.config.ConfigurationException;
 import com.android.tradefed.config.DynamicRemoteFileResolver;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.util.FileUtil;
@@ -75,7 +74,7 @@
             mResolver.resolveRemoteFiles(
                     new File("gs:/fake/file"), Mockito.mock(Option.class), new HashMap<>());
             fail("Should have thrown an exception.");
-        } catch (ConfigurationException expected) {
+        } catch (BuildRetrievalError expected) {
             assertEquals(
                     "Failed to download gs:/fake/file due to: download failure",
                     expected.getMessage());
diff --git a/tests/src/com/android/tradefed/config/remote/HttpRemoteFileResolverTest.java b/tests/src/com/android/tradefed/config/remote/HttpRemoteFileResolverTest.java
index 5419916..046c0a8 100644
--- a/tests/src/com/android/tradefed/config/remote/HttpRemoteFileResolverTest.java
+++ b/tests/src/com/android/tradefed/config/remote/HttpRemoteFileResolverTest.java
@@ -18,7 +18,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
-import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.net.IHttpHelper;
@@ -76,7 +76,7 @@
                     Mockito.mock(Option.class),
                     new HashMap<>());
             fail("Should have thrown an exception.");
-        } catch (ConfigurationException expected) {
+        } catch (BuildRetrievalError expected) {
             assertEquals(
                     "Failed to download http://fake/HttpRemoteFileResolverTest due to: download failure",
                     expected.getMessage());
diff --git a/tests/src/com/android/tradefed/config/remote/LocalFileResolverTest.java b/tests/src/com/android/tradefed/config/remote/LocalFileResolverTest.java
index 86d35f6..ab5ad17 100644
--- a/tests/src/com/android/tradefed/config/remote/LocalFileResolverTest.java
+++ b/tests/src/com/android/tradefed/config/remote/LocalFileResolverTest.java
@@ -18,7 +18,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
-import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.util.FileUtil;
 
@@ -59,7 +59,7 @@
         try {
             mResolver.resolveRemoteFiles(markedFile, Mockito.mock(Option.class));
             fail("Should have thrown an exception.");
-        } catch (ConfigurationException expected) {
+        } catch (BuildRetrievalError expected) {
             // Expected
         }
     }
diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
index 0174c00..a3946b3 100644
--- a/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
+++ b/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
@@ -274,6 +274,11 @@
         mMockConfig.resolveDynamicOptions();
         EasyMock.expectLastCall().andThrow(configException);
 
+        DeviceConfigurationHolder holder1 = new DeviceConfigurationHolder();
+        mProvider1 = EasyMock.createMock(IBuildProvider.class);
+        holder1.addSpecificConfig(mProvider1);
+        EasyMock.expect(mMockConfig.getDeviceConfigByName("device1")).andStubReturn(holder1);
+        EasyMock.expect(mDevice1.getSerialNumber()).andReturn("serial1");
         mMockConfig.cleanConfigurationData();
 
         mMockTestListener.invocationStarted(mContext);
diff --git a/tests/src/com/android/tradefed/testtype/DeviceJUnit4ClassRunnerTest.java b/tests/src/com/android/tradefed/testtype/DeviceJUnit4ClassRunnerTest.java
index 3e691b8..4450d2d 100644
--- a/tests/src/com/android/tradefed/testtype/DeviceJUnit4ClassRunnerTest.java
+++ b/tests/src/com/android/tradefed/testtype/DeviceJUnit4ClassRunnerTest.java
@@ -20,6 +20,7 @@
 import static org.mockito.Mockito.doReturn;
 
 import com.android.tradefed.build.BuildInfo;
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.config.ConfigurationDef;
 import com.android.tradefed.config.ConfigurationException;
 import com.android.tradefed.config.DynamicRemoteFileResolver;
@@ -79,7 +80,7 @@
                                                             Mockito.any(),
                                                             Mockito.any());
                                             return mockResolver;
-                                        } catch (ConfigurationException e) {
+                                        } catch (BuildRetrievalError e) {
                                             CLog.e(e);
                                         }
                                     }
diff --git a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
index 05326a5..90bba5d 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
@@ -25,6 +25,7 @@
 
 import com.android.ddmlib.IDevice;
 import com.android.tradefed.build.BuildInfo;
+import com.android.tradefed.build.BuildRetrievalError;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.build.IDeviceBuildInfo;
 import com.android.tradefed.config.Configuration;
@@ -70,6 +71,7 @@
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.ITestFilterReceiver;
 import com.android.tradefed.testtype.StubTest;
+import com.android.tradefed.testtype.coverage.CoverageOptions;
 import com.android.tradefed.util.AbiUtils;
 import com.android.tradefed.util.MultiMap;
 
@@ -1782,7 +1784,7 @@
                             String remoteFilePath,
                             List<String> includeFilters,
                             List<String> excludeFilters)
-                            throws ConfigurationException {
+                            throws BuildRetrievalError {
                         assertEquals(new File("tests_dir"), destDir);
                         assertEquals(remoteFilePath, remoteFilePath);
                         assertArrayEquals(new String[] {"/test/"}, includeFilters.toArray());