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
025package net.sourceforge.cobertura.check;
026
027import java.io.File;
028import java.math.BigDecimal;
029import java.util.HashMap;
030import java.util.Iterator;
031import java.util.Map;
032import java.util.StringTokenizer;
033
034import net.sourceforge.cobertura.coveragedata.ClassData;
035import net.sourceforge.cobertura.coveragedata.CoverageDataFileHandler;
036import net.sourceforge.cobertura.coveragedata.ProjectData;
037import net.sourceforge.cobertura.util.Header;
038
039import org.apache.log4j.Logger;
040import org.apache.oro.text.regex.MalformedPatternException;
041import org.apache.oro.text.regex.Pattern;
042import org.apache.oro.text.regex.Perl5Compiler;
043import org.apache.oro.text.regex.Perl5Matcher;
044
045public 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}