Home | History | Annotate | Download | only in lcov
      1 /* Copyright 2009 Google Inc. All Rights Reserved.
      2  * Derived from code Copyright (C) 2003 Vladimir Roubtsov.
      3  *
      4  * This program and the accompanying materials are made available under
      5  * the terms of the Common Public License v1.0 which accompanies this
      6  * distribution, and is available at http://www.eclipse.org/legal/cpl-v10.html
      7  *
      8  * $Id$
      9  */
     10 
     11 package com.vladium.emma.report.lcov;
     12 
     13 import com.vladium.emma.EMMARuntimeException;
     14 import com.vladium.emma.IAppErrorCodes;
     15 import com.vladium.emma.data.ClassDescriptor;
     16 import com.vladium.emma.data.ICoverageData;
     17 import com.vladium.emma.data.IMetaData;
     18 import com.vladium.emma.report.AbstractReportGenerator;
     19 import com.vladium.emma.report.AllItem;
     20 import com.vladium.emma.report.ClassItem;
     21 import com.vladium.emma.report.IItem;
     22 import com.vladium.emma.report.ItemComparator;
     23 import com.vladium.emma.report.MethodItem;
     24 import com.vladium.emma.report.PackageItem;
     25 import com.vladium.emma.report.SourcePathCache;
     26 import com.vladium.emma.report.SrcFileItem;
     27 import com.vladium.util.Descriptors;
     28 import com.vladium.util.Files;
     29 import com.vladium.util.IProperties;
     30 import com.vladium.util.IntObjectMap;
     31 import com.vladium.util.asserts.$assert;
     32 
     33 import java.io.BufferedWriter;
     34 import java.io.File;
     35 import java.io.FileOutputStream;
     36 import java.io.IOException;
     37 import java.io.OutputStreamWriter;
     38 import java.io.UnsupportedEncodingException;
     39 import java.util.Iterator;
     40 import java.util.LinkedList;
     41 
     42 /**
     43  * @author Vlad Roubtsov, (C) 2003
     44  * @author Tim Baverstock, (C) 2009
     45  *
     46  * Generates LCOV format files:
     47  *    http://manpages.ubuntu.com/manpages/karmic/man1/geninfo.1.html
     48  */
     49 public final class ReportGenerator extends AbstractReportGenerator
     50                                    implements IAppErrorCodes
     51 {
     52     public String getType()
     53     {
     54         return TYPE;
     55     }
     56 
     57     /**
     58      * Queue-based visitor, starts with the root, node visits enqueue child
     59      * nodes.
     60      */
     61     public void process(final IMetaData mdata,
     62                         final ICoverageData cdata,
     63                         final SourcePathCache cache,
     64                         final IProperties properties)
     65         throws EMMARuntimeException
     66     {
     67         initialize(mdata, cdata, cache, properties);
     68 
     69         long start = 0;
     70         long end;
     71         final boolean trace1 = m_log.atTRACE1();
     72 
     73         if (trace1)
     74         {
     75             start = System.currentTimeMillis();
     76         }
     77 
     78         m_queue = new LinkedList();
     79         for (m_queue.add(m_view.getRoot()); !m_queue.isEmpty(); )
     80         {
     81             final IItem head = (IItem) m_queue.removeFirst();
     82             head.accept(this, null);
     83         }
     84         close();
     85 
     86         if (trace1)
     87         {
     88             end = System.currentTimeMillis();
     89             m_log.trace1("process", "[" + getType() + "] report generated in "
     90                          + (end - start) + " ms");
     91         }
     92     }
     93 
     94     public void cleanup()
     95     {
     96         m_queue = null;
     97         close();
     98         super.cleanup();
     99     }
    100 
    101 
    102     /**
    103     * Visitor for top-level node; opens output file, enqueues packages.
    104     */
    105     public Object visit(final AllItem item, final Object ctx)
    106     {
    107         File outFile = m_settings.getOutFile();
    108         if (outFile == null)
    109         {
    110             outFile = new File("coverage.lcov");
    111             m_settings.setOutFile(outFile);
    112         }
    113 
    114         final File fullOutFile = Files.newFile(m_settings.getOutDir(), outFile);
    115 
    116         m_log.info("writing [" + getType() + "] report to ["
    117                    + fullOutFile.getAbsolutePath() + "] ...");
    118 
    119         openOutFile(fullOutFile, m_settings.getOutEncoding(), true);
    120 
    121         // Enqueue packages
    122         final ItemComparator order =
    123                 m_typeSortComparators[PackageItem.getTypeMetadata().getTypeID()];
    124         for (Iterator packages = item.getChildren(order); packages.hasNext(); )
    125         {
    126             final IItem pkg = (IItem) packages.next();
    127             m_queue.addLast(pkg);
    128         }
    129 
    130         return ctx;
    131     }
    132 
    133     /**
    134      * Visitor for packages; enqueues source files contained by the package.
    135      */
    136     public Object visit(final PackageItem item, final Object ctx)
    137     {
    138         if (m_verbose)
    139         {
    140             m_log.verbose("  report: processing package [" + item.getName() + "] ...");
    141         }
    142 
    143         // Enqueue source files
    144         int id = m_srcView
    145                  ? SrcFileItem.getTypeMetadata().getTypeID()
    146                  : ClassItem.getTypeMetadata().getTypeID();
    147         final ItemComparator order = m_typeSortComparators[id];
    148         for (Iterator srcORclsFiles = item.getChildren(order);
    149              srcORclsFiles.hasNext();
    150             )
    151         {
    152             final IItem srcORcls = (IItem) srcORclsFiles.next();
    153             m_queue.addLast(srcORcls);
    154         }
    155 
    156         return ctx;
    157     }
    158 
    159     /**
    160      * Visitor for source files: doesn't use the enqueue mechanism to examine
    161      * deeper nodes because it writes the 'end_of_record' decoration here.
    162      */
    163     public Object visit (final SrcFileItem item, final Object ctx)
    164     {
    165         row("SF:".concat(item.getFullVMName()));
    166 
    167         // TODO: Enqueue ClassItems, then an 'end_of_record' object
    168 
    169         emitFileCoverage(item);
    170 
    171         row("end_of_record");
    172         return ctx;
    173     }
    174 
    175     /** Issue a coverage report for all lines in the file, and for each
    176      * function in the file.
    177      */
    178     private void emitFileCoverage(final SrcFileItem item)
    179     {
    180         if ($assert.ENABLED)
    181         {
    182             $assert.ASSERT(item != null, "null input: item");
    183         }
    184 
    185         final String fileName = item.getFullVMName();
    186 
    187         final String packageVMName = ((PackageItem) item.getParent()).getVMName();
    188 
    189         if (!m_hasLineNumberInfo)
    190         {
    191             m_log.info("source file '"
    192                        + Descriptors.combineVMName(packageVMName, fileName)
    193                        + "' has no line number information");
    194         }
    195         boolean success = false;
    196 
    197         try
    198         {
    199             // For each class in the file, for each method in the class,
    200             // examine the execution blocks in the method until one with
    201             // coverage is found. Report coverage or non-coverage on the
    202             // strength of that one block (much as for now, a line is 'covered'
    203             // if it's partially covered).
    204 
    205             // TODO: Intertwingle method records and line records
    206 
    207             {
    208                 final ItemComparator order = m_typeSortComparators[
    209                         ClassItem.getTypeMetadata().getTypeID()];
    210                 int clsIndex = 0;
    211                 for (Iterator classes = item.getChildren(order);
    212                      classes.hasNext();
    213                      ++clsIndex)
    214                 {
    215                     final ClassItem cls = (ClassItem) classes.next();
    216 
    217                     final String className = cls.getName();
    218 
    219                     ClassDescriptor cdesc = cls.getClassDescriptor();
    220 
    221                     // [methodid][blocksinmethod]
    222                     boolean[][] ccoverage = cls.getCoverage();
    223 
    224                     final ItemComparator order2 = m_typeSortComparators[
    225                             MethodItem.getTypeMetadata().getTypeID()];
    226                     for (Iterator methods = cls.getChildren(order2); methods.hasNext(); )
    227                     {
    228                         final MethodItem method = (MethodItem) methods.next();
    229                         String mname = method.getName();
    230                         final int methodID = method.getID();
    231 
    232                         boolean covered = false;
    233                         if (ccoverage != null)
    234                         {
    235                             if ($assert.ENABLED)
    236                             {
    237                                 $assert.ASSERT(ccoverage.length > methodID, "index bounds");
    238                                 $assert.ASSERT(ccoverage[methodID] != null, "null: coverage");
    239                                 $assert.ASSERT(ccoverage[methodID].length > 0, "empty array");
    240                             }
    241                             covered = ccoverage[methodID][0];
    242                         }
    243 
    244                         row("FN:" + method.getFirstLine() + "," + className + "::" + mname);
    245                         row("FNDA:" + (covered ? 1 : 0) + "," + className + "::" + mname);
    246                     }
    247                 }
    248             }
    249 
    250             // For each line in the file, emit a DA.
    251 
    252             {
    253                 final int unitsType = m_settings.getUnitsType();
    254                 // line num:int -> SrcFileItem.LineCoverageData
    255                 IntObjectMap lineCoverageMap = null;
    256                 int[] lineCoverageKeys = null;
    257 
    258                 lineCoverageMap = item.getLineCoverage();
    259                 $assert.ASSERT(lineCoverageMap != null, "null: lineCoverageMap");
    260                 lineCoverageKeys = lineCoverageMap.keys();
    261                 java.util.Arrays.sort(lineCoverageKeys);
    262 
    263                 for (int i = 0; i < lineCoverageKeys.length; ++i)
    264                 {
    265                     int l = lineCoverageKeys[i];
    266                     final SrcFileItem.LineCoverageData lCoverageData =
    267                             (SrcFileItem.LineCoverageData) lineCoverageMap.get(l);
    268 
    269                     if ($assert.ENABLED)
    270                     {
    271                         $assert.ASSERT(lCoverageData != null, "lCoverage is null");
    272                     }
    273                     switch (lCoverageData.m_coverageStatus)
    274                     {
    275                         case SrcFileItem.LineCoverageData.LINE_COVERAGE_ZERO:
    276                             row("DA:" + l + ",0");
    277                             break;
    278 
    279                         case SrcFileItem.LineCoverageData.LINE_COVERAGE_PARTIAL:
    280                             // TODO: Add partial coverage support to LCOV
    281                             row("DA:" + l + ",1");
    282                             break;
    283 
    284                         case SrcFileItem.LineCoverageData.LINE_COVERAGE_COMPLETE:
    285                             row("DA:" + l + ",1");
    286                             break;
    287 
    288                         default:
    289                             $assert.ASSERT(false, "invalid line coverage status: "
    290                                            + lCoverageData.m_coverageStatus);
    291 
    292                     } // end of switch
    293                 }
    294             }
    295 
    296             success = true;
    297         }
    298         catch (Throwable t)
    299         {
    300             t.printStackTrace(System.out);
    301             success = false;
    302         }
    303 
    304         if (!success)
    305         {
    306             m_log.info("[source file '"
    307                        + Descriptors.combineVMName(packageVMName, fileName)
    308                        + "' not found in sourcepath]");
    309         }
    310     }
    311 
    312     public Object visit (final ClassItem item, final Object ctx)
    313     {
    314         return ctx;
    315     }
    316 
    317     private void row(final StringBuffer str)
    318     {
    319         if ($assert.ENABLED)
    320         {
    321             $assert.ASSERT(str != null, "str = null");
    322         }
    323 
    324         try
    325         {
    326             m_out.write(str.toString());
    327             m_out.newLine();
    328         }
    329         catch (IOException ioe)
    330         {
    331             throw new EMMARuntimeException(IAppErrorCodes.REPORT_IO_FAILURE, ioe);
    332         }
    333     }
    334 
    335     private void row(final String str)
    336     {
    337         if ($assert.ENABLED)
    338         {
    339             $assert.ASSERT(str != null, "str = null");
    340         }
    341 
    342         try
    343         {
    344             m_out.write(str);
    345             m_out.newLine();
    346         }
    347         catch (IOException ioe)
    348         {
    349             throw new EMMARuntimeException(IAppErrorCodes.REPORT_IO_FAILURE, ioe);
    350         }
    351     }
    352 
    353     private void close()
    354     {
    355         if (m_out != null)
    356         {
    357             try
    358             {
    359                 m_out.flush();
    360                 m_out.close();
    361             }
    362             catch (IOException ioe)
    363             {
    364                 throw new EMMARuntimeException(IAppErrorCodes.REPORT_IO_FAILURE, ioe);
    365             }
    366             finally
    367             {
    368                 m_out = null;
    369             }
    370         }
    371     }
    372 
    373     private void openOutFile(final File file, final String encoding, final boolean mkdirs)
    374     {
    375         try
    376         {
    377             if (mkdirs)
    378             {
    379                 final File parent = file.getParentFile();
    380                 if (parent != null)
    381                 {
    382                     parent.mkdirs();
    383                 }
    384             }
    385             file.delete();
    386             if (file.exists())
    387             {
    388                 throw new EMMARuntimeException("Failed to delete " + file);
    389             }
    390             m_out = new BufferedWriter(
    391                     new OutputStreamWriter(new FileOutputStream(file), encoding),
    392                     IO_BUF_SIZE);
    393         }
    394         catch (UnsupportedEncodingException uee)
    395         {
    396             throw new EMMARuntimeException(uee);
    397         }
    398         catch (IOException fnfe) // FileNotFoundException
    399         {
    400             // note: in J2SDK 1.3 FileOutputStream constructor's throws clause
    401             // was narrowed to FileNotFoundException:
    402             throw new EMMARuntimeException(fnfe);
    403         }
    404     }
    405 
    406     private LinkedList /* IITem */ m_queue;
    407     private BufferedWriter m_out;
    408 
    409     private static final String TYPE = "lcov";
    410 
    411     private static final int IO_BUF_SIZE = 32 * 1024;
    412 }
    413 
    414