diff options
Diffstat (limited to 'harness/src/main/java/com/android/csuite/config/AppRemoteFileResolver.java')
-rw-r--r-- | harness/src/main/java/com/android/csuite/config/AppRemoteFileResolver.java | 282 |
1 files changed, 0 insertions, 282 deletions
diff --git a/harness/src/main/java/com/android/csuite/config/AppRemoteFileResolver.java b/harness/src/main/java/com/android/csuite/config/AppRemoteFileResolver.java deleted file mode 100644 index 82fddb8..0000000 --- a/harness/src/main/java/com/android/csuite/config/AppRemoteFileResolver.java +++ /dev/null @@ -1,282 +0,0 @@ -/* - * Copyright (C) 2020 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.csuite.config; - -import com.android.tradefed.build.BuildRetrievalError; -import com.android.tradefed.config.ConfigurationException; -import com.android.tradefed.config.DynamicRemoteFileResolver; -import com.android.tradefed.config.Option; -import com.android.tradefed.config.OptionClass; -import com.android.tradefed.config.OptionSetter; -import com.android.tradefed.config.remote.IRemoteFileResolver; -import com.android.tradefed.device.ITestDevice; -import com.android.tradefed.log.LogUtil.CLog; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.common.base.Stopwatch; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableMap; - -import java.io.File; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Map; -import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.annotation.Nullable; -import javax.annotation.concurrent.NotThreadSafe; - -/** - * An implementation of {@code IRemoteFileResolver} for downloading Android apps. - * - * <p>The scheme supported by this resolver allows Trade Federation test configs to abstract the - * actual service used to download Android app APK files. Note that this is a 'meta' resolver that - * resolves abstract 'app://' URIs into a URI with a different scheme using a custom template. The - * actual downloading of this resolved URI is then delegated to another registered {@link - * IRemoteFileResolver} implementation. Variable placeholders in the URI template string are - * expanded with corresponding values. - * - * <h2>Syntax and usage</h2> - * - * <p>References to apps in TradeFed test configs must have the following syntax: - * - * <blockquote> - * - * <b>{@code app://}</b><i>package-name</i> - * - * </blockquote> - * - * where <i>package-name</i> is the name of the application package such as: - * - * <blockquote> - * - * <table cellpadding=0 cellspacing=0 summary="layout"> - * <tr><td>{@code app://com.example.myapp}<td></tr> - * </table> - * - * </blockquote> - * - * App APK files are downloaded to a directory and must be used in contexts that can handle File - * objects pointing to directories. - * - * <h2>Configuration</h2> - * - * <p>The URI template to use is specified using the {@code dynamic-download-args} TradeFed - * command-line argument: - * - * <blockquote> - * - * <pre> - * --dynamic-download-args app:uri-template=file:///app_files/{package} - * </pre> - * - * </blockquote> - * - * <p>Where {package} expands to the actual package name being downloaded. Any illegal URI - * characters must also be properly escaped as expected by {@link java.net.URI}. - * - * <p><span style="font-weight: bold; padding-right: 1em">Usage Note:</span> The {@code - * --enable-module-dynamic-download} flag must be set to {@code true} when used in test suites. - * - * @see com.android.tradefed.config.Option - */ -@NotThreadSafe -@OptionClass(alias = "app") -public final class AppRemoteFileResolver implements IRemoteFileResolver { - - private static final String URI_SCHEME = "app"; - private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(\\w+)\\}"); - - @VisibleForTesting static final String URI_TEMPLATE_OPTION = "uri-template"; - - @Option(name = URI_TEMPLATE_OPTION) - private String mUriTemplate; - - @Nullable private ITestDevice mPrimaryDevice; - - @Override - public String getSupportedProtocol() { - return URI_SCHEME; - } - - @Override - public void setPrimaryDevice(@Nullable ITestDevice primaryDevice) { - this.mPrimaryDevice = primaryDevice; - } - - @Override - public File resolveRemoteFiles(File uriSchemeAndPathAsFile) throws BuildRetrievalError { - // Note that this method is not really supported or even called by the framework. We - // only override it to simplify automated null pointer testing. - return resolveRemoteFiles(uriSchemeAndPathAsFile, ImmutableMap.of()); - } - - @Override - public File resolveRemoteFiles( - File uriSchemeAndPathAsFile, Map<String, String> uriQueryAndExtraParameters) - throws BuildRetrievalError { - URI appUri = checkAppUri(toUri(uriSchemeAndPathAsFile)); - Objects.requireNonNull(uriQueryAndExtraParameters); - - // TODO(hzalek): Remove this and make the corresponding option mandatory once test configs - // are using app URIs. - if (mUriTemplate == null) { - CLog.w("Resolver is not properly configured, skipping resolution of URI (%s)", appUri); - return null; - } - - Preconditions.checkState( - !mUriTemplate.isEmpty(), - String.format("%s=%s is empty", URI_TEMPLATE_OPTION, mUriTemplate)); - - String packageName = appUri.getAuthority(); - String expanded = expandVars(mUriTemplate, ImmutableMap.of("package", packageName)); - - URI uri; - try { - uri = new URI(expanded); - } catch (URISyntaxException e) { - throw new IllegalStateException( - String.format( - "URI template (%s) did not expand to a a valid URI (%s)", - URI_TEMPLATE_OPTION, mUriTemplate, expanded), - e); - } - - if (URI_SCHEME.equals(uri.getScheme())) { - throw new BuildRetrievalError( - String.format( - "Providers must return URIs with a scheme different than '%s': %s > %s", - URI_SCHEME, appUri, uri)); - } - - return resolveUriToFile(packageName, uri, uriQueryAndExtraParameters); - } - - private static URI toUri(File uriSchemeAndPathAsFile) { - try { - // TradeFed forces a URI into a File instance which is lossy and forces us to attempt - // restoring the original format here so we don't have to use regular expressions. Be - // warned that using getAbsolutePath() will incorrectly strip the scheme. - String path = uriSchemeAndPathAsFile.getPath(); - // Restore the original URI form since the first two forward slashes in the URI string - // get normalized into one when stored as a file. - path = path.replaceFirst(":/", "://"); - return new URI(path); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Could not parse provided URI", e); - } - } - - private static URI checkAppUri(URI uri) { - String uriScheme = uri.getScheme(); - if (!URI_SCHEME.equals(uriScheme)) { - throw new IllegalArgumentException( - String.format("Unsupported scheme (%s) in provided URI (%s)", uriScheme, uri)); - } - - // Note that the below code accesses the 'authority' component of the URI and not 'path' - // like the dynamic resolver implementation. The latter has to do so because the authority - // component is no longer defined once the '//' gets converted to a single '/'. - String packageName = uri.getAuthority(); - if (Strings.isNullOrEmpty(packageName)) { - throw new IllegalArgumentException( - String.format( - "Invalid package name (%s) in provided URI (%s)", packageName, uri)); - } - - if (!Strings.isNullOrEmpty(uri.getPath())) { - throw new IllegalArgumentException( - String.format( - "Path component (%s) incorrectly specified in provided URI (%s); " - + "app URIs must be of the form 'app://com.example.app'", - uri.getPath(), uri)); - } - - return uri; - } - - private static String expandVars(CharSequence template, Map<String, String> vars) { - StringBuilder sb = new StringBuilder(); - Matcher matcher = PLACEHOLDER_PATTERN.matcher(template); - int position = 0; - - while (matcher.find()) { - sb.append(template.subSequence(position, matcher.start(0))); - - String varName = matcher.group(1); - String varValue = vars.get(varName); - - if (varValue == null) { - throw new IllegalStateException( - String.format( - "URI template (%s) contains a placeholder for undefined var (%s)", - template, varName)); - } - - sb.append(varValue); - position = matcher.end(0); - } - - sb.append(template.subSequence(position, template.length())); - String expanded = sb.toString(); - - CLog.i("Template (%s) expanded (%s) using vars (%s)", template, expanded, vars); - return expanded; - } - - private File resolveUriToFile(String packageName, URI uri, Map<String, String> params) - throws BuildRetrievalError { - DynamicRemoteFileResolver resolver = new DynamicRemoteFileResolver(); - resolver.setDevice(mPrimaryDevice); - resolver.addExtraArgs(params); - - FileOptionSource optionSource = new FileOptionSource(); - Stopwatch stopwatch = Stopwatch.createStarted(); - - try { - OptionSetter setter = new OptionSetter(optionSource); - setter.setOptionValue(FileOptionSource.OPTION_NAME, uri.toString()); - setter.validateRemoteFilePath(resolver); - CLog.i("Resolution of files took %d ms", stopwatch.elapsed().toMillis()); - } catch (BuildRetrievalError e) { - throw new BuildRetrievalError( - String.format("Could not resolve URI (%s) for package '%s'", uri, packageName), - e); - } catch (ConfigurationException impossible) { - throw new AssertionError(impossible); - } - - if (!optionSource.file.exists()) { - CLog.w("URI (%s) resolved to non-existent local file (%s)", uri, optionSource.file); - } else { - CLog.i("URI (%s) resolved to local file (%s)", uri, optionSource.file); - } - - return optionSource.file; - } - - /** This is required to resolve URIs since the remote resolver only deals with options. */ - private static final class FileOptionSource { - static final String OPTION_NAME = "file"; - - @Option(name = OPTION_NAME, mandatory = true) - public File file; - } -} |