1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.compatibility.targetprep; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 21 import com.android.csuite.core.SystemPackageUninstaller; 22 import com.android.tradefed.build.IBuildInfo; 23 import com.android.tradefed.config.ConfigurationException; 24 import com.android.tradefed.config.Option; 25 import com.android.tradefed.config.OptionSetter; 26 import com.android.tradefed.device.DeviceNotAvailableException; 27 import com.android.tradefed.device.ITestDevice; 28 import com.android.tradefed.invoker.TestInformation; 29 import com.android.tradefed.log.LogUtil.CLog; 30 import com.android.tradefed.targetprep.BuildError; 31 import com.android.tradefed.targetprep.ITargetPreparer; 32 import com.android.tradefed.targetprep.TargetSetupError; 33 import com.android.tradefed.targetprep.TestAppInstallSetup; 34 import com.android.tradefed.util.AaptParser.AaptVersion; 35 36 import com.google.common.annotations.VisibleForTesting; 37 import com.google.common.util.concurrent.SimpleTimeLimiter; 38 import com.google.common.util.concurrent.TimeLimiter; 39 import com.google.common.util.concurrent.UncheckedTimeoutException; 40 41 import java.io.File; 42 import java.time.Duration; 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.concurrent.Executors; 46 import java.util.concurrent.TimeUnit; 47 48 /** A Tradefed preparer that downloads and installs an app on the target device. */ 49 public final class AppSetupPreparer implements ITargetPreparer { 50 51 @VisibleForTesting 52 static final String OPTION_WAIT_FOR_DEVICE_AVAILABLE_SECONDS = 53 "wait-for-device-available-seconds"; 54 55 @VisibleForTesting 56 static final String OPTION_EXPONENTIAL_BACKOFF_MULTIPLIER_SECONDS = 57 "exponential-backoff-multiplier-seconds"; 58 59 @VisibleForTesting static final String OPTION_TEST_FILE_NAME = "test-file-name"; 60 @VisibleForTesting static final String OPTION_INSTALL_ARG = "install-arg"; 61 @VisibleForTesting static final String OPTION_SETUP_TIMEOUT_MILLIS = "setup-timeout-millis"; 62 @VisibleForTesting static final String OPTION_MAX_RETRY = "max-retry"; 63 @VisibleForTesting static final String OPTION_AAPT_VERSION = "aapt-version"; 64 @VisibleForTesting static final String OPTION_INCREMENTAL_INSTALL = "incremental"; 65 66 @Option(name = "package-name", description = "Package name of testing app.") 67 private String mPackageName; 68 69 @Option( 70 name = OPTION_TEST_FILE_NAME, 71 description = "the name of an apk file to be installed on device. Can be repeated.") 72 private final List<File> mTestFiles = new ArrayList<>(); 73 74 @Option(name = OPTION_AAPT_VERSION, description = "The version of AAPT for APK parsing.") 75 private AaptVersion mAaptVersion = AaptVersion.AAPT2; 76 77 @Option( 78 name = OPTION_INSTALL_ARG, 79 description = 80 "Additional arguments to be passed to install command, " 81 + "including leading dash, e.g. \"-d\"") 82 private final List<String> mInstallArgs = new ArrayList<>(); 83 84 @Option( 85 name = OPTION_INCREMENTAL_INSTALL, 86 description = "Enable packages to be installed incrementally.") 87 private boolean mIncrementalInstallation = false; 88 89 @Option(name = OPTION_MAX_RETRY, description = "Max number of retries upon TargetSetupError.") 90 private int mMaxRetry = 0; 91 92 @Option( 93 name = OPTION_EXPONENTIAL_BACKOFF_MULTIPLIER_SECONDS, 94 description = 95 "The exponential backoff multiplier for retries in seconds. " 96 + "A value n means the preparer will wait for n^(retry_count) " 97 + "seconds between retries.") 98 private int mExponentialBackoffMultiplierSeconds = 0; 99 100 @Option( 101 name = OPTION_WAIT_FOR_DEVICE_AVAILABLE_SECONDS, 102 description = 103 "Timeout value for waiting for device available in seconds. " 104 + "A negative value means not to check device availability.") 105 private int mWaitForDeviceAvailableSeconds = -1; 106 107 @Option( 108 name = OPTION_SETUP_TIMEOUT_MILLIS, 109 description = 110 "Timeout value for a setUp operation. " 111 + "Note that the timeout is not a global timeout and will " 112 + "be applied to each retry attempt.") 113 private long mSetupOnceTimeoutMillis = TimeUnit.MINUTES.toMillis(10); 114 115 private final TestAppInstallSetup mTestAppInstallSetup; 116 private final Sleeper mSleeper; 117 private final TimeLimiter mTimeLimiter = 118 SimpleTimeLimiter.create(Executors.newCachedThreadPool()); 119 AppSetupPreparer()120 public AppSetupPreparer() { 121 this(new TestAppInstallSetup(), Sleepers.DefaultSleeper.INSTANCE); 122 } 123 124 @VisibleForTesting AppSetupPreparer(TestAppInstallSetup testAppInstallSetup, Sleeper sleeper)125 public AppSetupPreparer(TestAppInstallSetup testAppInstallSetup, Sleeper sleeper) { 126 mTestAppInstallSetup = testAppInstallSetup; 127 mSleeper = sleeper; 128 } 129 130 /** {@inheritDoc} */ 131 @Override setUp(ITestDevice device, IBuildInfo buildInfo)132 public void setUp(ITestDevice device, IBuildInfo buildInfo) 133 throws DeviceNotAvailableException, BuildError, TargetSetupError { 134 checkArgumentNonNegative(mMaxRetry, OPTION_MAX_RETRY); 135 checkArgumentNonNegative( 136 mExponentialBackoffMultiplierSeconds, 137 OPTION_EXPONENTIAL_BACKOFF_MULTIPLIER_SECONDS); 138 checkArgumentNonNegative(mSetupOnceTimeoutMillis, OPTION_SETUP_TIMEOUT_MILLIS); 139 140 int runCount = 0; 141 while (true) { 142 TargetSetupError currentException; 143 try { 144 runCount++; 145 146 ITargetPreparer handler = 147 mTimeLimiter.newProxy( 148 new ITargetPreparer() { 149 @Override 150 public void setUp(ITestDevice device, IBuildInfo buildInfo) 151 throws DeviceNotAvailableException, BuildError, 152 TargetSetupError { 153 setUpOnce(device, buildInfo); 154 } 155 }, 156 ITargetPreparer.class, 157 mSetupOnceTimeoutMillis, 158 TimeUnit.MILLISECONDS); 159 handler.setUp(device, buildInfo); 160 161 break; 162 } catch (TargetSetupError e) { 163 currentException = e; 164 } catch (UncheckedTimeoutException e) { 165 currentException = new TargetSetupError(e.getMessage(), e); 166 } 167 168 waitForDeviceAvailable(device); 169 if (runCount > mMaxRetry) { 170 throw currentException; 171 } 172 CLog.w("setUp failed: %s. Run count: %d. Retrying...", currentException, runCount); 173 174 try { 175 mSleeper.sleep( 176 Duration.ofSeconds( 177 (int) Math.pow(mExponentialBackoffMultiplierSeconds, runCount))); 178 } catch (InterruptedException e) { 179 Thread.currentThread().interrupt(); 180 throw new TargetSetupError(e.getMessage(), e); 181 } 182 } 183 } 184 setUpOnce(ITestDevice device, IBuildInfo buildInfo)185 private void setUpOnce(ITestDevice device, IBuildInfo buildInfo) 186 throws DeviceNotAvailableException, BuildError, TargetSetupError { 187 mTestAppInstallSetup.setAaptVersion(mAaptVersion); 188 189 try { 190 OptionSetter setter = new OptionSetter(mTestAppInstallSetup); 191 setter.setOptionValue("incremental", String.valueOf(mIncrementalInstallation)); 192 } catch (ConfigurationException e) { 193 throw new TargetSetupError(e.getMessage(), e); 194 } 195 196 if (mPackageName != null) { 197 SystemPackageUninstaller.uninstallPackage(mPackageName, device); 198 } 199 200 for (File testFile : mTestFiles) { 201 mTestAppInstallSetup.addTestFile(testFile); 202 } 203 204 for (String installArg : mInstallArgs) { 205 mTestAppInstallSetup.addInstallArg(installArg); 206 } 207 208 mTestAppInstallSetup.setUp(device, buildInfo); 209 } 210 211 /** {@inheritDoc} */ 212 @Override tearDown(TestInformation testInfo, Throwable e)213 public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException { 214 mTestAppInstallSetup.tearDown(testInfo, e); 215 } 216 waitForDeviceAvailable(ITestDevice device)217 private void waitForDeviceAvailable(ITestDevice device) throws DeviceNotAvailableException { 218 if (mWaitForDeviceAvailableSeconds < 0) { 219 return; 220 } 221 222 device.waitForDeviceAvailable(1000L * mWaitForDeviceAvailableSeconds); 223 } 224 checkArgumentNonNegative(long val, String name)225 private void checkArgumentNonNegative(long val, String name) { 226 checkArgument(val >= 0, "%s (%s) must not be negative", name, val); 227 } 228 229 @VisibleForTesting 230 interface Sleeper { sleep(Duration duration)231 void sleep(Duration duration) throws InterruptedException; 232 } 233 234 static class Sleepers { 235 enum DefaultSleeper implements Sleeper { 236 INSTANCE; 237 238 @Override sleep(Duration duration)239 public void sleep(Duration duration) throws InterruptedException { 240 Thread.sleep(duration.toMillis()); 241 } 242 } 243 Sleepers()244 private Sleepers() {} 245 } 246 } 247