1 /*
2  * Copyright (C) 2016 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.common.tradefed.targetprep;
18 
19 import com.android.compatibility.common.tradefed.build.VtsCompatibilityInvocationHelper;
20 import com.android.ddmlib.Log;
21 import com.android.tradefed.build.IBuildInfo;
22 import com.android.tradefed.config.Option;
23 import com.android.tradefed.config.OptionClass;
24 import com.android.tradefed.device.DeviceNotAvailableException;
25 import com.android.tradefed.device.ITestDevice;
26 import com.android.tradefed.invoker.TestInformation;
27 import com.android.tradefed.log.LogUtil.CLog;
28 import com.android.tradefed.targetprep.BuildError;
29 import com.android.tradefed.targetprep.PushFilePreparer;
30 import com.android.tradefed.targetprep.TargetSetupError;
31 import java.io.BufferedReader;
32 import java.io.File;
33 import java.io.FileNotFoundException;
34 import java.io.FileReader;
35 import java.util.Collection;
36 import java.util.TreeSet;
37 
38 /**
39  * Pushes specified testing artifacts from Compatibility repository.
40  */
41 @OptionClass(alias = "vts-file-pusher")
42 public class VtsFilePusher extends PushFilePreparer {
43     @Option(name="push-group", description=
44             "A push group name. Must be a .push file under tools/vts-tradefed/res/push_groups/. "
45                     + "May be under a relative path. May be repeated. Duplication of file specs "
46                     + "is ok. Will throw a TargetSetupError if a file push fails.")
47     private Collection<String> mPushSpecGroups = new TreeSet<>();
48 
49     @Option(name="push-group-cleanup", description = "Whether files in push group "
50             + "should be cleaned up from device after test. Note that preparer does not verify "
51             + "that files/directories have been deleted. Default value: true.")
52     private boolean mPushGroupCleanup = true;
53 
54     @Option(name="push-group-remount-system", description="Whether to remounts system "
55             + "partition to be writable before push or clean up default files. "
56             + "Default value: false")
57     private boolean mPushGroupRemount = false;
58 
59     @Option(name = "append-bitness", description = "Append the ABI's bitness to the filename.")
60     private boolean mAppendBitness = false;
61 
62     private static final String DIR_PUSH_GROUPS = "vts/tools/vts-tradefed/res/push_groups";
63     static final String PUSH_GROUP_FILE_EXTENSION = ".push";
64 
65     private Collection<String> mFilesPushed = new TreeSet<>();
66     private VtsCompatibilityInvocationHelper mInvocationHelper;
67 
68     /**
69      * Load file push specs from .push files as a collection of Strings
70      * @param buildInfo
71      * @return a collection of push spec strings
72      * @throws TargetSetupError if load fails
73      */
loadFilePushGroups(IBuildInfo buildInfo)74     private Collection<String> loadFilePushGroups(IBuildInfo buildInfo) throws TargetSetupError {
75         Collection<String> result = new TreeSet<>();
76         File testDir;
77         try {
78             testDir = mInvocationHelper.getTestsDir();
79         } catch(FileNotFoundException e) {
80             throw new TargetSetupError(e.getMessage());
81         }
82 
83         for (String group: mPushSpecGroups) {
84             TreeSet<String> stack = new TreeSet<>();
85             result.addAll(loadFilePushGroup(group, testDir.getAbsolutePath(), stack));
86         }
87         return result;
88     }
89 
90     /**
91      * Recursively load file push specs from a push group file into a collection of strings.
92      *
93      * @param specFile, push group file name with .push extension. Can contain relative directory.
94      * @param testsDir, the directory where test data files are stored.
95      */
loadFilePushGroup(String specFileName, String testsDir, Collection<String> stack)96     private Collection<String> loadFilePushGroup(String specFileName, String testsDir,
97             Collection<String> stack) throws TargetSetupError {
98         Collection<String> result = new TreeSet<>();
99 
100         String relativePath = new File(
101                 DIR_PUSH_GROUPS, specFileName).getPath();
102         File specFile = null;
103 
104         try {
105             specFile = new File(testsDir, relativePath);
106         } catch(Exception e) {
107             throw new TargetSetupError(e.getMessage());
108         }
109 
110         try (BufferedReader br = new BufferedReader(new FileReader(specFile))) {
111             String line;
112             while ((line = br.readLine()) != null) {
113                 String spec = line.trim();
114                 if (spec.contains("->")) {
115                     result.add(spec);
116                 } else if (spec.contains(PUSH_GROUP_FILE_EXTENSION)) {
117                     if (!stack.contains(spec)) {
118                         stack.add(spec);
119                         result.addAll(loadFilePushGroup(spec, testsDir, stack));
120                         stack.remove(spec);
121                     }
122                 } else if (spec.length() > 0) {
123                     throw new TargetSetupError("Unknown file push spec: " + spec);
124                 }
125             }
126         } catch(Exception e) {
127             throw new TargetSetupError(e.getMessage());
128         }
129 
130         return result;
131     }
132 
133     /**
134      * Push file groups if configured in .xml file.
135      * @param device
136      * @param buildInfo
137      * @throws TargetSetupError, DeviceNotAvailableException
138      */
pushFileGroups(ITestDevice device, IBuildInfo buildInfo)139     private void pushFileGroups(ITestDevice device, IBuildInfo buildInfo)
140             throws TargetSetupError, DeviceNotAvailableException {
141         if (mPushGroupRemount) {
142             device.remountSystemWritable();
143         }
144 
145         for (String pushspec : loadFilePushGroups(buildInfo)) {
146             String[] pair = pushspec.split("->");
147 
148             if (pair.length != 2) {
149                 throw new TargetSetupError(
150                         String.format("Failed to parse push spec '%s'", pushspec));
151             }
152 
153             File src = new File(pair[0]);
154 
155             if (!src.isAbsolute()) {
156                 src = resolveRelativeFilePath(buildInfo, pair[0]);
157             }
158 
159             Class cls = this.getClass();
160 
161             if (!src.exists()) {
162                 Log.w(cls.getSimpleName(), String.format(
163                         "Skipping push spec in push group whose source does not exist: %s",
164                         pushspec));
165                 continue;
166             }
167 
168             Log.d(cls.getSimpleName(),
169                     String.format("Trying to push file from local to remote: %s", pushspec));
170 
171             if ((src.isDirectory() && !device.pushDir(src, pair[1])) ||
172                     (!device.pushFile(src, pair[1]))) {
173                 mFilesPushed = null;
174                 throw new TargetSetupError(String.format("Failed to push local '%s' to remote '%s'",
175                         pair[0], pair[1]));
176             } else {
177                 mFilesPushed.add(pair[1]);
178             }
179         }
180     }
181 
182     /**
183      * {@inheritDoc}
184      */
185     @Override
setUp(TestInformation testInfo)186     public void setUp(TestInformation testInfo)
187             throws TargetSetupError, BuildError, DeviceNotAvailableException {
188         ITestDevice device = testInfo.getDevice();
189         IBuildInfo buildInfo = testInfo.getBuildInfo();
190         device.enableAdbRoot();
191         mInvocationHelper = new VtsCompatibilityInvocationHelper();
192         pushFileGroups(device, buildInfo);
193 
194         super.setUp(testInfo);
195     }
196 
197     /**
198      * {@inheritDoc}
199      */
200     @Override
tearDown(TestInformation testInfo, Throwable e)201     public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
202         ITestDevice device = testInfo.getDevice();
203         if (!(e instanceof DeviceNotAvailableException) && mPushGroupCleanup && mFilesPushed != null) {
204             device.enableAdbRoot();
205             if (mPushGroupRemount) {
206                 device.remountSystemWritable();
207             }
208             for (String devicePath : mFilesPushed) {
209                 device.executeShellCommand("rm -r " + devicePath);
210             }
211         }
212 
213         super.tearDown(testInfo, e);
214     }
215 
216     /**
217      * {@inheritDoc}
218      */
219     @Override
resolveRelativeFilePath(IBuildInfo buildInfo, String fileName)220     public File resolveRelativeFilePath(IBuildInfo buildInfo, String fileName) {
221         File f = null;
222         try {
223             f = new File(mInvocationHelper.getTestsDir(),
224                     String.format("%s%s", fileName, mAppendBitness ? getAbi().getBitness() : ""));
225             CLog.d("Copying from %s", f.getAbsolutePath());
226             return f;
227         } catch (FileNotFoundException e) {
228             CLog.e(e);
229             CLog.e("File not found: %s", f);
230         }
231         return null;
232     }
233 }
234