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 11package com.vladium.emma.report.lcov; 12 13import com.vladium.emma.EMMARuntimeException; 14import com.vladium.emma.IAppErrorCodes; 15import com.vladium.emma.data.ClassDescriptor; 16import com.vladium.emma.data.ICoverageData; 17import com.vladium.emma.data.IMetaData; 18import com.vladium.emma.report.AbstractReportGenerator; 19import com.vladium.emma.report.AllItem; 20import com.vladium.emma.report.ClassItem; 21import com.vladium.emma.report.IItem; 22import com.vladium.emma.report.ItemComparator; 23import com.vladium.emma.report.MethodItem; 24import com.vladium.emma.report.PackageItem; 25import com.vladium.emma.report.SourcePathCache; 26import com.vladium.emma.report.SrcFileItem; 27import com.vladium.util.Descriptors; 28import com.vladium.util.Files; 29import com.vladium.util.IProperties; 30import com.vladium.util.IntObjectMap; 31import com.vladium.util.asserts.$assert; 32 33import java.io.BufferedWriter; 34import java.io.File; 35import java.io.FileOutputStream; 36import java.io.IOException; 37import java.io.OutputStreamWriter; 38import java.io.UnsupportedEncodingException; 39import java.util.Iterator; 40import 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 */ 49public 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