001: package csdl.jblanket.report;
002:
003: import csdl.jblanket.JBlanket;
004: import csdl.jblanket.JBlanketException;
005: import csdl.jblanket.methodset.MethodSet;
006: import csdl.jblanket.methodset.MethodSetManager;
007: import csdl.jblanket.util.MethodCategories;
008:
009: import java.io.File;
010: import java.io.FileInputStream;
011: import java.io.FileOutputStream;
012: import java.io.IOException;
013: import java.util.Date;
014: import java.util.HashMap;
015: import java.util.List;
016: import java.util.Map;
017:
018: import org.apache.tools.ant.DirectoryScanner;
019:
020: /**
021: * Creates reports for method coverage.
022: * <p>
023: * This is the last step in JBlanket.
024: * <p>
025: * There are two main functions of this class:
026: * <ul>
027: * <li> Combines the methods invoked during testing in "COVER-*.xml" files into one XML file,
028: * where * is the fully qualified name of the test class, then derives the methods that were
029: * not tested.
030: * <li> Calls the AggregateTransformer class to combine JBlanket output and transform them
031: * into HTML files.
032: * </ul>
033: * <p>
034: * The above mentioned functions are achieved by invoking the
035: * <code>main</code> method. Arguments to pass to the main method include:
036: * <p>
037: * <b>Required</b> command line argument:
038: * <ul>
039: * <p>
040: * none.
041: * </ul>
042: * <p>
043: * <b>Optional</b> command line argument:
044: * <ul>
045: * <p>
046: * 'enable' - describes if a JBlanket report should be created. Valid values include
047: * "true", "on", "yes" to create a report or "false", "off", or "no" to not
048: * create a report.<br>
049: * <i>For example</i>: -enable true
050: * <p>
051: * 'verbose' - describes if report should be created in verbose mode<br>
052: * <i>For example</i>: -verbose true
053: * <p>
054: * 'reportFormat' - format of the HTML report, either 'frames' or 'noframes'<br>
055: * <i>For example</i>: -reportFormat frames
056: * <p>
057: * 'toDir' - directory where all HTML files should be sent<br>
058: * <i>For example</i>: -toDir jblanket/html
059: * <p>
060: * 'excludeOneLineMethods' - describes if one-line methods were excluded<br>
061: * <i>For example</i>: -excludeOneLineMethods false
062: * <p>
063: * 'excludeConstructors' - describes if constructors were excluded<br>
064: * <i>For example</i>: -excludeConstructors false
065: * <p>
066: * 'excludeIndividualMethods' - describes if individual methods were excluded<br>
067: * <i>For example</i>: -excludeIndividualMethods false
068: * <p>
069: * 'totalFile' - name of XML file containing all methods included in the coverage measurement<br>
070: * <i>For example</i>: -totalFile totalMethods.xml
071: * <p>
072: * 'testedFile' - name of XML file to contain all methods that were tested<br>
073: * <i>For example</i>: -testedFile testedMethods.xml
074: * <p>
075: * 'untestedFile' - name of XML file to contain all methods that were not tested<br>
076: * <i>For example</i>: -untestedFile untestedMethods.xml
077: * <p>
078: * 'onelineFile' - name of XML file containing all one-line methods<br>
079: * <i>For example</i>: -oneLineFile oneLineMethods.xml
080: * <p>
081: * 'constructionFile' - name of XML file containing all constructors<br>
082: * <i>For example</i>: -constructorFile constructorMethods.xml
083: * <p>
084: * 'excludedIndividualFile' - name of XML file containing all individually excluded methods<br>
085: * <i>For example</i>: -excludedIndividualFile excludedIndividualMethods.xml
086: * </ul>
087: * <p>
088: * Default values are provided for all optional command line arguments and are equivalent to
089: * those shown in the examples. Note that values for similar arguments found in
090: * csdl.jblanket.modifier.Modifier or csdl.jblanket.app.ExcludeIndividualMethodApp must be set to
091: * the same value. For example, if the 'excludeOneLineMethods' argument was set to 'true' and the
092: * 'onelinefile' argument was set to 'myOneLineMethods.xml' in the Modifier class, then arguments
093: * for 'excludeOneLineMethods' and 'onelinefile' in csdl.jblanket.report.JBlanketReport and
094: * csdl.jblanket.app.ExcludeIndividualMethodApp also need to be set to 'true' and
095: * 'myOneLineMethods.xml', respectively.
096: * <p>
097: * Note that the methods in <code>excludedIndividualFile</code> take priority over all the other
098: * methods. I.e., if a one-line method was individually excluded by the application, then it is
099: * considered to be an individually excluded methods instead of a one-line method.
100: *
101: * @author Joy M. Agustin
102: * @version $Id: JBlanketReport.java,v 1.3 2005/02/21 20:28:40 timshadel Exp $id
103: */
104: public class JBlanketReport extends JBlanket {
105:
106: /** Prefix to file name for JBlanket intermediate coverage data from JUnit tests */
107: private static final String PREFIX = "COVER-";
108: /** Name of file with aggregate of all methods */
109: private static final String AGG_FILENAME = "MethodSets.xml";
110:
111: /** Holds the name of the current class */
112: private static String currentClassName;
113: /** Time stamp from 'totalFile' that will be stores in JBlanket summary output files */
114: private Date timeStamp;
115: /** Format of HTML reports */
116: private String reportFormat;
117: /** Directory for HTML output */
118: private File toDir;
119: /** Message containing coverage results */
120: private String results;
121:
122: /** Contains methods invoked during testing */
123: private MethodSet testedSet;
124: /** Contains methods not invoked during testing */
125: private MethodSet untestedSet;
126:
127: /** Describes if individual methods should be excluded from the coverage measurement */
128: private boolean excludeIndividualMethods = false;
129: /** Contains individual methods excluded from coverage */
130: private MethodSet excludedIndividualSet;
131:
132: /**
133: * Constructs a new JBlanketReport object. This object will read in information stored in XML
134: * files <code>totalFile</code>, <code>oneLineFile</code>, and <code>constructorFile</code> and
135: * store information in the <Code>testedFile</code> and <code>untestedFile</code> files in XML
136: * format.
137: *
138: * @param verbose describes if JBlanket should execute in verbose mode.
139: * @param excludeOneLineMethods describes if one-line methods should be excluded.
140: * @param excludeConstructors describes if constructors should be excluded.
141: * @param excludeIndividualMethods describes if individual methods should be excluded.
142: * @param reportFormat format of HTML report.
143: * @param toDir output directory of HTML report.
144: * @throws JBlanketException if cannot read from <code>totalFile</code>,
145: * <code>oneLineFile</code>, or <code>constructorFile</code>.
146: */
147: public JBlanketReport(boolean verbose,
148: boolean excludeOneLineMethods, boolean excludeConstructors,
149: boolean excludeIndividualMethods, String reportFormat,
150: File toDir)
151:
152: throws JBlanketException {
153: super ();
154:
155: this .verbose = verbose;
156: this .reportFormat = reportFormat;
157: if (toDir == null) {
158: this .toDir = new File(jblanketDir);
159: } else {
160: this .toDir = toDir;
161: }
162:
163: // get all filesets; since report is probably created in different JVM than instrumentation,
164: // need to load all MethodSet elements from files
165: MethodSetManager manager = MethodSetManager.getInstance();
166: super .totalSet = manager.getMethodSet(super .categories
167: .getFileName("totalFile"));
168: this .timeStamp = loadMethods(super .totalSet, new File(
169: super .categories.getFileName("totalFile")));
170:
171: // if one-line methods are excluded, get them
172: super .excludeOneLineMethods = excludeOneLineMethods;
173: super .oneLineSet = new MethodSet();
174: if (excludeOneLineMethods) {
175: loadMethods(super .oneLineSet, new File(super .categories
176: .getFileName("oneLineFile")));
177: }
178:
179: // if constructors are excluded, get them
180: super .excludeConstructors = excludeConstructors;
181: super .constructorSet = new MethodSet();
182: if (excludeConstructors) {
183: loadMethods(super .constructorSet, new File(super .categories
184: .getFileName("constructorFile")));
185: }
186:
187: // if individual methods are excluded, get them
188: this .excludeIndividualMethods = excludeIndividualMethods;
189: this .excludedIndividualSet = new MethodSet();
190: if (excludeIndividualMethods) {
191: File excludeIndividualFile = new File(super .categories
192: .getFileName("excludedIndividualFile"));
193: // file will not exist if this is the first time excluding individual files
194: if (excludeIndividualFile.exists()) {
195: loadMethods(this .excludedIndividualSet,
196: excludeIndividualFile);
197: } else {
198: this .excludedIndividualSet = new MethodSet();
199: }
200: }
201:
202: super .untestableSet = manager.getMethodSet(super .categories
203: .getFileName("untestableFile"));
204: loadMethods(super .untestableSet, new File(super .categories
205: .getFileName("untestableFile")));
206:
207: // create empty MethodSet elements in MethodSetManager
208: this .testedSet = manager.getMethodSet(super .categories
209: .getFileName("testedFile"));
210: this .untestedSet = manager.getMethodSet(super .categories
211: .getFileName("untestedFile"));
212: }
213:
214: /**
215: * Creates a report from the XML files.
216: *
217: * @throws JBlanketException if unable to create report.
218: * @return a summary of the report.
219: */
220: public String createReport() throws JBlanketException {
221:
222: createTestedFile();
223: createUntestedFile();
224:
225: // create aggregate file "COVER-MethodSets.xml"
226: Map reportCategories = getReportCategories();
227: // send in the jblanketDir value to avoid added "Warnings" to the screen
228: AggregateTransformer transformer = new AggregateTransformer(
229: reportCategories, this .reportFormat, super .jblanketDir,
230: this .toDir);
231: File aggregateFile = new File(super .jblanketDir, PREFIX
232: + AGG_FILENAME);
233: transformer.createAggregateFile(aggregateFile);
234:
235: // transform aggregate file to HTML
236: transformer.transformXmlToHtml(aggregateFile.getAbsolutePath());
237: return this .results;
238: }
239:
240: /**
241: * Creates an aggregate file containing all methods invoked during testing.
242: *
243: * @throws JBlanketException if no files found from testing, cannot read date from any
244: * 'COVER-*' files, or cannot store test file.
245: */
246: protected void createTestedFile() throws JBlanketException {
247:
248: // get all intermediate coverage files from executing JUnit tests
249: DirectoryScanner scanner = new DirectoryScanner();
250: scanner.setIncludes(new String[] { PREFIX + "*" });
251: scanner.setExcludes(null);
252: scanner.setBasedir(new File(jblanketDir));
253: scanner.setCaseSensitive(true);
254: scanner.scan();
255: String[] files = scanner.getIncludedFiles();
256:
257: // if no 'COVER-*.xml' files are found, throw exception
258: if (files.length == 0) {
259: String message = "No intermediate '"
260: + PREFIX
261: + "*.xml' files found in "
262: + super .jblanketDir
263: + ". Invoked class files may not have been modified by JBlanket.";
264: throw new JBlanketException(message);
265: }
266:
267: // process each file to form a single aggregate file
268: MethodSetManager manager = MethodSetManager.getInstance();
269: for (int i = 0; i < files.length; i++) {
270: MethodSet nextSet = manager.getMethodSet(files[i]);
271:
272: // skip previous aggregate JBlanket output if not deleted between executions
273: if ((PREFIX + AGG_FILENAME).equals(files[i])) {
274: continue;
275: }
276: loadMethods(nextSet, new File(super .jblanketDir, files[i]));
277: this .testedSet.union(nextSet);
278: }
279:
280: // verify all methods in testedSet are valid; don't want to alter the other MethodSets just yet
281: this .testedSet.intersection(super .totalSet);
282:
283: // store total tested methods before extracting optional exclusions
284: try {
285: storeMethods(this .testedSet, new File(categories
286: .getFileName("total.testedFile")));
287: } catch (IOException e) {
288: throw new JBlanketException(
289: "Unable to store total tested methods to "
290: + categories
291: .getFileName("total.testedFile"), e);
292: }
293:
294: // removed the optional method exclusions
295: this .testedSet.difference(super .oneLineSet);
296: this .testedSet.difference(super .constructorSet);
297: this .testedSet.difference(this .excludedIndividualSet);
298: try {
299: storeMethods(this .testedSet, new File(categories
300: .getFileName("testedFile")));
301: } catch (IOException e) {
302: throw new JBlanketException(
303: "Unable to store tested methods to "
304: + categories.getFileName("testedFile"), e);
305: }
306: }
307:
308: /**
309: * Creates a file containing the methods that were not invoked during testing.
310: * <p>
311: * NOTE: Results are rounded to nearest percent while coverage in reports are rounded to the
312: * nearest half percent.
313: *
314: * @throws JBlanketException if cannot read from totalFile, set in constructor.
315: */
316: protected void createUntestedFile() throws JBlanketException {
317: // get number of methods in each set
318: int untestableSize = this .untestableSet.size();
319: int totalSize = super .totalSet.size() + untestableSize;
320: int oneLineSize = super .oneLineSet.size();
321: int constructorSize = super .constructorSet.size();
322: int excludedIndividualSize = this .excludedIndividualSet.size();
323: int subtotalSize = totalSize - oneLineSize - constructorSize
324: - excludedIndividualSize - untestableSize;
325: int testedSize = testedSet.size();
326:
327: // store total untested methods before extracting optional exclusions
328: this .untestedSet = super .totalSet.difference(this .testedSet);
329: try {
330: storeMethods(this .untestedSet, new File(categories
331: .getFileName("total.untestedFile")));
332: } catch (IOException e) {
333: throw new JBlanketException(
334: "Unable to store total untested methods to "
335: + categories
336: .getFileName("total.untestedFile"),
337: e);
338: }
339:
340: // remove the optional method exclusions
341: this .untestedSet.difference(super .oneLineSet);
342: this .untestedSet.difference(super .constructorSet);
343: this .untestedSet.difference(this .excludedIndividualSet);
344: try {
345: storeMethods(this .untestedSet, new File(super .categories
346: .getFileName("untestedFile")));
347: } catch (IOException e) {
348: throw new JBlanketException(
349: "Unable to store untested methods to "
350: + categories.getFileName("untestedFile"), e);
351: }
352:
353: // calculate coverage measurements
354: int untestedSize = this .untestedSet.size();
355: int percent = 100;
356: int oneLinePercent = Math
357: .round(((float) oneLineSize / totalSize) * percent);
358: int constructorPercent = Math
359: .round(((float) constructorSize / totalSize) * percent);
360: int excludedIndividualPercent = Math
361: .round(((float) excludedIndividualSize / totalSize)
362: * percent);
363: int testedPercent = Math
364: .round(((float) testedSize / subtotalSize) * percent);
365: int untestedPercent = percent - testedPercent;
366:
367: // generate output message
368: StringBuffer msg = new StringBuffer();
369: msg
370: .append("********************************************************\n");
371: msg.append("Method-level Coverage:\n");
372: msg.append("All methods : {total="
373: + totalSize + "}\n");
374: msg.append("Untestable methods : {total="
375: + untestableSize + "}\n");
376: if (super .excludeOneLineMethods) {
377: msg.append("Excluded One-line methods : {total="
378: + oneLineSize + "}\n");
379: }
380: if (super .excludeConstructors) {
381: msg.append("Excluded Constructors : {total="
382: + constructorSize + "}\n");
383: }
384: if (this .excludeIndividualMethods) {
385: msg.append("Excluded individual methods : {total="
386: + excludedIndividualSize + "}\n");
387: }
388: msg
389: .append("--------------------------------------------------------\n");
390: if (super .excludeOneLineMethods || super .excludeConstructors
391: || this .excludeIndividualMethods) {
392: msg.append("Remaining methods : {total="
393: + subtotalSize + "}\n");
394: }
395: msg.append("Tested methods : {total="
396: + testedSize + ", percent=" + testedPercent + "%}\n");
397: msg.append("Untested methods : {total="
398: + untestedSize + ", percent=" + untestedPercent
399: + "%}\n");
400: msg
401: .append("********************************************************");
402:
403: this .results = msg.toString();
404: }
405:
406: /**
407: * Loads all the method information into <code>jblanketSet</code> from <code>file</code>
408: * in the jblanket.dir system property.
409: *
410: * @param methodSet the collection of method information to load.
411: * @param file the input file.
412: * @return the Date found in <code>file</code>.
413: * @throws JBlanketException if cannot write to <code>fileName</code>.
414: */
415: private Date loadMethods(MethodSet methodSet, File file)
416: throws JBlanketException {
417:
418: Date timeStamp;
419: try {
420: // throws FileNotFoundException
421: FileInputStream in = new FileInputStream(file);
422: // throws PareseException
423: timeStamp = methodSet.load(in);
424: // throws IOException
425: in.close();
426: } catch (Exception e) {
427: throw new JBlanketException("Unable to read file "
428: + file.getAbsolutePath(), e);
429: }
430:
431: return timeStamp;
432: }
433:
434: /**
435: * Stores all the method information from <code>jblanketSet</code> to <code>fileName</code>
436: * in the jblanket.dir system property.
437: *
438: * @param methodSet the collection of method information to output.
439: * @param file the output file.
440: * @throws IOException if cannot write to <code>fileName</code>.
441: */
442: protected void storeMethods(MethodSet methodSet, File file)
443: throws IOException {
444:
445: // create FileOutputStream for output of test class results -- throws FileNotFoundException
446: FileOutputStream fostream = new FileOutputStream(file);
447: // store test class results -- throws IOException
448: methodSet.store(fostream, null, this .timeStamp);
449: // throws IOException
450: fostream.close();
451: }
452:
453: /**
454: * Returns all the categories of methods collected. These categories are used as categories
455: * for the methods in the aggregate XML file.
456: *
457: * @return all the collected method categories.
458: */
459: protected Map getReportCategories() {
460:
461: Map reportCategories = new HashMap();
462:
463: if (super .excludeConstructors) {
464: reportCategories.put("constructor", super .categories
465: .getFileName("constructorFile"));
466: }
467:
468: if (super .excludeOneLineMethods) {
469: reportCategories.put("oneline", super .categories
470: .getFileName("oneLineFile"));
471: }
472:
473: if (this .excludeIndividualMethods) {
474: reportCategories.put("excludedIndividual", super .categories
475: .getFileName("excludedIndividualFile"));
476: }
477:
478: reportCategories.put("tested", super .categories
479: .getFileName("testedFile"));
480: reportCategories.put("untested", super .categories
481: .getFileName("untestedFile"));
482: reportCategories.put("untestable", super .categories
483: .getFileName("untestableFile"));
484: return reportCategories;
485: }
486:
487: /**
488: * Provides the command line interface.
489: *
490: * @param args the command line arguments.
491: * @throws JBlanketException if cannot read the date from files.
492: */
493: public static void main(String args[]) throws JBlanketException {
494: main(java.util.Arrays.asList(args));
495: }
496:
497: /**
498: * Processes the command line arguments as a List.
499: * <p>
500: * Created for JBlanketReportTask Ant taskdef. This method grabs all of the output files
501: * from storeMethodTypeSignature method in csdl.jblanket.modifier.MethodCollector and combines
502: * the results into a format similar to JUnit.
503: * <p>
504: * The command line arguments are as follows:
505: * <pre>
506: * '-verbose' - describes if should execute in verbose mode
507: * '-reportFormat' - format of the HTML report, either 'frames' or 'noframes'
508: * '-toDir' - directory for HTML output
509: * '-excludeOneLineMethods' - describes if should exclude one-line methods
510: * '-excludeConstructors' - describes if should exclude constructors
511: * '-excludeIndividualMethods' - describes if should exclude individual methods
512: * '-totalFile' - name of the output XML file for total methods
513: * '-testedFile' - name of the output XML file to contain all methods that were tested
514: * '-untestedFile' - name of the output XML file to contain all methods that were not tested
515: * '-oneLineFile' - name of the output XML file for one-line methods
516: * '-constructorFile' - name of the output XML file for constructors
517: * '-excludedIndividualFile' - name of the output XML file for individually excluded methods
518: * </pre>
519: *
520: * @param args the List of command line arguments.
521: * @throws JBlanketException if cannot read the date from files.
522: */
523: public static void main(List args) throws JBlanketException {
524:
525: // Verbose mode
526: boolean verbose = false;
527: // format of final HTML report
528: String reportFormat = "frames";
529: File toDir = null;
530:
531: // Exclude one-line methods
532: boolean excludeOneLineMethods = false;
533: // Exclude constructors
534: boolean excludeConstructors = false;
535: // Exclude individual methods
536: boolean excludeIndividualMethods = false;
537:
538: MethodCategories categories = MethodCategories.getInstance();
539:
540: // index of current command line arguments.
541: int i;
542: // Parses args into corresponsing variables.
543: for (i = 0; i < args.size(); ++i) {
544: String argument = (String) args.get(i);
545: if (argument.equals("-verbose")) {
546: verbose = ((Boolean) args.get(++i)).booleanValue();
547: } else if (argument.equals("-reportFormat")) {
548: reportFormat = (String) args.get(++i);
549: } else if (argument.equals("-toDir")) {
550: toDir = new File((String) args.get(++i));
551: } else if (argument.equals("-excludeOneLineMethods")) {
552: excludeOneLineMethods = ((Boolean) args.get(++i))
553: .booleanValue();
554: } else if (argument.equals("-excludeConstructors")) {
555: excludeConstructors = ((Boolean) args.get(++i))
556: .booleanValue();
557: } else if (argument.equals("-excludeIndividualMethods")) {
558: excludeIndividualMethods = ((Boolean) args.get(++i))
559: .booleanValue();
560: } else if (argument.equals("-oneLineFile")) {
561: categories.addCategory("oneLineFile", (String) args
562: .get(++i));
563: } else if (argument.equals("-constructorFile")) {
564: categories.addCategory("constructorFile", (String) args
565: .get(++i));
566: } else if (argument.equals("-excludedIndividualFile")) {
567: categories.addCategory("excludedIndividualFile",
568: (String) args.get(++i));
569: } else if (argument.equals("-totalFile")) {
570: categories.addCategory("totalFile", (String) args
571: .get(++i));
572: } else if (argument.equals("-testedFile")) {
573: categories.addCategory("testedFile", (String) args
574: .get(++i));
575: } else if (argument.equals("-untestedFile")) {
576: categories.addCategory("untestedFile", (String) args
577: .get(++i));
578: } else if (argument.equals("-untestableFile")) {
579: categories.addCategory("untestableFile", (String) args
580: .get(++i));
581: } else {
582: System.out.println("Incorrect usage: " + argument);
583: System.exit(1);
584: }
585: }
586:
587: JBlanketReport report = new JBlanketReport(verbose,
588: excludeOneLineMethods, excludeConstructors,
589: excludeIndividualMethods, reportFormat, toDir);
590:
591: System.out.println(report.createReport());
592: if (reportFormat.equals("frames")) {
593: System.out.println("JBlanket results in "
594: + report.toDir.getAbsolutePath() + File.separator
595: + "index.html");
596: } else {
597: System.out.println("JBlanket results in "
598: + report.toDir.getAbsolutePath() + File.separator
599: + "jblanket-noframes.html");
600: }
601: }
602: }
|