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