Home | History | Annotate | Download | only in reporter
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
      5  * except in compliance with the License. You may obtain a copy of the License at
      6  *
      7  *      http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the
     10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     11  * KIND, either express or implied. See the License for the specific language governing
     12  * permissions and limitations under the License.
     13  */
     14 package com.google.android.jacoco.reporter;
     15 
     16 import org.apache.commons.cli.CommandLine;
     17 import org.apache.commons.cli.HelpFormatter;
     18 import org.apache.commons.cli.Option;
     19 import org.apache.commons.cli.Options;
     20 import org.apache.commons.cli.ParseException;
     21 import org.apache.commons.cli.PosixParser;
     22 import org.jacoco.core.analysis.Analyzer;
     23 import org.jacoco.core.analysis.CoverageBuilder;
     24 import org.jacoco.core.analysis.IBundleCoverage;
     25 import org.jacoco.core.data.ExecutionDataStore;
     26 import org.jacoco.core.tools.ExecFileLoader;
     27 import org.jacoco.report.DirectorySourceFileLocator;
     28 import org.jacoco.report.FileMultiReportOutput;
     29 import org.jacoco.report.IMultiReportOutput;
     30 import org.jacoco.report.IReportVisitor;
     31 import org.jacoco.report.MultiReportVisitor;
     32 import org.jacoco.report.MultiSourceFileLocator;
     33 import org.jacoco.report.html.HTMLFormatter;
     34 import org.jacoco.report.xml.XMLFormatter;
     35 import org.objectweb.asm.ClassReader;
     36 
     37 import java.io.File;
     38 import java.io.FileNotFoundException;
     39 import java.io.FileOutputStream;
     40 import java.io.IOException;
     41 import java.util.ArrayList;
     42 import java.util.List;
     43 
     44 public class ReportGenerator {
     45     private static final String OPT_CLASSPATH = "classpath";
     46     private static final String OPT_REPORT_NAME = "name";
     47     private static final String OPT_EXEC_FILE = "exec-file";
     48     private static final String OPT_SOURCES = "srcs";
     49     private static final String OPT_REPORT_DIR = "report-dir";
     50     private static final int TAB_WIDTH = 4;
     51 
     52     private final Config mConfig;
     53 
     54     private ReportGenerator(Config config) {
     55         mConfig = config;
     56     }
     57 
     58     private void execute() {
     59         ExecFileLoader execFileLoader = new ExecFileLoader();
     60         try {
     61             execFileLoader.load(mConfig.mExecFileDir);
     62             IReportVisitor reportVisitor = new MultiReportVisitor(getVisitors());
     63             reportVisitor.visitInfo(execFileLoader.getSessionInfoStore().getInfos(),
     64                     execFileLoader.getExecutionDataStore().getContents());
     65             MultiSourceFileLocator sourceFileLocator = new MultiSourceFileLocator(TAB_WIDTH);
     66             mConfig.mSourceDirs.stream().filter(File::isDirectory)
     67                     .map(sourceDir -> new DirectorySourceFileLocator(sourceDir, null, TAB_WIDTH))
     68                     .forEach(sourceFileLocator::add);
     69             reportVisitor.visitBundle(createBundle(execFileLoader.getExecutionDataStore()),
     70                     sourceFileLocator);
     71             reportVisitor.visitEnd();
     72         } catch (Exception e) {
     73             e.printStackTrace();
     74         }
     75     }
     76 
     77     private IBundleCoverage createBundle(ExecutionDataStore dataStore) throws IOException {
     78         CoverageBuilder coverageBuilder = new CoverageBuilder();
     79         Analyzer analyzer = new Analyzer(dataStore, coverageBuilder) {
     80             @Override
     81             public void analyzeClass(ClassReader reader) {
     82                 if (weHaveSourceFor(reader.getClassName())) {
     83                     super.analyzeClass(reader);
     84                 }
     85             }
     86 
     87             private boolean weHaveSourceFor(String asmClassName) {
     88                 String fileName = asmClassName.replaceFirst("\\$.*", "") + ".java";
     89                 return mConfig.mSourceDirs.stream().map(parent -> new File(parent, fileName))
     90                         .anyMatch(File::exists);
     91             }
     92         };
     93 
     94         for (File file : mConfig.mClasspath) {
     95             analyzer.analyzeAll(file);
     96         }
     97         return coverageBuilder.getBundle(mConfig.mReportName);
     98     }
     99 
    100     private List<IReportVisitor> getVisitors() throws Exception {
    101         List<IReportVisitor> visitors = new ArrayList<>();
    102         visitors.add(new XMLFormatter().createVisitor(mConfig.getXmlOutputStream()));
    103         visitors.add(new HTMLFormatter().createVisitor(mConfig.getHtmlReportOutput()));
    104         return visitors;
    105     }
    106 
    107     private static class Config {
    108         final String mReportName;
    109         final List<File> mClasspath;
    110         final List<File> mSourceDirs;
    111         final File mReportDir;
    112         final File mExecFileDir;
    113 
    114         Config(String reportName, List<File> classpath, List<File> sourceDirs,
    115                 File reportDir, File execFileDir) {
    116             mReportName = reportName;
    117             mClasspath = classpath;
    118             mSourceDirs = sourceDirs;
    119             mReportDir = reportDir;
    120             mExecFileDir = execFileDir;
    121         }
    122 
    123         FileOutputStream getXmlOutputStream() throws FileNotFoundException {
    124             File xmlFile = new File(mReportDir, "coverage.xml");
    125             return new FileOutputStream(xmlFile);
    126         }
    127 
    128         static Config from(CommandLine commandLine) {
    129             List<File> classpaths = parse(commandLine.getOptionValue(OPT_CLASSPATH),
    130                     "WARN: Classpath entry [%s] does not exist or is not a directory");
    131             List<File> sources = parse(commandLine.getOptionValue(OPT_SOURCES),
    132                     "WARN: Source entry [%s] does not exist or is not a directory");
    133             File execFileDir = new File(commandLine.getOptionValue(OPT_EXEC_FILE));
    134             ensure(execFileDir.exists() && execFileDir.canRead() && execFileDir.isFile(),
    135                     "execFile: [%s] does not exist or could not be read.", execFileDir);
    136 
    137             File reportDir = new File(commandLine.getOptionValue(OPT_REPORT_DIR));
    138             ensure(reportDir.exists() || reportDir.mkdirs(),
    139                     "Unable to create report dir [%s]", reportDir);
    140 
    141             return new Config(commandLine.getOptionValue(OPT_REPORT_NAME), classpaths, sources,
    142                     reportDir, execFileDir);
    143         }
    144 
    145         IMultiReportOutput getHtmlReportOutput() {
    146             return new FileMultiReportOutput(mReportDir);
    147         }
    148     }
    149 
    150     private static void ensure(boolean condition, String errorMessage, Object... args) {
    151         if (!condition) {
    152             System.err.printf(errorMessage, args);
    153             System.exit(0);
    154         }
    155     }
    156 
    157     private static List<File> parse(String value, String warningMessage) {
    158         List<File> files = new ArrayList<>(0);
    159         for (String classpath : value.split(System.getProperty("path.separator"))) {
    160             File file = new File(classpath);
    161             if (file.exists()) {
    162                 files.add(file);
    163             } else {
    164                 System.out.println(String.format(warningMessage, classpath));
    165             }
    166         }
    167         return files;
    168     }
    169 
    170     private static void printHelp(ParseException e, Options options) {
    171         System.out.println(e.getMessage());
    172         HelpFormatter helpFormatter = new HelpFormatter();
    173         helpFormatter.printHelp("java -cp ${report.jar} " + ReportGenerator.class.getName(),
    174                 "Generates jacoco reports in XML and HTML format.", options, "", true);
    175     }
    176 
    177     private static void addOption(Options options, String longName, String description) {
    178         Option option = new Option(null, longName, true, description);
    179         option.setArgs(1);
    180         option.setRequired(true);
    181         options.addOption(option);
    182     }
    183 
    184     public static void main(String[] args) {
    185         Options options = new Options();
    186         try {
    187             // TODO: Support multiple exec-files
    188             addOption(options, OPT_CLASSPATH, "Classpath used during testing");
    189             addOption(options, OPT_EXEC_FILE, "File generated by jacoco during testing");
    190             addOption(options, OPT_REPORT_NAME, "Name of the project tested");
    191             addOption(options, OPT_REPORT_DIR, "Directory into which reports will be generated");
    192             addOption(options, OPT_SOURCES, "List of source directories");
    193             CommandLine commandLine = new PosixParser().parse(options, args);
    194             new ReportGenerator(Config.from(commandLine))
    195                     .execute();
    196         } catch (ParseException e) {
    197             printHelp(e, options);
    198             System.exit(1);
    199         }
    200     }
    201 }
    202