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.testtype;
18 
19 import static com.google.common.base.Verify.verify;
20 import static com.google.common.base.Verify.verifyNotNull;
21 
22 import com.android.tradefed.device.DeviceNotAvailableException;
23 import com.android.tradefed.device.ITestDevice;
24 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
25 import com.android.tradefed.result.FileInputStreamSource;
26 import com.android.tradefed.result.ITestInvocationListener;
27 import com.android.tradefed.result.LogDataType;
28 import com.android.tradefed.result.ResultForwarder;
29 import com.android.tradefed.testtype.coverage.CoverageOptions;
30 import com.android.tradefed.util.FileUtil;
31 import com.android.tradefed.util.NativeCodeCoverageFlusher;
32 import com.android.tradefed.util.TarUtil;
33 import com.android.tradefed.util.ZipUtil;
34 
35 import com.google.common.collect.ImmutableList;
36 
37 import java.io.File;
38 import java.io.IOException;
39 import java.util.Arrays;
40 import java.util.HashMap;
41 
42 /**
43  * A {@link ResultForwarder} that will pull native coverage measurements off of the device and log
44  * them as test artifacts.
45  */
46 public final class NativeCodeCoverageListener extends ResultForwarder {
47 
48     private static final String NATIVE_COVERAGE_DEVICE_PATH = "/data/misc/trace";
49     private static final String COVERAGE_TAR_PATH =
50             String.format("%s/coverage.tar", NATIVE_COVERAGE_DEVICE_PATH);
51 
52     // Finds .gcda files in /data/misc/trace and compresses those files only. Stores the full
53     // path of the file on the device.
54     private static final String ZIP_COVERAGE_FILES_COMMAND =
55             String.format(
56                     "find %s -name '*.gcda' | tar -cvf %s -T -",
57                     NATIVE_COVERAGE_DEVICE_PATH, COVERAGE_TAR_PATH);
58 
59     // Deletes .gcda files in /data/misc/trace.
60     private static final String DELETE_COVERAGE_FILES_COMMAND =
61             String.format("find %s -name '*.gcda' -delete", NATIVE_COVERAGE_DEVICE_PATH);
62 
63     private final boolean mFlushCoverage;
64     private final ITestDevice mDevice;
65     private final NativeCodeCoverageFlusher mFlusher;
66     private boolean mCollectCoverageOnTestEnd = true;
67 
68     private String mCurrentRunName;
69 
NativeCodeCoverageListener(ITestDevice device, ITestInvocationListener... listeners)70     public NativeCodeCoverageListener(ITestDevice device, ITestInvocationListener... listeners) {
71         super(listeners);
72         mDevice = device;
73         mFlushCoverage = false;
74         mFlusher = new NativeCodeCoverageFlusher(mDevice, ImmutableList.of());
75     }
76 
NativeCodeCoverageListener( ITestDevice device, CoverageOptions coverageOptions, ITestInvocationListener... listeners)77     public NativeCodeCoverageListener(
78             ITestDevice device,
79             CoverageOptions coverageOptions,
80             ITestInvocationListener... listeners) {
81         super(listeners);
82         mDevice = device;
83         mFlushCoverage = coverageOptions.isCoverageFlushEnabled();
84         mFlusher = new NativeCodeCoverageFlusher(mDevice, coverageOptions.getCoverageProcesses());
85     }
86 
87     /**
88      * Sets whether to collect coverage on testRunEnded.
89      *
90      * <p>Set this to false during re-runs, otherwise each individual test re-run will collect
91      * coverage rather than having a single merged coverage result.
92      */
setCollectOnTestEnd(boolean collect)93     public void setCollectOnTestEnd(boolean collect) {
94         mCollectCoverageOnTestEnd = collect;
95     }
96 
97     @Override
testRunStarted(String runName, int testCount)98     public void testRunStarted(String runName, int testCount) {
99         super.testRunStarted(runName, testCount);
100         mCurrentRunName = runName;
101     }
102 
103     @Override
testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics)104     public void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
105         try {
106             if (mCollectCoverageOnTestEnd) {
107                 logCoverageMeasurements(mCurrentRunName);
108             }
109         } finally {
110             super.testRunEnded(elapsedTime, runMetrics);
111         }
112     }
113 
114     /** Pulls native coverage measurements from the device and logs them. */
logCoverageMeasurements(String runName)115     public void logCoverageMeasurements(String runName) {
116         File coverageTar = null;
117         File coverageZip = null;
118         try {
119             // Enable abd root on the device, otherwise the following commands will fail.
120             verify(mDevice.enableAdbRoot(), "Failed to enable adb root.");
121 
122             // Flush cross-process coverage.
123             if (mFlushCoverage) {
124                 mFlusher.forceCoverageFlush();
125             }
126 
127             // Compress coverage measurements on the device before pulling.
128             mDevice.executeShellCommand(ZIP_COVERAGE_FILES_COMMAND);
129             coverageTar = mDevice.pullFile(COVERAGE_TAR_PATH);
130             verifyNotNull(
131                     coverageTar,
132                     "Failed to pull the native code coverage file %s",
133                     COVERAGE_TAR_PATH);
134             mDevice.deleteFile(COVERAGE_TAR_PATH);
135 
136             coverageZip = convertTarToZip(coverageTar);
137 
138             try (FileInputStreamSource source = new FileInputStreamSource(coverageZip, true)) {
139                 testLog(runName + "_native_runtime_coverage", LogDataType.NATIVE_COVERAGE, source);
140             }
141 
142             // Delete coverage files on the device.
143             mDevice.executeShellCommand(DELETE_COVERAGE_FILES_COMMAND);
144         } catch (DeviceNotAvailableException | IOException e) {
145             throw new RuntimeException(e);
146         } finally {
147             FileUtil.deleteFile(coverageTar);
148             FileUtil.deleteFile(coverageZip);
149         }
150     }
151 
152     /**
153      * Converts a .tar file to a .zip file.
154      *
155      * @param tar the .tar file to convert
156      * @return a .zip file with the same contents
157      * @throws IOException
158      */
convertTarToZip(File tar)159     private File convertTarToZip(File tar) throws IOException {
160         File untarDir = null;
161         try {
162             untarDir = FileUtil.createTempDir("gcov_coverage");
163             TarUtil.unTar(tar, untarDir);
164             return ZipUtil.createZip(Arrays.asList(untarDir.listFiles()), "native_coverage");
165         } finally {
166             FileUtil.recursiveDelete(untarDir);
167         }
168     }
169 }
170