This is the mail archive of the
mauve-patches@sourceware.org
mailing list for the Mauve project.
FYI: added tool for test coverage
- From: Fabien DUMINY <fabien dot duminy at laposte dot net>
- To: mauve-patches at sources dot redhat dot com
- Date: Sun, 16 Oct 2005 19:56:12 +0200
- Subject: FYI: added tool for test coverage
I added that to estimate test coverage :
2005-10-16 Fabien DUMINY <fduminy@jnode.org>
* gnu/testlet/runner/TestletToAPI.java : added
* README.TestletToAPI : added
Index: README.TestletToAPI
===================================================================
RCS file: README.TestletToAPI
diff -N README.TestletToAPI
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ README.TestletToAPI 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,13 @@
+TestletToAPI can be used with Mauve but is in fact independant of it.
+
+TestletToAPI generates 2 japi files that can be used to show the coverage
+(in term of methods, not of lines of code) of mauve testlets.
+
+That's a command line tool that takes 4 parameters :
+ inTestlets.japi : the output of japize on mauve testlets");
+ inTested.japi : the output of japize on tested api (Classpath, jdk, ...)
+ outTestlets.japi, outTested.japi : 2 japi files to be used as input of japicompat
+
+Example of usage (in a "native" command line style) :
+ TestletToAPI W:\mauve\mauve.japi W:\mauve\jdk15.japi W:\mauve\mauve.japi.post W:\mauve\jdk15.japi.post
+
Index: TestletToAPI.java
===================================================================
RCS file: TestletToAPI.java
diff -N TestletToAPI.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ TestletToAPI.java 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,583 @@
+// Copyright (C) 2005 by <fduminy@jnode.org>
+// Written by Fabien DUMINY (fduminy@jnode.org)
+
+// This file is part of Mauve Reporter.
+
+// Mauve Reporter is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+
+// Mauve Reporter is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Mauve Reporter; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+package gnu.testlet.runner;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Generate 2 japi files that can be used to show the coverage
+ * (in term of methods, not of lines of code) of mauve testlets
+ */
+public class TestletToAPI
+{
+ private static final String TESTLET_SERIALIZATION = "serialization";
+ private static final String TESTLET_CONSTRUCTORS = "constructors";
+ private static final String TESTLET_CONSTANTS = "constants";
+ private static final String TESTLET_CLASS_SUFFIX = "Class!"; // don't remove the "!" at the end !
+
+ public static void main(String[] args) throws IOException
+ {
+ if(args.length < 4)
+ {
+ usage();
+ return;
+ }
+
+ final String inTestletsFile = args[0];
+ final String inTestedFile = args[1];
+ final String outTestletsFile = args[2];
+ final String outTestedFile = args[3];
+
+ // just insure that all files are differents
+ // that's especially usefull to avoid erasing input files !
+ for(int i = 0 ; i < 4 ; i++)
+ {
+ for(int j = 0 ; j < 4 ; j++)
+ {
+ if((j != i) && args[i].equals(args[j]))
+ throw new IllegalArgumentException("argument #"+i+" and argument #"+j+" are the same");
+ }
+ }
+
+ final Reader inTestlets = new FileReader(inTestletsFile);
+ final Reader inTested = new FileReader(inTestedFile);
+ final Writer outTestlets = new FileWriter(outTestletsFile);
+ final Writer outTested = new FileWriter(outTestedFile);
+ convert(inTestlets, inTested, outTestlets, outTested);
+ }
+
+ public static void usage()
+ {
+ System.out.println("usage: TestletToAPI inTestlets.japi inTested.japi outTestlets.japi outTested.japi");
+ System.out.println(" inTestlets.japi: the output of japize on mauve testlets");
+ System.out.println(" inTested.japi: the output of japize on tested api (Classpath, jdk, ...)");
+ System.out.println(" outTestlets.japi, outTested.japi: 2 japi files to be used as input of japicompat");
+ }
+
+ public static void convert(Reader inTestlets, Reader inTested, Writer outTestlets, Writer outTested) throws IOException
+ {
+ final Stats stats = new Stats();
+ final Map methodToFullLine = processTestedAPI(inTested, outTested, stats);
+ processMauveAPI(inTestlets, outTestlets, methodToFullLine, stats);
+
+ System.out.println("=== Statistics ===");
+ System.out.println("NbTestlets:"+stats.getNbTestlets());
+ System.out.println("NbMethods:"+stats.getNbMethods());
+ System.out.println("NbMethodsNotFound:"+stats.getNbMethodsNotFound());
+
+ if(stats.getNbMethods() == 0)
+ System.out.println("Estimated coverage : #ERROR#");
+ else
+ {
+ double ratio = (double) stats.getNbTestlets() / (double) stats.getNbMethods();
+ double percent = ((int)(10000 * ratio)) / 100.0d;
+ System.out.println("Estimated coverage : "+percent+" %");
+ }
+ }
+
+ public static void processMauveAPI(Reader in, Writer out, Map methodToFullLine,
+ Stats stats) throws IOException
+ {
+ BufferedReader reader = null;
+ BufferedWriter writer = null;
+ try
+ {
+ reader = new BufferedReader(in);
+ writer = new BufferedWriter(out);
+ String l;
+ StringBuffer line = new StringBuffer(128);
+ Set testedMethods = new HashSet();
+ boolean firstLine = true;
+ while((l = reader.readLine()) != null)
+ {
+ line.setLength(0); // clear the buffer
+ line.append(l);
+
+ if(firstLine)
+ {
+ writer.write(l);
+ writer.write('\n');
+ firstLine = false;
+ continue;
+ }
+
+ if(!acceptAPILine(line.toString())) continue;
+
+ Line mauveAPI = parseMauveAPILine(line);
+
+ final String methodName = mauveAPI.getMethodName();
+ if(testedMethods.contains(methodName)) continue;
+ testedMethods.add(methodName);
+
+ Line testedAPI = (Line) methodToFullLine.get(methodName);
+ if((testedAPI == null) || (testedAPI == Line.NULL))
+ {
+ System.err.println("method not found in API : "+methodName);
+ stats.incNbMethodsNotFound();
+ continue;
+ }
+
+ String className = testedAPI.getClassName();
+ if(!testedMethods.contains(className))
+ {
+ // first method of the class, add the class declaration line
+ testedMethods.add(className);
+
+ Line classDecl = (Line) methodToFullLine.get(className);
+ if((classDecl == null) || (classDecl == Line.NULL))
+ throw new IllegalStateException(className + " has no declaration");
+
+ writer.write(classDecl.getContent());
+ writer.write('\n');
+ }
+
+ // write the method (or constructors, or constants, or ...) declaration
+ writer.write(testedAPI.getContent());
+ writer.write('\n');
+ stats.inNbTestlets();
+ }
+ }
+ finally
+ {
+ if(reader != null)
+ {
+ reader.close();
+ }
+ if(writer != null)
+ {
+ writer.close();
+ }
+
+ in.close();
+ out.close();
+ }
+ }
+
+ public static Map processTestedAPI(Reader inTested, Writer outTested,
+ Stats stats) throws IOException
+ {
+ Map methodToFullLine = new HashMap();
+ BufferedReader reader = null;
+ BufferedWriter writer = null;
+ try
+ {
+ reader = new BufferedReader(inTested);
+ writer = new BufferedWriter(outTested);
+ String l;
+ StringBuffer line = new StringBuffer(128);
+ boolean firstLine = true;
+ while((l = reader.readLine()) != null)
+ {
+ line.setLength(0); // clear the buffer
+ line.append(l);
+
+ if(firstLine)
+ {
+ firstLine = false;
+ }
+ else if(acceptAPILine(l))
+ {
+ Line parsedLine = parseTestedAPILine(line);
+ String methodName = parsedLine.getMethodName();
+
+ switch(parsedLine.getType())
+ {
+ case Line.CONSTANTS:
+ case Line.CONSTRUCTORS:
+ Line constLine = (Line) methodToFullLine.get(methodName);
+ if((constLine == Line.NULL) || (constLine == null))
+ {
+ methodToFullLine.put(methodName, parsedLine);
+ }
+ else
+ {
+ constLine.append(parsedLine.getContent());
+ }
+ break;
+
+ case Line.CLASS_DECL:
+ if(!methodToFullLine.containsKey(methodName))
+ {
+ methodToFullLine.put(parsedLine.getClassName(), parsedLine);
+
+ if(parsedLine.isSerializable())
+ {
+ // this a serializable class, add a pseudo method
+ String serialMethod = parsedLine.getClassName()+'!'+TESTLET_SERIALIZATION;
+ String serialLine = serialMethod + "() Pcifu V";
+ Line sl = new Line(Line.SERIALIZATION, serialMethod, serialLine, true);
+ methodToFullLine.put(serialMethod, sl);
+
+ // will write 2 lines on output :
+ // 1 for the declaration and 1 for the pseudo method
+ l += "\n" ;
+ if(l.charAt(0) == '+')
+ {
+ if(l.charAt(1) == '+')
+ l += "++";
+ else
+ l += "+";
+ }
+ l += serialLine;
+ stats.incNbMethods(); // 1 extra method (a pseudo method)
+ }
+ }
+ break;
+
+ case Line.SERIALIZATION:
+ throw new IllegalStateException("type SERIALIZATION unexpected here");
+
+ case Line.METHOD:
+ if(!methodToFullLine.containsKey(methodName))
+ {
+ methodToFullLine.put(methodName, parsedLine);
+ }
+ break;
+
+ default:
+ if(parsedLine != Line.NULL)
+ {
+ throw new IllegalStateException("unknown type of line");
+ }
+ } // switch
+ } // if
+
+ writer.write(l);
+ writer.write('\n');
+ stats.incNbMethods();
+ } // while
+ }
+ finally
+ {
+ if(reader != null)
+ {
+ reader.close();
+ }
+ inTested.close();
+
+ if(writer != null)
+ {
+ writer.close();
+ }
+ outTested.close();
+ }
+
+ return methodToFullLine;
+ }
+
+ public static Line parseTestedAPILine(StringBuffer japizeLine)
+ {
+ final String line = japizeLine.toString();
+ boolean isClassDecl = preprocessAPILine(japizeLine);
+ boolean isSerialClassDecl = false;
+ // tested API (Classpath, jdk or something compatible)
+
+ if(!isClassDecl && (japizeLine.indexOf("(") < 0))
+ {
+ // class declared as Serializable ?
+ isSerialClassDecl = (japizeLine.indexOf("class#") >= 0) &&
+ (japizeLine.lastIndexOf("java.io.Serializable") >= 0);
+
+ boolean isNonSerialClassDecl = (japizeLine.indexOf("class:") >= 0);
+ boolean isInterfaceDecl = (japizeLine.indexOf("interface") >= 0);
+ isClassDecl = (isNonSerialClassDecl || isSerialClassDecl || isInterfaceDecl);
+ }
+ if(isClassDecl)
+ {
+ // special case for class declaration (Serializable or not) and
+ // interface declarations
+ int idx = japizeLine.indexOf("!");
+ if(idx < 0) return Line.NULL;
+
+ japizeLine.setLength(idx+1);
+ return new Line(Line.CLASS_DECL, japizeLine.toString(), line, isSerialClassDecl);
+ }
+
+ int idx = japizeLine.indexOf("(");
+ if(idx < 0)
+ {
+ // constant ?
+ idx = japizeLine.indexOf("!#");
+ if(idx < 0) return Line.NULL;
+
+ // special case for constants
+ japizeLine.setLength(idx+1);
+ japizeLine.append(TESTLET_CONSTANTS);
+ return new Line(Line.CONSTANTS, japizeLine.toString(), line);
+ }
+
+ boolean isConstructor = (japizeLine.indexOf("constructor") >= 0);
+ if(isConstructor)
+ {
+ // special case for constructors
+ japizeLine.setLength(idx);
+ japizeLine.append(TESTLET_CONSTRUCTORS);
+ return new Line(Line.CONSTRUCTORS, japizeLine.toString(), line);
+ }
+
+ if(idx >= 0)
+ {
+ // simple method
+ japizeLine.setLength(idx);
+ return new Line(Line.METHOD, japizeLine.toString(), line);
+ }
+
+ return Line.NULL;
+ }
+
+ public static Line parseMauveAPILine(StringBuffer japizeLine)
+ {
+ int type;
+ final String line = japizeLine.toString();
+ preprocessAPILine(japizeLine);
+
+ // mauve test API
+
+ japizeLine.delete(0, "gnu.testlet.".length());
+
+ int idx = japizeLine.indexOf("!");
+ if(idx < 0) return Line.NULL;
+ japizeLine.setLength(idx);
+
+ idx = japizeLine.indexOf(",");
+ if(idx < 0) return Line.NULL;
+ japizeLine.setCharAt(idx, '!');
+
+ idx = japizeLine.lastIndexOf(".");
+ if(idx < 0) return Line.NULL;
+ japizeLine.setCharAt(idx, ',');
+
+ // special case for some classes
+ // (due to case conflicts in filenames under Windows)
+ // example : ColorClass->Color, ...
+ idx = japizeLine.indexOf(TESTLET_CLASS_SUFFIX);
+ if(idx >= 0)
+ {
+ japizeLine.delete(idx, idx+TESTLET_CLASS_SUFFIX.length()-1);
+ }
+
+ if(japizeLine.indexOf(TESTLET_CONSTANTS) >= 0)
+ type = Line.CONSTANTS;
+ else if(japizeLine.indexOf(TESTLET_CONSTRUCTORS) >= 0)
+ type = Line.CONSTRUCTORS;
+ else if(japizeLine.indexOf(TESTLET_SERIALIZATION) >= 0)
+ type = Line.SERIALIZATION;
+ else
+ type = Line.METHOD;
+
+ return new Line(type, japizeLine.toString(), line);
+ }
+
+ public static boolean acceptAPILine(String line)
+ {
+// final String filter = "java.awt.AWTKeyStroke";
+// return line.startsWith("gnu.testlet."+filter) ||
+// line.startsWith(filter);
+
+ boolean isMauveFramework = line.toString().startsWith("gnu.testlet,");
+// return !isMauveFramework &&
+// (line.contains("java.awt.BasicStroke") ||
+// line.contains("java.awt,BasicStroke") );
+ return !isMauveFramework;
+ }
+
+ public static class Stats
+ {
+ private int nbTestlets;
+ private int nbMethods; // include special methods (serialization, constructors, constants, ...)
+ private int nbMethodsNotFound;
+
+ public Stats()
+ {
+ this.nbTestlets = 0;
+ this.nbMethods = 0;
+ this.nbMethodsNotFound = 0;
+ }
+
+ /**
+ * @return Returns the nbMethods.
+ */
+ final int getNbMethods()
+ {
+ return nbMethods;
+ }
+
+ final void incNbMethods()
+ {
+ this.nbMethods++;
+ }
+
+ /**
+ * @return Returns the nbTestlets.
+ */
+ final int getNbTestlets()
+ {
+ return nbTestlets;
+ }
+
+ final void inNbTestlets()
+ {
+ this.nbTestlets++;
+ }
+
+ /**
+ * @return Returns the nbMethodsNotFound.
+ */
+ final int getNbMethodsNotFound()
+ {
+ return nbMethodsNotFound;
+ }
+
+ /**
+ * @param nbMethodsNotFound The nbMethodsNotFound to set.
+ */
+ final void incNbMethodsNotFound()
+ {
+ this.nbMethodsNotFound++;
+ }
+
+
+ }
+
+ public static class Line
+ {
+ public static final Line NULL = new Line();
+
+ private static final int METHOD = 0;
+ private static final int CONSTRUCTORS = 1;
+ private static final int CONSTANTS = 2;
+ private static final int SERIALIZATION = 3;
+ private static final int CLASS_DECL = 4;
+
+ final private int type;
+ final private String className;
+ final private String methodName; // in fact: classname + '!' + methodName
+ final private StringBuffer lines;
+ final private boolean serializable;
+
+ public Line(int type, String methodName, String line)
+ {
+ this(type, methodName, line, false);
+ }
+
+ public Line(int type, String methodName, String line, boolean serializable)
+ {
+ this.type = type;
+ this.methodName = methodName;
+ this.lines = new StringBuffer(line);
+ this.serializable = serializable;
+
+ int idx = methodName.indexOf('!');
+ if(idx < 0) throw new IllegalArgumentException("methodName must contains the '!' character");
+ this.className = methodName.substring(0, idx);
+ }
+
+ public int getType()
+ {
+ return type;
+ }
+
+ public boolean isSerializable()
+ {
+ return serializable;
+ }
+
+ public String getContent()
+ {
+ return lines.toString();
+ }
+
+ private Line()
+ {
+ this.type = -1;
+ this.className = "";
+ this.methodName = "";
+ this.lines = null;
+ this.serializable = false;
+ }
+
+ public void append(String nextLine)
+ {
+ if(!isConstructor() && !isConstant())
+ throw new UnsupportedOperationException("reserved to constructor and constant lines");
+
+ lines.append('\n');
+ lines.append(nextLine);
+ }
+
+ public boolean isConstructor()
+ {
+ return type == CONSTRUCTORS;
+ }
+
+ public boolean isConstant()
+ {
+ return type == CONSTANTS;
+ }
+
+ /**
+ * @return Returns the methodName.
+ */
+ final public String getMethodName()
+ {
+ return methodName;
+ }
+
+ public String getClassName()
+ {
+ return className;
+ }
+
+ public String toString()
+ {
+ return "["+methodName+"]->"+lines.toString();
+ }
+ }
+
+ public static boolean preprocessAPILine(StringBuffer japizeLine)
+ {
+ boolean isClassDecl = false;
+ if(japizeLine.charAt(0) == '+')
+ {
+ if(japizeLine.charAt(1) == '+')
+ {
+ japizeLine.delete(0, 2);
+
+ // declaration of java.lang.Object ?
+ isClassDecl = (japizeLine.toString().endsWith("class"));
+ }
+ else
+ {
+ japizeLine.delete(0, 1);
+ }
+ }
+
+ return isClassDecl;
+ }
+}