• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.testtype;
18 
19 import com.android.tradefed.config.ConfigurationException;
20 import com.android.tradefed.config.OptionCopier;
21 import com.android.tradefed.device.DeviceNotAvailableException;
22 import com.android.tradefed.log.LogUtil.CLog;
23 import com.android.tradefed.result.CollectingTestListener;
24 import com.android.tradefed.result.FilteredResultForwarder;
25 import com.android.tradefed.result.ITestInvocationListener;
26 import com.android.tradefed.result.TestDescription;
27 import com.android.tradefed.util.FileUtil;
28 
29 import com.google.common.annotations.VisibleForTesting;
30 
31 import java.io.BufferedWriter;
32 import java.io.File;
33 import java.io.FileWriter;
34 import java.io.IOException;
35 import java.util.Collection;
36 import java.util.LinkedHashMap;
37 import java.util.Map;
38 
39 /**
40  * Runs a set of instrumentation tests by specifying a list of line separated test classes and
41  * methods in a file pushed to device (expected format: com.android.foo.FooClassName#testMethodName)
42  * <p>
43  * Note: Requires a runner that supports test execution from a file. Will default to serial tests
44  * execution via {@link InstrumentationSerialTest} if any issues with file creation are encountered
45  * or if all tests in the created file fail to successfully finish execution.
46  */
47 class InstrumentationFileTest implements IRemoteTest {
48 
49     // on device test folder location where the test file should be saved
50     private static final String ON_DEVICE_TEST_DIR_LOCATION = "/data/local/tmp/";
51     /** Key that matches the -e package option for instrumentation. */
52     private static final String PACKAGE_ARG_KEY = "package";
53 
54     private InstrumentationTest mInstrumentationTest = null;
55 
56     /** the set of tests to run */
57     private final Collection<TestDescription> mTests;
58 
59     private String mFilePathOnDevice = null;
60 
61     private int mAttemps;
62 
63     private int mMaxAttemps;
64 
65     private boolean mRetrySerially;
66 
67     /**
68      * Creates a {@link InstrumentationFileTest}.
69      *
70      * @param instrumentationTest {@link InstrumentationTest} used to configure this class
71      * @param testsToRun a {@link Collection} of tests to run. Note this {@link Collection} will be
72      *     used as is (ie a reference to the testsToRun object will be kept).
73      */
InstrumentationFileTest( InstrumentationTest instrumentationTest, Collection<TestDescription> testsToRun, boolean retrySerially, int maxAttempts)74     InstrumentationFileTest(
75             InstrumentationTest instrumentationTest,
76             Collection<TestDescription> testsToRun,
77             boolean retrySerially,
78             int maxAttempts)
79             throws ConfigurationException {
80         // reuse the InstrumentationTest class to perform actual test run
81         mInstrumentationTest = createInstrumentationTest();
82         // copy all options from the original InstrumentationTest
83         OptionCopier.copyOptions(instrumentationTest, mInstrumentationTest);
84         mInstrumentationTest.setDevice(instrumentationTest.getDevice());
85         mInstrumentationTest.setForceAbi(instrumentationTest.getForceAbi());
86         mInstrumentationTest.setReRunUsingTestFile(true);
87         // no need to rerun when executing tests one by one
88         mInstrumentationTest.setRerunMode(false);
89         mInstrumentationTest.setIsRerun(true);
90         // keep local copy of tests to be run
91         mTests = testsToRun;
92         mAttemps = 0;
93         mRetrySerially = retrySerially;
94         mMaxAttemps = maxAttempts;
95     }
96 
97     /**
98      * {@inheritDoc}
99      */
100     @Override
run(final ITestInvocationListener listener)101     public void run(final ITestInvocationListener listener) throws DeviceNotAvailableException {
102         if (mInstrumentationTest.getDevice() == null) {
103             throw new IllegalArgumentException("Device has not been set");
104         }
105         // reuse InstrumentationTest class to perform actual test run
106         writeTestsToFileAndRun(mTests, listener);
107     }
108 
109 
110     /**
111      * Creates a file based on the {@link Collection} of tests to run. Upon successful file creation
112      * will push the file onto the test device and attempt to run them via {@link
113      * InstrumentationTest}. If something goes wrong, will default to serial test execution.
114      *
115      * @param tests a {@link Collection} of tests to run
116      * @param listener the test result listener
117      * @throws DeviceNotAvailableException
118      */
writeTestsToFileAndRun( Collection<TestDescription> tests, final ITestInvocationListener listener)119     private void writeTestsToFileAndRun(
120             Collection<TestDescription> tests, final ITestInvocationListener listener)
121             throws DeviceNotAvailableException {
122         mAttemps += 1;
123         if (mMaxAttemps > 0 && mAttemps <= mMaxAttemps) {
124             CLog.d("Try to run tests from file for the %d/%d attempts",
125                     mAttemps, mMaxAttemps);
126         } else if (mMaxAttemps > 0) {
127             if (mRetrySerially) {
128                 CLog.d("Running tests from file exceeded max attempts."
129                         + " Try to run tests serially.");
130                 reRunTestsSerially(mInstrumentationTest, listener);
131             } else {
132                 CLog.d("Running tests from file exceeded max attempts. Ignore the rest tests");
133                 return;
134             }
135         }
136         File testFile = null;
137         try {
138             // create and populate test file
139             testFile = FileUtil.createTempFile(
140                     "tf_testFile_" + InstrumentationFileTest.class.getCanonicalName(), ".txt");
141             try (BufferedWriter bw = new BufferedWriter(new FileWriter(testFile))) {
142                 // Remove parameterized tests to only re-run their base method.
143                 Collection<TestDescription> uniqueMethods = createRerunSet(tests);
144 
145                 for (TestDescription testToRun : uniqueMethods) {
146                     // We use getTestNameNoParams to avoid attempting re-running individual
147                     // parameterized tests. Instead ask the base method to re-run them all.
148                     bw.write(
149                             String.format(
150                                     "%s#%s",
151                                     testToRun.getClassName(),
152                                     testToRun.getTestNameWithoutParams()));
153                     bw.newLine();
154                 }
155                 CLog.d("Test file %s was successfully created", testFile.getAbsolutePath());
156             }
157             // push test file to the device and run
158             mFilePathOnDevice = ON_DEVICE_TEST_DIR_LOCATION + testFile.getName();
159             if (pushFileToTestDevice(testFile, mFilePathOnDevice)) {
160                 // Unset package name if any just in case to avoid conflict with classname.
161                 // Since at that point we explicitly request the class to rerun there is no need to
162                 // keep any of the original package options.
163                 mInstrumentationTest.setTestPackageName(null);
164                 mInstrumentationTest.removeFromInstrumentationArg(PACKAGE_ARG_KEY);
165                 mInstrumentationTest.setTestFilePathOnDevice(mFilePathOnDevice);
166                 CLog.d("Test file %s was successfully pushed to %s on device",
167                         testFile.getAbsolutePath(), mFilePathOnDevice);
168                 runTests(mInstrumentationTest, listener);
169             } else {
170                 if (mRetrySerially) {
171                     CLog.e("Failed to push file to device, re-running tests serially");
172                     reRunTestsSerially(mInstrumentationTest, listener);
173                 } else {
174                     CLog.e("Failed to push file to device, ignore the rest of tests");
175                 }
176             }
177         } catch (IOException e) {
178             if (mRetrySerially) {
179                 CLog.e("Failed to run tests from file, re-running tests serially: %s",
180                         e.getMessage());
181                 reRunTestsSerially(mInstrumentationTest, listener);
182             } else {
183                 CLog.e("Failed to push file to device, ignore the rest of tests");
184             }
185         } finally {
186             // clean up test file, if it was created
187             FileUtil.deleteFile(testFile);
188         }
189     }
190 
191     /**
192      * Run all tests from file. Attempt to re-run not finished tests.
193      * If all tests in file fail to run default to executing them serially.
194      */
runTests(InstrumentationTest runner, ITestInvocationListener listener)195     private void runTests(InstrumentationTest runner, ITestInvocationListener listener)
196             throws DeviceNotAvailableException {
197         CollectingTestListener testTracker = new CollectingTestListener();
198         try {
199             runner.run(new FilteredResultForwarder(mTests, listener, testTracker));
200         } finally {
201             deleteTestFileFromDevice(mFilePathOnDevice);
202             Collection<TestDescription> completedTests =
203                     testTracker.getCurrentRunResults().getCompletedTests();
204             if (mTests.removeAll(completedTests) && !mTests.isEmpty()) {
205                 // re-run remaining tests from file
206                 writeTestsToFileAndRun(mTests, listener);
207             } else if (!mTests.isEmpty()) {
208                 if (mRetrySerially) {
209                     CLog.e("all remaining tests failed to run from file, re-running tests serially");
210                     reRunTestsSerially(runner, listener);
211                 } else {
212                     CLog.e("all remaining tests failed to run from file, will be ignored");
213                 }
214             }
215         }
216     }
217 
218     /**
219      * Re-runs remaining tests one-by-one
220      */
reRunTestsSerially(InstrumentationTest runner, ITestInvocationListener listener)221     private void reRunTestsSerially(InstrumentationTest runner, ITestInvocationListener listener)
222             throws DeviceNotAvailableException {
223         // clear file path arguments to ensure it won't get used.
224         runner.setTestFilePathOnDevice(null);
225         // enforce serial re-run
226         runner.setReRunUsingTestFile(false);
227         // Set tests to run
228         runner.setTestsToRun(mTests);
229         runner.run(listener);
230     }
231 
232     /**
233      * Returns a new collection of {@link TestDescription} where only one instance of each
234      * parameterized method is in the list.
235      */
createRerunSet(Collection<TestDescription> tests)236     private Collection<TestDescription> createRerunSet(Collection<TestDescription> tests) {
237         Map<String, TestDescription> uniqueMethods = new LinkedHashMap<>();
238         for (TestDescription test : tests) {
239             uniqueMethods.put(test.getTestNameWithoutParams(), test);
240         }
241         return uniqueMethods.values();
242     }
243 
244     /**
245      * Util method to push file to a device. Exposed for unit testing.
246      *
247      * @return if file was pushed to the device successfully
248      * @throws DeviceNotAvailableException
249      */
pushFileToTestDevice(File file, String destinationPath)250     boolean pushFileToTestDevice(File file, String destinationPath)
251             throws DeviceNotAvailableException {
252         return mInstrumentationTest.getDevice().pushFile(file, destinationPath);
253     }
254 
255     /**
256      * Delete file from the device if it exists
257      */
deleteTestFileFromDevice(String pathToFile)258     void deleteTestFileFromDevice(String pathToFile) throws DeviceNotAvailableException {
259         if (mInstrumentationTest.getDevice().doesFileExist(pathToFile)) {
260             mInstrumentationTest.getDevice()
261                     .executeShellCommand(String.format("rm %s", pathToFile));
262             CLog.d("Removed test file from device: %s", pathToFile);
263         }
264     }
265 
266     /** @return the {@link InstrumentationTest} to use. Exposed for unit testing. */
267     @VisibleForTesting
createInstrumentationTest()268     InstrumentationTest createInstrumentationTest() {
269         return new InstrumentationTest();
270     }
271 }
272