Home | History | Annotate | Download | only in result
      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