• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.tradefed.testtype;
17 
18 import com.android.tradefed.build.IFolderBuildInfo;
19 import com.android.tradefed.config.Option;
20 import com.android.tradefed.log.LogUtil.CLog;
21 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
22 import com.android.tradefed.result.FailureDescription;
23 import com.android.tradefed.result.FileInputStreamSource;
24 import com.android.tradefed.result.ITestInvocationListener;
25 import com.android.tradefed.result.InputStreamSource;
26 import com.android.tradefed.result.LogDataType;
27 import com.android.tradefed.result.TestDescription;
28 import com.android.tradefed.util.FileUtil;
29 import com.android.tradefed.util.HprofAllocSiteParser;
30 import com.android.tradefed.util.StreamUtil;
31 import com.android.tradefed.util.SystemUtil.EnvVariable;
32 import com.android.tradefed.util.TarUtil;
33 import com.android.tradefed.util.proto.TfMetricProtoUtil;
34 
35 import com.google.common.annotations.VisibleForTesting;
36 
37 import java.io.File;
38 import java.io.IOException;
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.HashMap;
42 import java.util.LinkedHashSet;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Set;
46 import java.util.regex.Pattern;
47 
48 /**
49  * A {@link IRemoteTest} for running unit or functional tests against a separate TF installation.
50  * <p/>
51  * Launches an external java process to run the tests. Used for running the TF unit or
52  * functional tests continuously.
53  */
54 public class TfTestLauncher extends SubprocessTfLauncher {
55 
56     @Option(name = "jacoco-code-coverage", description = "Enable jacoco code coverage on the java "
57             + "sub process. Run will be slightly slower because of the overhead.")
58     private boolean mEnableCoverage = false;
59 
60     @Option(name = "include-coverage", description = "Patterns to include in the code coverage.")
61     private Set<String> mIncludeCoverage = new LinkedHashSet<>();
62 
63     @Option(name = "exclude-coverage", description = "Patterns to exclude in the code coverage.")
64     private Set<String> mExcludeCoverage = new LinkedHashSet<>();
65 
66     @Option(
67         name = "hprof-heap-memory",
68         description =
69                 "Enable hprof agent while running the java"
70                         + "sub process. Run will be slightly slower because of the overhead."
71     )
72     private boolean mEnableHprof = false;
73 
74     @Option(name = "ant-config-res", description = "The name of the ant resource configuration to "
75             + "transform the results in readable format.")
76     private String mAntConfigResource = "/jacoco/ant-tf-coverage.xml";
77 
78     @Option(name = "sub-branch", description = "The branch to be provided to the sub invocation, "
79             + "if null, the branch in build info will be used.")
80     private String mSubBranch = null;
81 
82     @Option(name = "sub-build-flavor", description = "The build flavor to be provided to the "
83             + "sub invocation, if null, the build flavor in build info will be used.")
84     private String mSubBuildFlavor = null;
85 
86     @Option(name = "sub-build-id", description = "The build id that the sub invocation will try "
87             + "to use in case where it needs its own device.")
88     private String mSubBuildId = null;
89 
90     @Option(name = "use-virtual-device", description =
91             "Flag if the subprocess is going to need to instantiate a virtual device to run.")
92     private boolean mUseVirtualDevice = false;
93 
94     @Option(name = "sub-apk-path", description = "The name of all the Apks that needs to be "
95             + "installed by the subprocess invocation. Apk need to be inside the downloaded zip. "
96             + "Can be repeated.")
97     private List<String> mSubApkPath = new ArrayList<String>();
98 
99     @Option(name = "skip-temp-dir-check", description = "Whether or not to skip temp dir check.")
100     private boolean mSkipTmpDirCheck = false;
101 
102     // The regex pattern of temp files to be found in the temporary dir of the subprocess.
103     // Any file not matching the patterns, or multiple files in the temporary dir match the same
104     // pattern, is considered as test failure.
105     private static final String[] EXPECTED_TMP_FILE_PATTERNS = {
106         "inv_.*", "tradefed_global_log_.*", "lc_cache", "stage-android-build-api",
107     };
108     // A destination file where the hprof report will be put.
109     private File mHprofFile = null;
110 
111     /** {@inheritDoc} */
112     @Override
addJavaArguments(List<String> args)113     protected void addJavaArguments(List<String> args) {
114         super.addJavaArguments(args);
115         try {
116             if (mEnableHprof) {
117                 mHprofFile = FileUtil.createTempFile("java.hprof", ".txt");
118                 // verbose=n to avoid dump in stderr
119                 // cutoff the min value we look at.
120                 String hprofAgent =
121                         String.format(
122                                 "-agentlib:hprof=heap=sites,cutoff=0.01,depth=16,verbose=n,file=%s",
123                                 mHprofFile.getAbsolutePath());
124                 args.add(hprofAgent);
125             }
126         } catch (IOException e) {
127             throw new RuntimeException(e);
128         }
129     }
130 
131     /** {@inheritDoc} */
132     @Override
preRun()133     protected void preRun() {
134         super.preRun();
135 
136         if (!mUseVirtualDevice) {
137             mCmdArgs.add("-n");
138         } else {
139             // if it needs a device we also enable more logs
140             mCmdArgs.add("--log-level");
141             mCmdArgs.add("VERBOSE");
142             mCmdArgs.add("--log-level-display");
143             mCmdArgs.add("VERBOSE");
144         }
145         mCmdArgs.add("--test-tag");
146         mCmdArgs.add(mBuildInfo.getTestTag());
147         mCmdArgs.add("--build-id");
148         if (mSubBuildId != null) {
149             mCmdArgs.add(mSubBuildId);
150         } else {
151             mCmdArgs.add(mBuildInfo.getBuildId());
152         }
153         mCmdArgs.add("--branch");
154         if (mSubBranch != null) {
155             mCmdArgs.add(mSubBranch);
156         } else if (mBuildInfo.getBuildBranch() != null) {
157             mCmdArgs.add(mBuildInfo.getBuildBranch());
158         } else {
159             throw new RuntimeException("Branch option is required for the sub invocation.");
160         }
161         mCmdArgs.add("--build-flavor");
162         if (mSubBuildFlavor != null) {
163             mCmdArgs.add(mSubBuildFlavor);
164         } else if (mBuildInfo.getBuildFlavor() != null) {
165             mCmdArgs.add(mBuildInfo.getBuildFlavor());
166         } else {
167             throw new RuntimeException("Build flavor option is required for the sub invocation.");
168         }
169 
170         for (String apk : mSubApkPath) {
171             mCmdArgs.add("--apk-path");
172             String apkPath =
173                     String.format(
174                             "%s%s%s",
175                             ((IFolderBuildInfo) mBuildInfo).getRootDir().getAbsolutePath(),
176                             File.separator,
177                             apk);
178             mCmdArgs.add(apkPath);
179         }
180         // Unset potential build environment to ensure they do not affect the unit tests
181         getRunUtil().unsetEnvVariable(EnvVariable.ANDROID_HOST_OUT_TESTCASES.name());
182         getRunUtil().unsetEnvVariable(EnvVariable.ANDROID_TARGET_OUT_TESTCASES.name());
183     }
184 
185     /** {@inheritDoc} */
186     @Override
postRun(ITestInvocationListener listener, boolean exception, long elapsedTime)187     protected void postRun(ITestInvocationListener listener, boolean exception, long elapsedTime) {
188         super.postRun(listener, exception, elapsedTime);
189         reportMetrics(elapsedTime, listener);
190         if (mEnableHprof) {
191             logHprofResults(mHprofFile, listener);
192         }
193 
194         if (mTmpDir != null) {
195             testTmpDirClean(mTmpDir, listener);
196         }
197         cleanTmpFile();
198     }
199 
200     @VisibleForTesting
cleanTmpFile()201     void cleanTmpFile() {
202         FileUtil.deleteFile(mHprofFile);
203     }
204 
205     /**
206      * Report an elapsed-time metric to keep track of it.
207      *
208      * @param elapsedTime time it took the subprocess to run.
209      * @param listener the {@link ITestInvocationListener} where to report the metric.
210      */
reportMetrics(long elapsedTime, ITestInvocationListener listener)211     private void reportMetrics(long elapsedTime, ITestInvocationListener listener) {
212         if (elapsedTime == -1L) {
213             return;
214         }
215         listener.testRunStarted("elapsed-time", 1);
216         TestDescription tid = new TestDescription("elapsed-time", "run-elapsed-time");
217         listener.testStarted(tid);
218         HashMap<String, Metric> runMetrics = new HashMap<>();
219         runMetrics.put(
220                 "elapsed-time", TfMetricProtoUtil.stringToMetric(Long.toString(elapsedTime)));
221         listener.testEnded(tid, runMetrics);
222         listener.testRunEnded(0L, runMetrics);
223     }
224 
225     /**
226      * Extra test to ensure no files are created by the unit tests in the subprocess and not
227      * cleaned.
228      *
229      * @param tmpDir the temporary dir of the subprocess.
230      * @param listener the {@link ITestInvocationListener} where to report the test.
231      */
232     @VisibleForTesting
testTmpDirClean(File tmpDir, ITestInvocationListener listener)233     protected void testTmpDirClean(File tmpDir, ITestInvocationListener listener) {
234         if (mSkipTmpDirCheck) {
235             return;
236         }
237         listener.testRunStarted("temporaryFiles", 1);
238         TestDescription tid = new TestDescription("temporary-files", "testIfClean");
239         listener.testStarted(tid);
240         String[] listFiles = tmpDir.list();
241         List<String> unmatchedFiles = new ArrayList<String>();
242         List<String> patterns = new ArrayList<String>(Arrays.asList(EXPECTED_TMP_FILE_PATTERNS));
243         patterns.add(mBuildInfo.getBuildBranch());
244         for (String file : Arrays.asList(listFiles)) {
245             boolean matchFound = false;
246             for (String pattern : patterns) {
247                 if (Pattern.matches(pattern, file)) {
248                     patterns.remove(pattern);
249                     matchFound = true;
250                     break;
251                 }
252             }
253             if (!matchFound) {
254                 unmatchedFiles.add(file);
255             }
256         }
257         if (unmatchedFiles.size() > 0) {
258             String trace =
259                     String.format(
260                             "Found '%d' unexpected temporary files: %s.\nOnly "
261                                     + "expected files are: %s. And each should appears only once.",
262                             unmatchedFiles.size(), unmatchedFiles, patterns);
263             listener.testFailed(tid, FailureDescription.create(trace));
264         }
265         listener.testEnded(tid, new HashMap<String, Metric>());
266         listener.testRunEnded(0, new HashMap<String, Metric>());
267     }
268 
269     /**
270      * Helper to log and report as metric the hprof data.
271      *
272      * @param hprofFile file containing the Hprof report
273      * @param listener the {@link ITestInvocationListener} where to report the test.
274      */
logHprofResults(File hprofFile, ITestInvocationListener listener)275     private void logHprofResults(File hprofFile, ITestInvocationListener listener) {
276         if (hprofFile == null) {
277             CLog.w("Hprof file was null. Skipping parsing.");
278             return;
279         }
280         if (!hprofFile.exists()) {
281             CLog.w("Hprof file %s was not found. Skipping parsing.", hprofFile.getAbsolutePath());
282             return;
283         }
284         InputStreamSource memory = null;
285         File tmpGzip = null;
286         try {
287             tmpGzip = TarUtil.gzip(hprofFile);
288             memory = new FileInputStreamSource(tmpGzip);
289             listener.testLog("hprof", LogDataType.GZIP, memory);
290         } catch (IOException e) {
291             CLog.e(e);
292             return;
293         } finally {
294             StreamUtil.cancel(memory);
295             FileUtil.deleteFile(tmpGzip);
296         }
297         HprofAllocSiteParser parser = new HprofAllocSiteParser();
298         try {
299             Map<String, String> results = parser.parse(hprofFile);
300             if (results.isEmpty()) {
301                 CLog.d("No allocation site found from hprof file");
302                 return;
303             }
304             listener.testRunStarted("hprofAllocSites", 1);
305             TestDescription tid = new TestDescription("hprof", "allocationSites");
306             listener.testStarted(tid);
307             listener.testEnded(tid, TfMetricProtoUtil.upgradeConvert(results));
308             listener.testRunEnded(0, new HashMap<String, Metric>());
309         } catch (IOException e) {
310             throw new RuntimeException(e);
311         }
312     }
313 }
314