1 /*
2  * Copyright (C) 2019 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.device.metric;
18 
19 import static com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain.GCOV;
20 
21 import static com.google.common.base.Verify.verifyNotNull;
22 
23 import com.android.tradefed.config.IConfiguration;
24 import com.android.tradefed.config.IConfigurationReceiver;
25 import com.android.tradefed.device.DeviceNotAvailableException;
26 import com.android.tradefed.device.ITestDevice;
27 import com.android.tradefed.invoker.IInvocationContext;
28 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
29 import com.android.tradefed.result.FileInputStreamSource;
30 import com.android.tradefed.result.ITestInvocationListener;
31 import com.android.tradefed.result.LogDataType;
32 import com.android.tradefed.util.AdbRootElevator;
33 import com.android.tradefed.util.FileUtil;
34 import com.android.tradefed.util.IRunUtil;
35 import com.android.tradefed.util.NativeCodeCoverageFlusher;
36 import com.android.tradefed.util.RunUtil;
37 import com.android.tradefed.util.TarUtil;
38 import com.android.tradefed.util.ZipUtil;
39 
40 import com.google.common.annotations.VisibleForTesting;
41 
42 import java.io.File;
43 import java.io.IOException;
44 import java.util.Arrays;
45 import java.util.Map;
46 
47 /**
48  * A {@link com.android.tradefed.device.metric.BaseDeviceMetricCollector} that will pull gcov
49  * coverage measurements off of the device and log them as test artifacts.
50  */
51 public final class GcovCodeCoverageCollector extends BaseDeviceMetricCollector
52         implements IConfigurationReceiver {
53 
54     private static final String NATIVE_COVERAGE_DEVICE_PATH = "/data/misc/trace";
55     private static final String COVERAGE_TAR_PATH =
56             String.format("%s/coverage.tar", NATIVE_COVERAGE_DEVICE_PATH);
57 
58     // Finds .gcda files in /data/misc/trace and compresses those files only. Stores the full
59     // path of the file on the device.
60     private static final String ZIP_COVERAGE_FILES_COMMAND =
61             String.format(
62                     "find %s -name '*.gcda' | tar -cvf %s -T -",
63                     NATIVE_COVERAGE_DEVICE_PATH, COVERAGE_TAR_PATH);
64 
65     // Deletes .gcda files in /data/misc/trace.
66     private static final String DELETE_COVERAGE_FILES_COMMAND =
67             String.format("find %s -name '*.gcda' -delete", NATIVE_COVERAGE_DEVICE_PATH);
68 
69     private NativeCodeCoverageFlusher mFlusher;
70     private boolean mCollectCoverageOnTestEnd = true;
71     private IConfiguration mConfiguration;
72     private IRunUtil mRunUtil = RunUtil.getDefault();
73 
74     @Override
extraInit(IInvocationContext context, ITestInvocationListener listener)75     public void extraInit(IInvocationContext context, ITestInvocationListener listener)
76             throws DeviceNotAvailableException {
77         super.extraInit(context, listener);
78 
79         if (isGcovCoverageEnabled()) {
80             for (ITestDevice device : getRealDevices()) {
81                 // Clear coverage measurements on the device.
82                 try (AdbRootElevator adbRoot = new AdbRootElevator(device)) {
83                     getCoverageFlusher(device).resetCoverage();
84                 }
85             }
86         }
87     }
88 
89     @VisibleForTesting
setRunUtil(IRunUtil runUtil)90     void setRunUtil(IRunUtil runUtil) {
91         mRunUtil = runUtil;
92         if (mFlusher != null) {
93             mFlusher.setRunUtil(runUtil);
94         }
95     }
96 
97     @Override
setConfiguration(IConfiguration config)98     public void setConfiguration(IConfiguration config) {
99         mConfiguration = config;
100     }
101 
isGcovCoverageEnabled()102     private boolean isGcovCoverageEnabled() {
103         return mConfiguration != null
104                 && mConfiguration.getCoverageOptions().isCoverageEnabled()
105                 && mConfiguration.getCoverageOptions().getCoverageToolchains().contains(GCOV);
106     }
107 
getCoverageFlusher(ITestDevice device)108     private NativeCodeCoverageFlusher getCoverageFlusher(ITestDevice device) {
109         if (mFlusher == null) {
110             mFlusher = new NativeCodeCoverageFlusher(device, mConfiguration.getCoverageOptions());
111             mFlusher.setRunUtil(mRunUtil);
112         }
113         return mFlusher;
114     }
115 
116     /**
117      * Sets whether to collect coverage on testRunEnded.
118      *
119      * <p>Set this to false during re-runs, otherwise each individual test re-run will collect
120      * coverage rather than having a single merged coverage result.
121      */
setCollectOnTestEnd(boolean collect)122     public void setCollectOnTestEnd(boolean collect) {
123         mCollectCoverageOnTestEnd = collect;
124     }
125 
126     @Override
onTestRunEnd(DeviceMetricData runData, final Map<String, Metric> runMetrics)127     public void onTestRunEnd(DeviceMetricData runData, final Map<String, Metric> runMetrics)
128             throws DeviceNotAvailableException {
129         if (!isGcovCoverageEnabled()) {
130             return;
131         }
132 
133         if (mCollectCoverageOnTestEnd) {
134             for (ITestDevice device : getRealDevices()) {
135                 logCoverageMeasurements(device, getRunName());
136             }
137         }
138     }
139 
140     /** Pulls native coverage measurements from the device and logs them. */
logCoverageMeasurements(ITestDevice device, String runName)141     public void logCoverageMeasurements(ITestDevice device, String runName)
142             throws DeviceNotAvailableException {
143         File coverageTar = null;
144         File coverageZip = null;
145 
146         // Enable abd root on the device, otherwise the following commands will fail.
147         try (AdbRootElevator adbRoot = new AdbRootElevator(device)) {
148             // Flush cross-process coverage.
149             if (mConfiguration.getCoverageOptions().isCoverageFlushEnabled()) {
150                 getCoverageFlusher(device).forceCoverageFlush();
151             }
152 
153             // Compress coverage measurements on the device before pulling.
154             device.executeShellCommand(ZIP_COVERAGE_FILES_COMMAND);
155             coverageTar = device.pullFile(COVERAGE_TAR_PATH);
156             verifyNotNull(
157                     coverageTar,
158                     "Failed to pull the native code coverage file %s",
159                     COVERAGE_TAR_PATH);
160             device.deleteFile(COVERAGE_TAR_PATH);
161 
162             coverageZip = convertTarToZip(coverageTar);
163 
164             try (FileInputStreamSource source = new FileInputStreamSource(coverageZip, true)) {
165                 testLog(runName + "_native_runtime_coverage", LogDataType.NATIVE_COVERAGE, source);
166             }
167 
168             // Delete coverage files on the device.
169             device.executeShellCommand(DELETE_COVERAGE_FILES_COMMAND);
170         } catch (IOException e) {
171             throw new RuntimeException(e);
172         } finally {
173             FileUtil.deleteFile(coverageTar);
174             FileUtil.deleteFile(coverageZip);
175         }
176     }
177 
178     /**
179      * Converts a .tar file to a .zip file.
180      *
181      * @param tar the .tar file to convert
182      * @return a .zip file with the same contents
183      * @throws IOException
184      */
convertTarToZip(File tar)185     private File convertTarToZip(File tar) throws IOException {
186         File untarDir = null;
187         try {
188             untarDir = FileUtil.createTempDir("gcov_coverage");
189             TarUtil.unTar(tar, untarDir);
190             return ZipUtil.createZip(Arrays.asList(untarDir.listFiles()), "native_coverage");
191         } finally {
192             FileUtil.recursiveDelete(untarDir);
193         }
194     }
195 }
196