/* Copyright 2009 Google Inc. All Rights Reserved. * Derived from code Copyright (C) 2003 Vladimir Roubtsov. * * This program and the accompanying materials are made available under * the terms of the Common Public License v1.0 which accompanies this * distribution, and is available at http://www.eclipse.org/legal/cpl-v10.html * * $Id$ */ package com.vladium.emma.report.lcov; import com.vladium.emma.EMMARuntimeException; import com.vladium.emma.IAppErrorCodes; import com.vladium.emma.data.ClassDescriptor; import com.vladium.emma.data.ICoverageData; import com.vladium.emma.data.IMetaData; import com.vladium.emma.report.AbstractReportGenerator; import com.vladium.emma.report.AllItem; import com.vladium.emma.report.ClassItem; import com.vladium.emma.report.IItem; import com.vladium.emma.report.ItemComparator; import com.vladium.emma.report.MethodItem; import com.vladium.emma.report.PackageItem; import com.vladium.emma.report.SourcePathCache; import com.vladium.emma.report.SrcFileItem; import com.vladium.util.Descriptors; import com.vladium.util.Files; import com.vladium.util.IProperties; import com.vladium.util.IntObjectMap; import com.vladium.util.asserts.$assert; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.util.Iterator; import java.util.LinkedList; /** * @author Vlad Roubtsov, (C) 2003 * @author Tim Baverstock, (C) 2009 * * Generates LCOV format files: * http://manpages.ubuntu.com/manpages/karmic/man1/geninfo.1.html */ public final class ReportGenerator extends AbstractReportGenerator implements IAppErrorCodes { public String getType() { return TYPE; } /** * Queue-based visitor, starts with the root, node visits enqueue child * nodes. */ public void process(final IMetaData mdata, final ICoverageData cdata, final SourcePathCache cache, final IProperties properties) throws EMMARuntimeException { initialize(mdata, cdata, cache, properties); long start = 0; long end; final boolean trace1 = m_log.atTRACE1(); if (trace1) { start = System.currentTimeMillis(); } m_queue = new LinkedList(); for (m_queue.add(m_view.getRoot()); !m_queue.isEmpty(); ) { final IItem head = (IItem) m_queue.removeFirst(); head.accept(this, null); } close(); if (trace1) { end = System.currentTimeMillis(); m_log.trace1("process", "[" + getType() + "] report generated in " + (end - start) + " ms"); } } public void cleanup() { m_queue = null; close(); super.cleanup(); } /** * Visitor for top-level node; opens output file, enqueues packages. */ public Object visit(final AllItem item, final Object ctx) { File outFile = m_settings.getOutFile(); if (outFile == null) { outFile = new File("coverage.lcov"); m_settings.setOutFile(outFile); } final File fullOutFile = Files.newFile(m_settings.getOutDir(), outFile); m_log.info("writing [" + getType() + "] report to [" + fullOutFile.getAbsolutePath() + "] ..."); openOutFile(fullOutFile, m_settings.getOutEncoding(), true); // Enqueue packages final ItemComparator order = m_typeSortComparators[PackageItem.getTypeMetadata().getTypeID()]; for (Iterator packages = item.getChildren(order); packages.hasNext(); ) { final IItem pkg = (IItem) packages.next(); m_queue.addLast(pkg); } return ctx; } /** * Visitor for packages; enqueues source files contained by the package. */ public Object visit(final PackageItem item, final Object ctx) { if (m_verbose) { m_log.verbose(" report: processing package [" + item.getName() + "] ..."); } // Enqueue source files int id = m_srcView ? SrcFileItem.getTypeMetadata().getTypeID() : ClassItem.getTypeMetadata().getTypeID(); final ItemComparator order = m_typeSortComparators[id]; for (Iterator srcORclsFiles = item.getChildren(order); srcORclsFiles.hasNext(); ) { final IItem srcORcls = (IItem) srcORclsFiles.next(); m_queue.addLast(srcORcls); } return ctx; } /** * Visitor for source files: doesn't use the enqueue mechanism to examine * deeper nodes because it writes the 'end_of_record' decoration here. */ public Object visit (final SrcFileItem item, final Object ctx) { row("SF:".concat(item.getFullVMName())); // TODO: Enqueue ClassItems, then an 'end_of_record' object emitFileCoverage(item); row("end_of_record"); return ctx; } /** Issue a coverage report for all lines in the file, and for each * function in the file. */ private void emitFileCoverage(final SrcFileItem item) { if ($assert.ENABLED) { $assert.ASSERT(item != null, "null input: item"); } final String fileName = item.getFullVMName(); final String packageVMName = ((PackageItem) item.getParent()).getVMName(); if (!m_hasLineNumberInfo) { m_log.info("source file '" + Descriptors.combineVMName(packageVMName, fileName) + "' has no line number information"); } boolean success = false; try { // For each class in the file, for each method in the class, // examine the execution blocks in the method until one with // coverage is found. Report coverage or non-coverage on the // strength of that one block (much as for now, a line is 'covered' // if it's partially covered). // TODO: Intertwingle method records and line records { final ItemComparator order = m_typeSortComparators[ ClassItem.getTypeMetadata().getTypeID()]; int clsIndex = 0; for (Iterator classes = item.getChildren(order); classes.hasNext(); ++clsIndex) { final ClassItem cls = (ClassItem) classes.next(); final String className = cls.getName(); ClassDescriptor cdesc = cls.getClassDescriptor(); // [methodid][blocksinmethod] boolean[][] ccoverage = cls.getCoverage(); final ItemComparator order2 = m_typeSortComparators[ MethodItem.getTypeMetadata().getTypeID()]; for (Iterator methods = cls.getChildren(order2); methods.hasNext(); ) { final MethodItem method = (MethodItem) methods.next(); String mname = method.getName(); final int methodID = method.getID(); boolean covered = false; if (ccoverage != null) { if ($assert.ENABLED) { $assert.ASSERT(ccoverage.length > methodID, "index bounds"); $assert.ASSERT(ccoverage[methodID] != null, "null: coverage"); $assert.ASSERT(ccoverage[methodID].length > 0, "empty array"); } covered = ccoverage[methodID][0]; } row("FN:" + method.getFirstLine() + "," + className + "::" + mname); row("FNDA:" + (covered ? 1 : 0) + "," + className + "::" + mname); } } } // For each line in the file, emit a DA. { final int unitsType = m_settings.getUnitsType(); // line num:int -> SrcFileItem.LineCoverageData IntObjectMap lineCoverageMap = null; int[] lineCoverageKeys = null; lineCoverageMap = item.getLineCoverage(); $assert.ASSERT(lineCoverageMap != null, "null: lineCoverageMap"); lineCoverageKeys = lineCoverageMap.keys(); java.util.Arrays.sort(lineCoverageKeys); for (int i = 0; i < lineCoverageKeys.length; ++i) { int l = lineCoverageKeys[i]; final SrcFileItem.LineCoverageData lCoverageData = (SrcFileItem.LineCoverageData) lineCoverageMap.get(l); if ($assert.ENABLED) { $assert.ASSERT(lCoverageData != null, "lCoverage is null"); } switch (lCoverageData.m_coverageStatus) { case SrcFileItem.LineCoverageData.LINE_COVERAGE_ZERO: row("DA:" + l + ",0"); break; case SrcFileItem.LineCoverageData.LINE_COVERAGE_PARTIAL: // TODO: Add partial coverage support to LCOV row("DA:" + l + ",1"); break; case SrcFileItem.LineCoverageData.LINE_COVERAGE_COMPLETE: row("DA:" + l + ",1"); break; default: $assert.ASSERT(false, "invalid line coverage status: " + lCoverageData.m_coverageStatus); } // end of switch } } success = true; } catch (Throwable t) { t.printStackTrace(System.out); success = false; } if (!success) { m_log.info("[source file '" + Descriptors.combineVMName(packageVMName, fileName) + "' not found in sourcepath]"); } } public Object visit (final ClassItem item, final Object ctx) { return ctx; } private void row(final StringBuffer str) { if ($assert.ENABLED) { $assert.ASSERT(str != null, "str = null"); } try { m_out.write(str.toString()); m_out.newLine(); } catch (IOException ioe) { throw new EMMARuntimeException(IAppErrorCodes.REPORT_IO_FAILURE, ioe); } } private void row(final String str) { if ($assert.ENABLED) { $assert.ASSERT(str != null, "str = null"); } try { m_out.write(str); m_out.newLine(); } catch (IOException ioe) { throw new EMMARuntimeException(IAppErrorCodes.REPORT_IO_FAILURE, ioe); } } private void close() { if (m_out != null) { try { m_out.flush(); m_out.close(); } catch (IOException ioe) { throw new EMMARuntimeException(IAppErrorCodes.REPORT_IO_FAILURE, ioe); } finally { m_out = null; } } } private void openOutFile(final File file, final String encoding, final boolean mkdirs) { try { if (mkdirs) { final File parent = file.getParentFile(); if (parent != null) { parent.mkdirs(); } } file.delete(); if (file.exists()) { throw new EMMARuntimeException("Failed to delete " + file); } m_out = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(file), encoding), IO_BUF_SIZE); } catch (UnsupportedEncodingException uee) { throw new EMMARuntimeException(uee); } catch (IOException fnfe) // FileNotFoundException { // note: in J2SDK 1.3 FileOutputStream constructor's throws clause // was narrowed to FileNotFoundException: throw new EMMARuntimeException(fnfe); } } private LinkedList /* IITem */ m_queue; private BufferedWriter m_out; private static final String TYPE = "lcov"; private static final int IO_BUF_SIZE = 32 * 1024; }