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 }