001 /* 002 * Cobertura - http://cobertura.sourceforge.net/ 003 * 004 * Copyright (C) 2003 jcoverage ltd. 005 * Copyright (C) 2005 Mark Doliner 006 * Copyright (C) 2005 Nathan Wilson 007 * Copyright (C) 2009 Charlie Squires 008 * 009 * Cobertura is free software; you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as published 011 * by the Free Software Foundation; either version 2 of the License, 012 * or (at your option) any later version. 013 * 014 * Cobertura is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 017 * General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public License 020 * along with Cobertura; if not, write to the Free Software 021 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 022 * USA 023 */ 024 025 package net.sourceforge.cobertura.check; 026 027 import java.io.File; 028 import java.math.BigDecimal; 029 import java.util.HashMap; 030 import java.util.Iterator; 031 import java.util.Map; 032 import java.util.StringTokenizer; 033 034 import net.sourceforge.cobertura.coveragedata.ClassData; 035 import net.sourceforge.cobertura.coveragedata.CoverageDataFileHandler; 036 import net.sourceforge.cobertura.coveragedata.ProjectData; 037 import net.sourceforge.cobertura.util.Header; 038 039 import org.apache.log4j.Logger; 040 import org.apache.oro.text.regex.MalformedPatternException; 041 import org.apache.oro.text.regex.Pattern; 042 import org.apache.oro.text.regex.Perl5Compiler; 043 import org.apache.oro.text.regex.Perl5Matcher; 044 045 public class Main 046 { 047 048 private static final Logger logger = Logger.getLogger(Main.class); 049 050 final Perl5Matcher pm = new Perl5Matcher(); 051 052 final Perl5Compiler pc = new Perl5Compiler(); 053 054 /** 055 * The default CoverageRate needed for a class to pass the check. 056 */ 057 CoverageRate minimumCoverageRate; 058 059 /** 060 * The keys of this map contain regular expression Patterns that 061 * match against classes. The values of this map contain 062 * CoverageRate objects that specify the minimum coverage rates 063 * needed for a class that matches the pattern. 064 */ 065 Map minimumCoverageRates = new HashMap(); 066 067 /** 068 * The keys of this map contain package names. The values of this 069 * map contain PackageCoverage objects that track the line and 070 * branch coverage values for a package. 071 */ 072 Map packageCoverageMap = new HashMap(); 073 074 double inRangeAndDivideByOneHundred(String coverageRateAsPercentage) 075 { 076 return inRangeAndDivideByOneHundred(Integer.valueOf( 077 coverageRateAsPercentage).intValue()); 078 } 079 080 double inRangeAndDivideByOneHundred(int coverageRateAsPercentage) 081 { 082 if ((coverageRateAsPercentage >= 0) 083 && (coverageRateAsPercentage <= 100)) 084 { 085 return (double)coverageRateAsPercentage / 100; 086 } 087 throw new IllegalArgumentException("The value " 088 + coverageRateAsPercentage 089 + "% is invalid. Percentages must be between 0 and 100."); 090 } 091 092 void setMinimumCoverageRate(String minimumCoverageRate) 093 throws MalformedPatternException 094 { 095 StringTokenizer tokenizer = new StringTokenizer(minimumCoverageRate, 096 ":"); 097 this.minimumCoverageRates.put(pc.compile(tokenizer.nextToken()), 098 new CoverageRate(inRangeAndDivideByOneHundred(tokenizer 099 .nextToken()), inRangeAndDivideByOneHundred(tokenizer 100 .nextToken()))); 101 } 102 103 /** 104 * This method returns the CoverageRate object that 105 * applies to the given class. If checks if there is a 106 * pattern that matches the class name, and returns that 107 * if it finds one. Otherwise it uses the global minimum 108 * rates that were passed in. 109 */ 110 CoverageRate findMinimumCoverageRate(String classname) 111 { 112 Iterator iter = this.minimumCoverageRates.entrySet().iterator(); 113 while (iter.hasNext()) 114 { 115 Map.Entry entry = (Map.Entry)iter.next(); 116 117 if (pm.matches(classname, (Pattern)entry.getKey())) 118 { 119 return (CoverageRate)entry.getValue(); 120 } 121 } 122 return this.minimumCoverageRate; 123 } 124 125 public Main(String[] args) throws MalformedPatternException 126 { 127 int exitStatus = 0; 128 129 Header.print(System.out); 130 131 File dataFile = CoverageDataFileHandler.getDefaultDataFile(); 132 double branchCoverageRate = -1.0; 133 double lineCoverageRate = -1.0; 134 double packageBranchCoverageRate = -1.0; 135 double packageLineCoverageRate = -1.0; 136 double totalBranchCoverageRate = -1.0; 137 double totalLineCoverageRate = -1.0; 138 139 for (int i = 0; i < args.length; i++) 140 { 141 if (args[i].equals("--branch")) 142 { 143 branchCoverageRate = inRangeAndDivideByOneHundred(args[++i]); 144 } 145 else if (args[i].equals("--datafile")) 146 { 147 dataFile = new File(args[++i]); 148 } 149 else if (args[i].equals("--line")) 150 { 151 lineCoverageRate = inRangeAndDivideByOneHundred(args[++i]); 152 } 153 else if (args[i].equals("--regex")) 154 { 155 setMinimumCoverageRate(args[++i]); 156 } 157 else if (args[i].equals("--packagebranch")) 158 { 159 packageBranchCoverageRate = inRangeAndDivideByOneHundred(args[++i]); 160 } 161 else if (args[i].equals("--packageline")) 162 { 163 packageLineCoverageRate = inRangeAndDivideByOneHundred(args[++i]); 164 } 165 else if (args[i].equals("--totalbranch")) 166 { 167 totalBranchCoverageRate = inRangeAndDivideByOneHundred(args[++i]); 168 } 169 else if (args[i].equals("--totalline")) 170 { 171 totalLineCoverageRate = inRangeAndDivideByOneHundred(args[++i]); 172 } 173 } 174 175 ProjectData projectData = CoverageDataFileHandler 176 .loadCoverageData(dataFile); 177 178 if (projectData == null) 179 { 180 System.err.println("Error: Unable to read from data file " 181 + dataFile.getAbsolutePath()); 182 System.exit(1); 183 } 184 185 // If they didn't specify any thresholds, then use some defaults 186 if ((branchCoverageRate == -1.0) && (lineCoverageRate == -1.0) 187 && (packageLineCoverageRate == -1.0) 188 && (packageBranchCoverageRate == -1.0) 189 && (totalLineCoverageRate == -1.0) 190 && (totalBranchCoverageRate == -1.0) 191 && (this.minimumCoverageRates.size() == 0)) 192 { 193 branchCoverageRate = 0.5; 194 lineCoverageRate = 0.5; 195 packageBranchCoverageRate = 0.5; 196 packageLineCoverageRate = 0.5; 197 totalBranchCoverageRate = 0.5; 198 totalLineCoverageRate = 0.5; 199 } 200 // If they specified one or more thresholds, default everything else to 0 201 else 202 { 203 if (branchCoverageRate == -1.0) 204 branchCoverageRate = 0.0; 205 if (lineCoverageRate == -1.0) 206 lineCoverageRate = 0.0; 207 if (packageLineCoverageRate == -1.0) 208 packageLineCoverageRate = 0.0; 209 if (packageBranchCoverageRate == -1.0) 210 packageBranchCoverageRate = 0.0; 211 if (totalLineCoverageRate == -1.0) 212 totalLineCoverageRate = 0.0; 213 if (totalBranchCoverageRate == -1.0) 214 totalBranchCoverageRate = 0.0; 215 } 216 217 this.minimumCoverageRate = new CoverageRate(lineCoverageRate, 218 branchCoverageRate); 219 220 double totalLines = 0; 221 double totalLinesCovered = 0; 222 double totalBranches = 0; 223 double totalBranchesCovered = 0; 224 225 Iterator iter = projectData.getClasses().iterator(); 226 while (iter.hasNext()) 227 { 228 ClassData classData = (ClassData)iter.next(); 229 CoverageRate coverageRate = findMinimumCoverageRate(classData 230 .getName()); 231 232 if (totalBranchCoverageRate > 0.0) 233 { 234 totalBranches += classData.getNumberOfValidBranches(); 235 totalBranchesCovered += classData.getNumberOfCoveredBranches(); 236 } 237 238 if (totalLineCoverageRate > 0.0) 239 { 240 totalLines += classData.getNumberOfValidLines(); 241 totalLinesCovered += classData.getNumberOfCoveredLines(); 242 } 243 244 PackageCoverage packageCoverage = getPackageCoverage(classData 245 .getPackageName()); 246 if (packageBranchCoverageRate > 0.0) 247 { 248 packageCoverage.addBranchCount(classData 249 .getNumberOfValidBranches()); 250 packageCoverage.addBranchCoverage(classData 251 .getNumberOfCoveredBranches()); 252 } 253 254 if (packageLineCoverageRate > 0.0) 255 { 256 packageCoverage.addLineCount(classData.getNumberOfValidLines()); 257 packageCoverage.addLineCoverage(classData 258 .getNumberOfCoveredLines()); 259 } 260 261 logger.debug("Class " + classData.getName() 262 + ", line coverage rate: " 263 + percentage(classData.getLineCoverageRate()) 264 + "%, branch coverage rate: " 265 + percentage(classData.getBranchCoverageRate()) + "%"); 266 267 if (classData.getBranchCoverageRate() < coverageRate 268 .getBranchCoverageRate()) 269 { 270 System.err.println(classData.getName() 271 + " failed check. Branch coverage rate of " 272 + percentage(classData.getBranchCoverageRate()) 273 + "% is below " 274 + percentage(coverageRate.getBranchCoverageRate()) 275 + "%"); 276 exitStatus |= 2; 277 } 278 279 if (classData.getLineCoverageRate() < coverageRate 280 .getLineCoverageRate()) 281 { 282 System.err.println(classData.getName() 283 + " failed check. Line coverage rate of " 284 + percentage(classData.getLineCoverageRate()) 285 + "% is below " 286 + percentage(coverageRate.getLineCoverageRate()) + "%"); 287 exitStatus |= 4; 288 } 289 } 290 291 exitStatus |= checkPackageCoverageLevels(packageBranchCoverageRate, 292 packageLineCoverageRate); 293 294 // Check the rates for the overall project 295 if ((totalBranches > 0) 296 && (totalBranchCoverageRate > (totalBranchesCovered / totalBranches))) 297 { 298 System.err 299 .println("Project failed check. " 300 + "Total branch coverage rate of " 301 + percentage(totalBranchesCovered / totalBranches) 302 + "% is below " 303 + percentage(totalBranchCoverageRate) + "%"); 304 exitStatus |= 8; 305 } 306 307 if ((totalLines > 0) 308 && (totalLineCoverageRate > (totalLinesCovered / totalLines))) 309 { 310 System.err.println("Project failed check. " 311 + "Total line coverage rate of " 312 + percentage(totalLinesCovered / totalLines) 313 + "% is below " + percentage(totalLineCoverageRate) + "%"); 314 exitStatus |= 16; 315 } 316 317 System.exit(exitStatus); 318 } 319 320 private PackageCoverage getPackageCoverage(String packageName) 321 { 322 PackageCoverage packageCoverage = (PackageCoverage)packageCoverageMap 323 .get(packageName); 324 if (packageCoverage == null) 325 { 326 packageCoverage = new PackageCoverage(); 327 packageCoverageMap.put(packageName, packageCoverage); 328 } 329 return packageCoverage; 330 } 331 332 private int checkPackageCoverageLevels(double packageBranchCoverageRate, 333 double packageLineCoverageRate) 334 { 335 int exitStatus = 0; 336 Iterator iter = packageCoverageMap.entrySet().iterator(); 337 while (iter.hasNext()) 338 { 339 Map.Entry entry = (Map.Entry)iter.next(); 340 String packageName = (String)entry.getKey(); 341 PackageCoverage packageCoverage = (PackageCoverage)entry.getValue(); 342 343 exitStatus |= checkPackageCoverage(packageBranchCoverageRate, 344 packageLineCoverageRate, packageName, packageCoverage); 345 } 346 return exitStatus; 347 } 348 349 private int checkPackageCoverage(double packageBranchCoverageRate, 350 double packageLineCoverageRate, String packageName, 351 PackageCoverage packageCoverage) 352 { 353 int exitStatus = 0; 354 double branchCoverage = packageCoverage.getBranchCoverage() 355 / packageCoverage.getBranchCount(); 356 if ((packageCoverage.getBranchCount() > 0) 357 && (packageBranchCoverageRate > branchCoverage)) 358 { 359 System.err.println("Package " + packageName 360 + " failed check. Package branch coverage rate of " 361 + percentage(branchCoverage) + "% is below " 362 + percentage(packageBranchCoverageRate) + "%"); 363 exitStatus |= 32; 364 } 365 366 double lineCoverage = packageCoverage.getLineCoverage() 367 / packageCoverage.getLineCount(); 368 if ((packageCoverage.getLineCount() > 0) 369 && (packageLineCoverageRate > lineCoverage)) 370 { 371 System.err.println("Package " + packageName 372 + " failed check. Package line coverage rate of " 373 + percentage(lineCoverage) + "% is below " 374 + percentage(packageLineCoverageRate) + "%"); 375 exitStatus |= 64; 376 } 377 378 return exitStatus; 379 } 380 381 private String percentage(double coverateRate) 382 { 383 BigDecimal decimal = new BigDecimal(coverateRate * 100); 384 return decimal.setScale(1, BigDecimal.ROUND_DOWN).toString(); 385 } 386 387 public static void main(String[] args) throws MalformedPatternException 388 { 389 new Main(args); 390 } 391 392 }