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