| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.tradefed.result; |
| |
| import com.android.ddmlib.IDevice; |
| import com.android.tradefed.build.IBuildInfo; |
| import com.android.tradefed.config.Option; |
| import com.android.tradefed.config.OptionClass; |
| import com.android.tradefed.log.LogUtil.CLog; |
| import com.android.tradefed.testtype.CodeCoverageTest; |
| import com.android.tradefed.util.CommandResult; |
| import com.android.tradefed.util.CommandStatus; |
| import com.android.tradefed.util.FileUtil; |
| import com.android.tradefed.util.IRunUtil; |
| import com.android.tradefed.util.RunUtil; |
| import com.android.tradefed.util.ZipUtil; |
| |
| import junit.framework.Assert; |
| import junit.framework.AssertionFailedError; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.zip.ZipFile; |
| |
| /** |
| * A {@link ITestInvocationListener} that will generate code coverage reports. |
| * <p/> |
| * Used in conjunction with {@link CodeCoverageTest}. This assumes that emmalib.jar |
| * is in same filesystem location as ddmlib jar. |
| */ |
| @OptionClass(alias = "code-coverage-reporter") |
| public class CodeCoverageReporter extends StubTestInvocationListener { |
| @Option(name = "coverage-metadata-file-path", description = |
| "The path of the Emma coverage meta data file used to generate the report.") |
| private String mCoverageMetaFilePath = null; |
| |
| @Option(name = "coverage-output-path", description = |
| "The location where to store the html coverage reports.", |
| mandatory = true) |
| private String mReportRootPath = null; |
| |
| @Option(name = "coverage-metadata-label", description = |
| "The label of the Emma coverage meta data zip file inside the IBuildInfo.") |
| private String mCoverageMetaZipFileName = "emma_meta.zip"; |
| |
| @Option(name = "log-retention-days", description = |
| "The number of days to keep generated coverage files") |
| private Integer mLogRetentionDays = null; |
| |
| static private int REPORT_GENERATION_TIMEOUT_MS = 3 * 60 * 1000; |
| |
| static public String XML_REPORT_NAME = "report.xml"; |
| |
| private IBuildInfo mBuildInfo; |
| private LogFileSaver mLogFileSaver; |
| |
| private File mLocalTmpDir = null; |
| private List<File> mCoverageFilesList = new ArrayList<File>(); |
| private File mCoverageMetaFile = null; |
| private File mXMLReportFile = null; |
| private File mReportOutputPath = null; |
| |
| public void setMetaZipFilePath(String filePath) { |
| mCoverageMetaFilePath = filePath; |
| } |
| |
| public void setReportRootPath(String rootPath) { |
| mReportRootPath = rootPath; |
| } |
| |
| public void setMetaZipFileName(String filename) { |
| mCoverageMetaZipFileName = filename; |
| } |
| |
| public void setLogRetentionDays(Integer logRetentionDays) { |
| mLogRetentionDays = logRetentionDays; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) { |
| if (LogDataType.COVERAGE.equals(dataType)) { |
| File coverageFile = saveLogAsFile(dataName, dataType, dataStream); |
| mCoverageFilesList.add(coverageFile); |
| CLog.d("Saved a new device coverage file saved at %s", coverageFile.getAbsolutePath()); |
| } |
| } |
| |
| private File saveLogAsFile(String dataName, LogDataType dataType, |
| InputStreamSource dataStream) { |
| try { |
| File logFile = mLogFileSaver.saveLogData(dataName, dataType, |
| dataStream.createInputStream()); |
| return logFile; |
| } catch (IOException e) { |
| CLog.e(e); |
| } |
| return null; |
| } |
| |
| public File getXMLReportFile() { |
| return mXMLReportFile; |
| } |
| |
| public File getReportOutputPath() { |
| return mReportOutputPath; |
| } |
| |
| public File getHTMLReportFile() { |
| return new File(mReportOutputPath, "index.html"); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void invocationStarted(IBuildInfo buildInfo) { |
| mBuildInfo = buildInfo; |
| |
| // Append build and branch information to output directory. |
| mReportOutputPath = generateReportLocation(mReportRootPath); |
| CLog.d("ReportOutputPath: %s", mReportOutputPath); |
| |
| mXMLReportFile = new File(mReportOutputPath, XML_REPORT_NAME); |
| CLog.d("ReportOutputPath: %s", mXMLReportFile); |
| |
| // We want to save all other files in the same directory as the report. |
| mLogFileSaver = new LogFileSaver(mReportOutputPath); |
| |
| CLog.d("ReportOutputPath %s", mReportOutputPath.getAbsolutePath()); |
| CLog.d("LogfileSaver file dir %s", mLogFileSaver.getFileDir().getAbsolutePath()); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void invocationEnded(long elapsedTime) { |
| // Generate report |
| generateReport(); |
| } |
| |
| public void generateReport() { |
| CLog.d("Generating report for code coverage"); |
| try { |
| fetchAppropriateMetaDataFile(); |
| |
| if (!mCoverageFilesList.isEmpty()) { |
| generateCoverageReport(mCoverageFilesList, mCoverageMetaFile); |
| } else { |
| CLog.w("No coverage files were generated by the test. " + |
| "Perhaps test failed to run successfully."); |
| } |
| } finally { |
| // Cleanup residual files. |
| if (!mCoverageFilesList.isEmpty()) { |
| for (File coverageFile : mCoverageFilesList) { |
| FileUtil.recursiveDelete(coverageFile); |
| } |
| } |
| if (mLocalTmpDir != null) { |
| FileUtil.recursiveDelete(mLocalTmpDir); |
| |
| } |
| } |
| } |
| |
| private void fetchAppropriateMetaDataFile() { |
| File coverageZipFile = mBuildInfo.getFile(mCoverageMetaZipFileName); |
| Assert.assertNotNull("Failed to get the coverage metadata zipfile from the build.", |
| coverageZipFile); |
| CLog.d("Coverage zip file: %s", coverageZipFile.getAbsolutePath()); |
| |
| try { |
| mLocalTmpDir = FileUtil.createTempDir("emma-meta"); |
| ZipFile zipFile = new ZipFile(coverageZipFile); |
| ZipUtil.extractZip(zipFile, mLocalTmpDir); |
| File coverageMetaFile; |
| if (mCoverageMetaFilePath == null) { |
| coverageMetaFile = FileUtil.findFile(mLocalTmpDir, "coverage.em"); |
| } else { |
| coverageMetaFile = new File(mLocalTmpDir, mCoverageMetaFilePath); |
| } |
| if (coverageMetaFile.exists()) { |
| mCoverageMetaFile = coverageMetaFile; |
| CLog.d("Coverage meta data file %s", mCoverageMetaFile.getAbsolutePath()); |
| } |
| } catch (IOException e) { |
| CLog.e(e); |
| } |
| } |
| |
| private File generateReportLocation(String rootPath) { |
| String branchName = mBuildInfo.getBuildBranch(); |
| String buildId = mBuildInfo.getBuildId(); |
| String testTag = mBuildInfo.getTestTag(); |
| File branchPath = new File(rootPath, branchName); |
| File buildIdPath = new File(branchPath, buildId); |
| File testTagPath = new File(buildIdPath, testTag); |
| FileUtil.mkdirsRWX(testTagPath); |
| if (mLogRetentionDays != null) { |
| RetentionFileSaver f = new RetentionFileSaver(); |
| f.writeRetentionFile(testTagPath, mLogRetentionDays); |
| } |
| return testTagPath; |
| } |
| |
| private void generateCoverageReport(List<File> coverageFileList, File metaFile) { |
| Assert.assertFalse("Could not find a valid coverage file.", coverageFileList.isEmpty()); |
| Assert.assertNotNull("Could not find a valid meta data coverage file.", metaFile); |
| String emmaPath = findEmmaJarPath(); |
| List<String> cmdList = new ArrayList<String>(); |
| cmdList.addAll(Arrays.asList("java", "-cp", emmaPath, "emma", "report", "-r", "html", |
| "-r", "xml", "-in", metaFile.getAbsolutePath(), "-Dreport.html.out.encoding=UTF-8", |
| "-Dreport.html.out.file=" + mReportOutputPath.getAbsolutePath() + "/index.html", |
| "-Dreport.xml.out.file=" + mReportOutputPath.getAbsolutePath() + "/report.xml")); |
| // Now append all the coverage files collected. |
| for (File coverageFile : coverageFileList) { |
| cmdList.add("-in"); |
| cmdList.add(coverageFile.getAbsolutePath()); |
| } |
| String[] cmd = cmdList.toArray(new String[cmdList.size()]); |
| IRunUtil runUtil = RunUtil.getDefault(); |
| CommandResult result = runUtil.runTimedCmd(REPORT_GENERATION_TIMEOUT_MS, cmd); |
| if (!result.getStatus().equals(CommandStatus.SUCCESS)) { |
| CLog.e("Failed to generate coverage report. stderr: %s", |
| result.getStderr()); |
| } else { |
| // Make the report world readable. |
| boolean setPerms = FileUtil.chmodRWXRecursively(mReportOutputPath); |
| if (!setPerms) { |
| CLog.w("Failed to set %s to be world accessible.", |
| mReportOutputPath.getAbsolutePath()); |
| } |
| } |
| } |
| |
| /** |
| * Tries to find emma.jar in same location as ddmlib.jar. |
| * |
| * @return full path to emma jar file |
| * @throws AssertionFailedError if could not find emma jar |
| */ |
| String findEmmaJarPath() { |
| String ddmlibPath = IDevice.class.getProtectionDomain() |
| .getCodeSource() |
| .getLocation() |
| .getFile(); |
| Assert.assertFalse("failed to find ddmlib path", ddmlibPath.isEmpty()); |
| File parentFolder = new File(ddmlibPath); |
| File emmaJar = new File(parentFolder.getParent(), "emmalib.jar"); |
| Assert.assertTrue( |
| String.format("Failed to find emma.jar in %s", emmaJar.getAbsolutePath()), |
| emmaJar.exists()); |
| CLog.d("Found emma jar at %s", emmaJar.getAbsolutePath()); |
| return emmaJar.getAbsolutePath(); |
| } |
| } |