1 /* 2 * Copyright (C) 2012 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.result; 18 19 import com.android.ddmlib.IDevice; 20 import com.android.tradefed.build.IBuildInfo; 21 import com.android.tradefed.config.Option; 22 import com.android.tradefed.config.OptionClass; 23 import com.android.tradefed.invoker.IInvocationContext; 24 import com.android.tradefed.log.LogUtil.CLog; 25 import com.android.tradefed.testtype.CodeCoverageTest; 26 import com.android.tradefed.util.CommandResult; 27 import com.android.tradefed.util.CommandStatus; 28 import com.android.tradefed.util.FileUtil; 29 import com.android.tradefed.util.IRunUtil; 30 import com.android.tradefed.util.RunUtil; 31 import com.android.tradefed.util.ZipUtil2; 32 33 import org.apache.commons.compress.archivers.zip.ZipFile; 34 import org.junit.Assert; 35 36 import java.io.File; 37 import java.io.IOException; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.List; 41 42 /** 43 * A {@link ITestInvocationListener} that will generate code coverage reports. 44 * <p/> 45 * Used in conjunction with {@link CodeCoverageTest}. This assumes that emmalib.jar 46 * is in same filesystem location as ddmlib jar. 47 */ 48 @OptionClass(alias = "code-coverage-reporter") 49 public class CodeCoverageReporter implements ITestInvocationListener { 50 @Option(name = "coverage-metadata-file-path", description = 51 "The path of the Emma coverage meta data file used to generate the report.") 52 private String mCoverageMetaFilePath = null; 53 54 @Option(name = "coverage-output-path", description = 55 "The location where to store the html coverage reports.", 56 mandatory = true) 57 private String mReportRootPath = null; 58 59 @Option(name = "coverage-metadata-label", description = 60 "The label of the Emma coverage meta data zip file inside the IBuildInfo.") 61 private String mCoverageMetaZipFileName = "emma_meta.zip"; 62 63 @Option(name = "log-retention-days", description = 64 "The number of days to keep generated coverage files") 65 private Integer mLogRetentionDays = null; 66 67 private static final int REPORT_GENERATION_TIMEOUT_MS = 3 * 60 * 1000; 68 69 public static final String XML_REPORT_NAME = "report.xml"; 70 71 private IBuildInfo mBuildInfo; 72 private LogFileSaver mLogFileSaver; 73 74 private File mLocalTmpDir = null; 75 private List<File> mCoverageFilesList = new ArrayList<File>(); 76 private File mCoverageMetaFile = null; 77 private File mXMLReportFile = null; 78 private File mReportOutputPath = null; 79 80 public void setMetaZipFilePath(String filePath) { 81 mCoverageMetaFilePath = filePath; 82 } 83 84 public void setReportRootPath(String rootPath) { 85 mReportRootPath = rootPath; 86 } 87 88 public void setMetaZipFileName(String filename) { 89 mCoverageMetaZipFileName = filename; 90 } 91 92 public void setLogRetentionDays(Integer logRetentionDays) { 93 mLogRetentionDays = logRetentionDays; 94 } 95 96 /** 97 * {@inheritDoc} 98 */ 99 @Override 100 public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) { 101 if (LogDataType.COVERAGE.equals(dataType)) { 102 File coverageFile = saveLogAsFile(dataName, dataType, dataStream); 103 mCoverageFilesList.add(coverageFile); 104 CLog.d("Saved a new device coverage file saved at %s", coverageFile.getAbsolutePath()); 105 } 106 } 107 108 private File saveLogAsFile(String dataName, LogDataType dataType, 109 InputStreamSource dataStream) { 110 try { 111 File logFile = mLogFileSaver.saveLogData(dataName, dataType, 112 dataStream.createInputStream()); 113 return logFile; 114 } catch (IOException e) { 115 CLog.e(e); 116 } 117 return null; 118 } 119 120 public File getXMLReportFile() { 121 return mXMLReportFile; 122 } 123 124 public File getReportOutputPath() { 125 return mReportOutputPath; 126 } 127 128 public File getHTMLReportFile() { 129 return new File(mReportOutputPath, "index.html"); 130 } 131 132 /** {@inheritDoc} */ 133 @Override 134 public void invocationStarted(IInvocationContext context) { 135 // FIXME: do code coverage reporting for each different build info (for multi-device case) 136 mBuildInfo = context.getBuildInfos().get(0); 137 138 // Append build and branch information to output directory. 139 mReportOutputPath = generateReportLocation(mReportRootPath); 140 CLog.d("ReportOutputPath: %s", mReportOutputPath); 141 142 mXMLReportFile = new File(mReportOutputPath, XML_REPORT_NAME); 143 CLog.d("ReportOutputPath: %s", mXMLReportFile); 144 145 // We want to save all other files in the same directory as the report. 146 mLogFileSaver = new LogFileSaver(mReportOutputPath); 147 148 CLog.d("ReportOutputPath %s", mReportOutputPath.getAbsolutePath()); 149 CLog.d("LogfileSaver file dir %s", mLogFileSaver.getFileDir().getAbsolutePath()); 150 } 151 152 /** 153 * {@inheritDoc} 154 */ 155 @Override 156 public void invocationEnded(long elapsedTime) { 157 // Generate report 158 generateReport(); 159 } 160 161 public void generateReport() { 162 CLog.d("Generating report for code coverage"); 163 try { 164 fetchAppropriateMetaDataFile(); 165 166 if (!mCoverageFilesList.isEmpty()) { 167 generateCoverageReport(mCoverageFilesList, mCoverageMetaFile); 168 } else { 169 CLog.w("No coverage files were generated by the test. " + 170 "Perhaps test failed to run successfully."); 171 } 172 } finally { 173 // Cleanup residual files. 174 if (!mCoverageFilesList.isEmpty()) { 175 for (File coverageFile : mCoverageFilesList) { 176 FileUtil.recursiveDelete(coverageFile); 177 } 178 } 179 if (mLocalTmpDir != null) { 180 FileUtil.recursiveDelete(mLocalTmpDir); 181 182 } 183 } 184 } 185 186 private void fetchAppropriateMetaDataFile() { 187 File coverageZipFile = mBuildInfo.getFile(mCoverageMetaZipFileName); 188 Assert.assertNotNull("Failed to get the coverage metadata zipfile from the build.", 189 coverageZipFile); 190 CLog.d("Coverage zip file: %s", coverageZipFile.getAbsolutePath()); 191 192 try { 193 mLocalTmpDir = FileUtil.createTempDir("emma-meta"); 194 ZipFile zipFile = new ZipFile(coverageZipFile); 195 ZipUtil2.extractZip(zipFile, mLocalTmpDir); 196 File coverageMetaFile; 197 if (mCoverageMetaFilePath == null) { 198 coverageMetaFile = FileUtil.findFile(mLocalTmpDir, "coverage.em"); 199 } else { 200 coverageMetaFile = new File(mLocalTmpDir, mCoverageMetaFilePath); 201 } 202 if (coverageMetaFile.exists()) { 203 mCoverageMetaFile = coverageMetaFile; 204 CLog.d("Coverage meta data file %s", mCoverageMetaFile.getAbsolutePath()); 205 } 206 } catch (IOException e) { 207 CLog.e(e); 208 } 209 } 210 211 private File generateReportLocation(String rootPath) { 212 String branchName = mBuildInfo.getBuildBranch(); 213 String buildId = mBuildInfo.getBuildId(); 214 String testTag = mBuildInfo.getTestTag(); 215 File branchPath = new File(rootPath, branchName); 216 File buildIdPath = new File(branchPath, buildId); 217 File testTagPath = new File(buildIdPath, testTag); 218 FileUtil.mkdirsRWX(testTagPath); 219 if (mLogRetentionDays != null) { 220 RetentionFileSaver f = new RetentionFileSaver(); 221 f.writeRetentionFile(testTagPath, mLogRetentionDays); 222 } 223 return testTagPath; 224 } 225 226 private void generateCoverageReport(List<File> coverageFileList, File metaFile) { 227 Assert.assertFalse("Could not find a valid coverage file.", coverageFileList.isEmpty()); 228 Assert.assertNotNull("Could not find a valid meta data coverage file.", metaFile); 229 String emmaPath = findEmmaJarPath(); 230 List<String> cmdList = new ArrayList<String>(); 231 cmdList.addAll(Arrays.asList("java", "-cp", emmaPath, "emma", "report", "-r", "html", 232 "-r", "xml", "-in", metaFile.getAbsolutePath(), "-Dreport.html.out.encoding=UTF-8", 233 "-Dreport.html.out.file=" + mReportOutputPath.getAbsolutePath() + "/index.html", 234 "-Dreport.xml.out.file=" + mReportOutputPath.getAbsolutePath() + "/report.xml")); 235 // Now append all the coverage files collected. 236 for (File coverageFile : coverageFileList) { 237 cmdList.add("-in"); 238 cmdList.add(coverageFile.getAbsolutePath()); 239 } 240 String[] cmd = cmdList.toArray(new String[cmdList.size()]); 241 IRunUtil runUtil = RunUtil.getDefault(); 242 CommandResult result = runUtil.runTimedCmd(REPORT_GENERATION_TIMEOUT_MS, cmd); 243 if (!result.getStatus().equals(CommandStatus.SUCCESS)) { 244 CLog.e("Failed to generate coverage report. stderr: %s", 245 result.getStderr()); 246 } else { 247 // Make the report world readable. 248 boolean setPerms = FileUtil.chmodRWXRecursively(mReportOutputPath); 249 if (!setPerms) { 250 CLog.w("Failed to set %s to be world accessible.", 251 mReportOutputPath.getAbsolutePath()); 252 } 253 } 254 } 255 256 /** 257 * Tries to find emma.jar in same location as ddmlib.jar. 258 * 259 * @return full path to emma jar file 260 * @throws AssertionError if could not find emma jar 261 */ 262 String findEmmaJarPath() { 263 String ddmlibPath = IDevice.class.getProtectionDomain() 264 .getCodeSource() 265 .getLocation() 266 .getFile(); 267 Assert.assertFalse("failed to find ddmlib path", ddmlibPath.isEmpty()); 268 File parentFolder = new File(ddmlibPath); 269 File emmaJar = new File(parentFolder.getParent(), "emmalib.jar"); 270 Assert.assertTrue( 271 String.format("Failed to find emma.jar in %s", emmaJar.getAbsolutePath()), 272 emmaJar.exists()); 273 CLog.d("Found emma jar at %s", emmaJar.getAbsolutePath()); 274 return emmaJar.getAbsolutePath(); 275 } 276 } 277