1 /*
2  * Copyright (C) 2017 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.annotations.VisibleForTesting;
20 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
21 import com.android.tradefed.build.IBuildInfo;
22 import com.android.tradefed.config.OptionClass;
23 import com.android.tradefed.config.Option;
24 import com.android.tradefed.device.DeviceNotAvailableException;
25 import com.android.tradefed.device.ITestDevice;
26 import com.android.tradefed.log.LogUtil.CLog;
27 import com.android.tradefed.targetprep.TargetSetupError;
28 import com.android.tradefed.util.CommandResult;
29 import com.android.tradefed.util.CommandStatus;
30 import com.android.tradefed.util.FileUtil;
31 import com.android.tradefed.util.IRunUtil;
32 import com.android.tradefed.util.RunUtil;
33 import com.android.tradefed.util.VtsVendorConfigFileUtil;
34 
35 import java.io.File;
36 import java.io.FileNotFoundException;
37 import java.io.IOException;
38 /**
39  * Preparer class for sanitizer and gcov coverage.
40  *
41  * <p>For devices instrumented with sanitizer coverage, this preparer fetches the unstripped
42  * binaries and copies them to a temporary directory whose path is passed down with the test
43  * IBuildInfo object. For gcov-instrumented devices, the zip containing gcno files is retrieved
44  * along with the build info artifact.
45  */
46 @OptionClass(alias = "vts-coverage-preparer")
47 public class VtsCoveragePreparer implements ITargetPreparer, ITargetCleaner {
48     static final long BASE_TIMEOUT = 1000 * 60 * 20; // timeout for fetching artifacts
49     static final String BUILD_INFO_ARTIFACT = "BUILD_INFO"; // name of build info artifact
50     static final String GCOV_PROPERTY = "ro.vts.coverage"; // indicates gcov when val is 1
51     static final String GCOV_ARTIFACT = "%s-coverage-%s.zip"; // gcov coverage artifact
52     static final String GCOV_FILE_NAME = "gcov.zip"; // gcov zip file to pass to VTS
53 
54     static final String SELINUX_DISABLED = "Disabled"; // selinux disabled
55     static final String SELINUX_ENFORCING = "Enforcing"; // selinux enforcing mode
56     static final String SELINUX_PERMISSIVE = "Permissive"; // selinux permissive mode
57 
58     static final String SYMBOLS_ARTIFACT = "%s-symbols-%s.zip"; // symbolized binary zip
59     static final String SYMBOLS_FILE_NAME = "symbols.zip"; // sancov zip to pass to VTS
60     static final String SANCOV_FLAVOR = "_asan_coverage"; // sancov device build flavor
61 
62     // Path to store coverage data on the target.
63     static final String COVERAGE_DATA_PATH = "/data/misc/trace/";
64     // Path to store coverage report.
65     static final String COVERAGE_REPORT_PATH = "coverage_report_path";
66 
67     // Build key for gcov resources
68     static final String GCOV_RESOURCES_KEY = "gcov-resources-path-%s";
69 
70     // Buid key for sancov resources
71     static final String SANCOV_RESOURCES_KEY = "sancov-resources-path-%s";
72 
73     // Relative path to coverage configure binary in VTS package
74     static final String COVERAGE_CONFIGURE_SRC = "DATA/bin/vts_coverage_configure";
75 
76     // Target path for coverage configure binary, will be removed in teardown
77     static final String COVERAGE_CONFIGURE_DST = "/data/local/tmp/vts_coverage_configure";
78 
79     // Default path to store coverage reports.
80     static final String DEFAULT_COVRAGE_REPORT_PATH = "/tmp/vts-coverage-report/";
81 
82     // Default path to store coverage resource files locally.
83     static final String DEFAULT_LOCAL_COVERAGE_RESOURCE_PATH = "/tmp/coverage/";
84 
85     private File mDeviceInfoPath = null; // host path where coverage device artifacts are stored
86     private String mEnforcingState = null; // start state for selinux enforcement
87     private IRunUtil mRunUtil = null;
88 
89     @Option(name = "use-local-artifects", description = "Whether to use local artifacts.")
90     private boolean mUseLocalArtifects = false;
91 
92     @Option(name = "local-coverage-resource-path",
93             description = "Path to look for local artifacts.")
94     private String mLocalCoverageResourcePath = DEFAULT_LOCAL_COVERAGE_RESOURCE_PATH;
95 
96     @Option(name = "coverage-report-dir", description = "Local directory to store coverage report.")
97     private String mCoverageReportDir = null;
98 
99     /** {@inheritDoc} */
100     @Override
setUp(ITestDevice device, IBuildInfo buildInfo)101     public void setUp(ITestDevice device, IBuildInfo buildInfo)
102             throws DeviceNotAvailableException, TargetSetupError {
103         String flavor = device.getBuildFlavor();
104         String buildId = device.getBuildId();
105 
106         if (buildId == null) {
107             CLog.w("Missing build ID. Coverage disabled.");
108             return;
109         }
110 
111         boolean sancovEnabled = (flavor != null) && flavor.contains(SANCOV_FLAVOR);
112 
113         String coverageProperty = device.getProperty(GCOV_PROPERTY);
114         boolean gcovEnabled = (coverageProperty != null) && coverageProperty.equals("1");
115 
116         if (!sancovEnabled && !gcovEnabled) {
117             CLog.d("Coverage disabled.");
118             return;
119         }
120         if (sancovEnabled) {
121             CLog.d("Sanitizer coverage processing enabled.");
122         }
123         if (gcovEnabled) {
124             CLog.d("Gcov coverage processing enabled.");
125         }
126 
127         if (mRunUtil == null) {
128             mRunUtil = new RunUtil();
129         }
130 
131         CompatibilityBuildHelper buildHelper = createBuildHelper(buildInfo);
132         if (!mUseLocalArtifects) {
133             // Load the vendor configuration
134             String artifactFetcher = getArtifactFetcher(buildInfo);
135             if (artifactFetcher == null) {
136                 throw new TargetSetupError(
137                         "Vendor configuration with build_artifact_fetcher required.");
138             }
139 
140             try {
141                 // Create a temporary coverage directory
142                 mDeviceInfoPath = createTempDir(device);
143             } catch (IOException e) {
144                 cleanupCoverageData(device);
145                 throw new TargetSetupError(
146                         "Failed to create temp dir to store coverage resource files.");
147             }
148 
149             if (sancovEnabled) {
150                 // Fetch the symbolized binaries
151                 String artifactName = String.format(
152                         SYMBOLS_ARTIFACT, flavor.substring(0, flavor.lastIndexOf("-")), buildId);
153                 File artifactFile = new File(mDeviceInfoPath, SYMBOLS_FILE_NAME);
154 
155                 String cmdString = String.format(artifactFetcher, buildId, flavor, artifactName,
156                         artifactFile.getAbsolutePath().toString());
157                 String[] cmd = cmdString.split("\\s+");
158                 CommandResult commandResult = mRunUtil.runTimedCmd(BASE_TIMEOUT, cmd);
159                 if (commandResult == null || commandResult.getStatus() != CommandStatus.SUCCESS
160                         || !artifactFile.exists()) {
161                     cleanupCoverageData(device);
162                     throw new TargetSetupError("Could not fetch unstripped binaries.");
163                 }
164             }
165 
166             if (gcovEnabled) {
167                 // Fetch the symbolized binaries
168                 String artifactName = String.format(
169                         GCOV_ARTIFACT, flavor.substring(0, flavor.lastIndexOf("-")), buildId);
170                 File artifactFile = new File(mDeviceInfoPath, GCOV_FILE_NAME);
171 
172                 String cmdString = String.format(artifactFetcher, buildId, flavor, artifactName,
173                         artifactFile.getAbsolutePath().toString());
174                 String[] cmd = cmdString.split("\\s+");
175                 CommandResult commandResult = mRunUtil.runTimedCmd(BASE_TIMEOUT, cmd);
176                 if (commandResult == null || commandResult.getStatus() != CommandStatus.SUCCESS
177                         || !artifactFile.exists()) {
178                     cleanupCoverageData(device);
179                     throw new TargetSetupError("Could not fetch gcov build artifacts.");
180                 }
181             }
182 
183             // Fetch the device build information file
184             String cmdString = String.format(artifactFetcher, buildId, flavor, BUILD_INFO_ARTIFACT,
185                     mDeviceInfoPath.getAbsolutePath().toString());
186             String[] cmd = cmdString.split("\\s+");
187             CommandResult commandResult = mRunUtil.runTimedCmd(BASE_TIMEOUT, cmd);
188             File artifactFile = new File(mDeviceInfoPath, BUILD_INFO_ARTIFACT);
189             if (commandResult == null || commandResult.getStatus() != CommandStatus.SUCCESS
190                     || !artifactFile.exists()) {
191                 cleanupCoverageData(device);
192                 throw new TargetSetupError("Could not fetch build info.");
193             }
194         } else {
195             mDeviceInfoPath = new File(mLocalCoverageResourcePath);
196             String fileName = sancovEnabled ? SYMBOLS_FILE_NAME : GCOV_FILE_NAME;
197             File artifactFile = new File(mDeviceInfoPath, fileName);
198             if (!artifactFile.exists()) {
199                 cleanupCoverageData(device);
200                 throw new TargetSetupError(String.format("Could not find %s under %s.",
201                         GCOV_FILE_NAME, mDeviceInfoPath.getAbsolutePath()));
202             }
203             File buildInfoArtifact = new File(mDeviceInfoPath, BUILD_INFO_ARTIFACT);
204             if (!buildInfoArtifact.exists()) {
205                 cleanupCoverageData(device);
206                 throw new TargetSetupError(String.format("Could not find %s under %s.",
207                         BUILD_INFO_ARTIFACT, mDeviceInfoPath.getAbsolutePath()));
208             }
209         }
210 
211         try {
212             // Push the coverage configure tool
213             File configureSrc = new File(buildHelper.getTestsDir(), COVERAGE_CONFIGURE_SRC);
214             device.pushFile(configureSrc, COVERAGE_CONFIGURE_DST);
215         } catch (FileNotFoundException e) {
216             cleanupCoverageData(device);
217             throw new TargetSetupError("Failed to push the vts coverage configure tool.");
218         }
219 
220         if (mCoverageReportDir != null) {
221             try {
222                 File resultDir = buildHelper.getResultDir();
223                 File coverageDir = new File(resultDir, mCoverageReportDir);
224                 buildInfo.addBuildAttribute(COVERAGE_REPORT_PATH, coverageDir.getAbsolutePath());
225             } catch (FileNotFoundException e) {
226                 cleanupCoverageData(device);
227                 throw new TargetSetupError("Failed to get coverageDir.");
228             }
229         }
230 
231         device.executeShellCommand(String.format("rm -rf %s/*", COVERAGE_DATA_PATH));
232         mEnforcingState = device.executeShellCommand("getenforce");
233         if (!mEnforcingState.equals(SELINUX_DISABLED)
234                 && !mEnforcingState.equals(SELINUX_PERMISSIVE)) {
235             device.executeShellCommand("setenforce " + SELINUX_PERMISSIVE);
236         }
237 
238         if (sancovEnabled) {
239             buildInfo.setFile(
240                     getSancovResourceDirKey(device), mDeviceInfoPath, buildInfo.getBuildId());
241         }
242 
243         if (gcovEnabled) {
244             buildInfo.setFile(
245                     getGcovResourceDirKey(device), mDeviceInfoPath, buildInfo.getBuildId());
246         }
247     }
248 
249     /** {@inheritDoc} */
250     @Override
tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)251     public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
252             throws DeviceNotAvailableException {
253         if (mEnforcingState != null && !mEnforcingState.equals(SELINUX_DISABLED)) {
254             device.executeShellCommand("setenforce " + mEnforcingState);
255         }
256         cleanupCoverageData(device);
257     }
258 
259     /**
260      * Get the key of the symbolized binary directory for the specified device.
261      *
262      * @param device the target device whose sancov resources directory to get.
263      * @return the (String) key name of the device's sancov resources directory.
264      */
getSancovResourceDirKey(ITestDevice device)265     public static String getSancovResourceDirKey(ITestDevice device) {
266         return String.format(SANCOV_RESOURCES_KEY, device.getSerialNumber());
267     }
268 
269     /**
270      * Get the key of the gcov resources for the specified device.
271      *
272      * @param device the target device whose sancov resources directory to get.
273      * @return the (String) key name of the device's gcov resources directory.
274      */
getGcovResourceDirKey(ITestDevice device)275     public static String getGcovResourceDirKey(ITestDevice device) {
276         return String.format(GCOV_RESOURCES_KEY, device.getSerialNumber());
277     }
278 
279     /**
280      * Cleanup the coverage data on target and host.
281      *
282      * @param device the target device.
283      */
cleanupCoverageData(ITestDevice device)284     private void cleanupCoverageData(ITestDevice device) throws DeviceNotAvailableException {
285         if (mDeviceInfoPath != null) {
286             FileUtil.recursiveDelete(mDeviceInfoPath);
287         }
288         device.executeShellCommand("rm -rf " + COVERAGE_CONFIGURE_DST);
289         device.executeShellCommand(String.format("rm -rf %s/*", COVERAGE_DATA_PATH));
290     }
291 
292     @VisibleForTesting
createTempDir(ITestDevice device)293     File createTempDir(ITestDevice device) throws IOException {
294         return FileUtil.createTempDir(device.getSerialNumber());
295     }
296 
297     @VisibleForTesting
getArtifactFetcher(IBuildInfo buildInfo)298     String getArtifactFetcher(IBuildInfo buildInfo) {
299         VtsVendorConfigFileUtil configFileUtil = new VtsVendorConfigFileUtil();
300         if (configFileUtil.LoadVendorConfig(buildInfo)) {
301             return configFileUtil.GetVendorConfigVariable("build_artifact_fetcher");
302         }
303         return null;
304     }
305 
306     /**
307      * Create and return a {@link CompatibilityBuildHelper} to use during the preparer.
308      */
309     @VisibleForTesting
createBuildHelper(IBuildInfo buildInfo)310     CompatibilityBuildHelper createBuildHelper(IBuildInfo buildInfo) {
311         return new CompatibilityBuildHelper(buildInfo);
312     }
313 }
314