1 /* 2 * Copyright (C) 2011 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.tradefed.targetprep; 18 19 import com.android.ddmlib.IDevice; 20 import com.android.ddmlib.Log; 21 import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey; 22 import com.android.tradefed.build.IBuildInfo; 23 import com.android.tradefed.build.IDeviceBuildInfo; 24 import com.android.tradefed.config.Option; 25 import com.android.tradefed.config.OptionClass; 26 import com.android.tradefed.device.DeviceNotAvailableException; 27 import com.android.tradefed.device.ITestDevice; 28 import com.android.tradefed.util.FileUtil; 29 30 import java.io.File; 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.Collection; 34 35 /** 36 * A {@link ITargetPreparer} that attempts to push any number of files from any host path to any 37 * device path. 38 * 39 * <p>Should be performed *after* a new build is flashed, and *after* DeviceSetup is run (if 40 * enabled) 41 */ 42 @OptionClass(alias = "push-file") 43 public class PushFilePreparer extends BaseTargetPreparer implements ITargetCleaner { 44 private static final String LOG_TAG = "PushFilePreparer"; 45 private static final String MEDIA_SCAN_INTENT = 46 "am broadcast -a android.intent.action.MEDIA_MOUNTED -d file://%s " 47 + "--receiver-include-background"; 48 49 @Option(name="push", description= 50 "A push-spec, formatted as '/path/to/srcfile.txt->/path/to/destfile.txt' or " + 51 "'/path/to/srcfile.txt->/path/to/destdir/'. May be repeated.") 52 private Collection<String> mPushSpecs = new ArrayList<>(); 53 54 @Option(name="post-push", description= 55 "A command to run on the device (with `adb shell (yourcommand)`) after all pushes " + 56 "have been attempted. Will not be run if a push fails with abort-on-push-failure " + 57 "enabled. May be repeated.") 58 private Collection<String> mPostPushCommands = new ArrayList<>(); 59 60 @Option(name="abort-on-push-failure", description= 61 "If false, continue if pushes fail. If true, abort the Invocation on any failure.") 62 private boolean mAbortOnFailure = true; 63 64 @Option(name="trigger-media-scan", description= 65 "After pushing files, trigger a media scan of external storage on device.") 66 private boolean mTriggerMediaScan = false; 67 68 @Option(name="cleanup", description = "Whether files pushed onto device should be cleaned up " 69 + "after test. Note that the preparer does not verify that files/directories have " 70 + "been deleted.") 71 private boolean mCleanup = false; 72 73 @Option(name="remount-system", description="Remounts system partition to be writable " 74 + "so that files could be pushed there too") 75 private boolean mRemount = false; 76 77 private Collection<String> mFilesPushed = null; 78 79 /** 80 * Helper method to only throw if mAbortOnFailure is enabled. Callers should behave as if this 81 * method may return. 82 */ fail(String message, ITestDevice device)83 private void fail(String message, ITestDevice device) throws TargetSetupError { 84 if (mAbortOnFailure) { 85 throw new TargetSetupError(message, device.getDeviceDescriptor()); 86 } else { 87 // Log the error and return 88 Log.w(LOG_TAG, message); 89 } 90 } 91 92 /** 93 * Resolve relative file path via {@link IBuildInfo} and test cases directories. 94 * 95 * @param buildInfo the build artifact information 96 * @param fileName relative file path to be resolved 97 * @return the file from the build info or test cases directories 98 */ resolveRelativeFilePath(IBuildInfo buildInfo, String fileName)99 public File resolveRelativeFilePath(IBuildInfo buildInfo, String fileName) { 100 File src = null; 101 if (buildInfo != null) { 102 src = buildInfo.getFile(fileName); 103 if (src != null && src.exists()) { 104 return src; 105 } 106 } 107 108 if (buildInfo instanceof IDeviceBuildInfo) { 109 IDeviceBuildInfo deviceBuild = (IDeviceBuildInfo) buildInfo; 110 // If it exists always look first in the ANDROID_TARGET_OUT_TESTCASES 111 File targetTestCases = deviceBuild.getFile(BuildInfoFileKey.TARGET_LINKED_DIR); 112 if (targetTestCases != null) { 113 src = FileUtil.findFile(targetTestCases, fileName); 114 } 115 if (src != null && src.exists()) { 116 return src; 117 } 118 // Search the full tests dir if no target dir is available. 119 File testsDir = deviceBuild.getTestsDir(); 120 return FileUtil.findFile(testsDir, fileName); 121 } 122 return null; 123 } 124 125 /** 126 * {@inheritDoc} 127 */ 128 @Override setUp(ITestDevice device, IBuildInfo buildInfo)129 public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, BuildError, 130 DeviceNotAvailableException { 131 mFilesPushed = new ArrayList<>(); 132 if (mRemount) { 133 device.remountSystemWritable(); 134 } 135 for (String pushspec : mPushSpecs) { 136 String[] pair = pushspec.split("->"); 137 if (pair.length != 2) { 138 fail(String.format("Invalid pushspec: '%s'", Arrays.asList(pair)), device); 139 continue; 140 } 141 Log.d(LOG_TAG, String.format("Trying to push local '%s' to remote '%s'", pair[0], 142 pair[1])); 143 144 File src = new File(pair[0]); 145 if (!src.isAbsolute()) { 146 src = resolveRelativeFilePath(buildInfo, pair[0]); 147 } 148 if (src == null || !src.exists()) { 149 fail(String.format("Local source file '%s' does not exist", pair[0]), device); 150 continue; 151 } 152 if (src.isDirectory()) { 153 if (!device.pushDir(src, pair[1])) { 154 fail(String.format("Failed to push local '%s' to remote '%s'", pair[0], 155 pair[1]), device); 156 continue; 157 } else { 158 mFilesPushed.add(pair[1]); 159 } 160 } else { 161 if (!device.pushFile(src, pair[1])) { 162 fail(String.format("Failed to push local '%s' to remote '%s'", pair[0], 163 pair[1]), device); 164 continue; 165 } else { 166 mFilesPushed.add(pair[1]); 167 } 168 } 169 } 170 171 for (String command : mPostPushCommands) { 172 device.executeShellCommand(command); 173 } 174 175 if (mTriggerMediaScan) { 176 String mountPoint = device.getMountPoint(IDevice.MNT_EXTERNAL_STORAGE); 177 device.executeShellCommand(String.format(MEDIA_SCAN_INTENT, mountPoint)); 178 } 179 } 180 181 /** 182 * {@inheritDoc} 183 */ 184 @Override tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)185 public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) 186 throws DeviceNotAvailableException { 187 if (!(e instanceof DeviceNotAvailableException) && mCleanup && mFilesPushed != null) { 188 if (mRemount) { 189 device.remountSystemWritable(); 190 } 191 for (String devicePath : mFilesPushed) { 192 device.executeShellCommand("rm -r " + devicePath); 193 } 194 } 195 } 196 } 197