Mercurial > hg > MauveTestCoverage
changeset 0:29318fe8d6d0
2012-01-04 Pavel Tisnovsky <ptisnovs@redhat.com>
* adding .classpath:
* adding .project:
* adding .settings/org.eclipse.jdt.core.prefs:
* adding AUTHORS:
* adding ChangeLog:
* adding LICENSE:
* adding Makefile:
* adding NEWS:
* adding README:
* adding TODO:
* adding class_list.txt:
* adding path_to_rt_jar.txt:
* adding src/PrintClassList.java:
* adding src/PrintPublicMethods.java:
* adding src/PrintTestCoverage.java:
* adding src/ReportGenerator.java:
* adding src/index.html:
* adding src/style.css:
* adding test_directory.txt:
Initial push to this project.
author | Pavel Tisnovsky <ptisnovs@redhat.com> |
---|---|
date | Wed, 04 Jan 2012 11:20:55 +0100 |
parents | |
children | 17f81193758a |
files | .classpath .project .settings/org.eclipse.jdt.core.prefs AUTHORS ChangeLog LICENSE Makefile NEWS README TODO class_list.txt path_to_rt_jar.txt src/PrintClassList.java src/PrintPublicMethods.java src/PrintTestCoverage.java src/ReportGenerator.java src/index.html src/style.css test_directory.txt |
diffstat | 17 files changed, 2033 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.classpath Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> + <classpathentry kind="output" path="bin"/> +</classpath>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.project Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>MauveTestCoverage</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.settings/org.eclipse.jdt.core.prefs Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,12 @@ +#Tue Dec 20 12:00:42 CET 2011 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/AUTHORS Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,10 @@ +The following people have made contibutions to this project. +Please keep this list in alphabetical order. + +Pavel Tisnovsky <ptisnovs@redhat.com> + +The following people provided feedback and bug reports: + +If your name doesn't appear on either list, but should, if it appears +on the wrong list, or if your name or mail address is misspelled, +please let us know.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ChangeLog Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,23 @@ +2012-01-04 Pavel Tisnovsky <ptisnovs@redhat.com> + + * adding .classpath: + * adding .project: + * adding .settings/org.eclipse.jdt.core.prefs: + * adding AUTHORS: + * adding ChangeLog: + * adding LICENSE: + * adding Makefile: + * adding NEWS: + * adding README: + * adding TODO: + * adding class_list.txt: + * adding path_to_rt_jar.txt: + * adding src/PrintClassList.java: + * adding src/PrintPublicMethods.java: + * adding src/PrintTestCoverage.java: + * adding src/ReportGenerator.java: + * adding src/index.html: + * adding src/style.css: + * adding test_directory.txt: + Initial push to this project. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENSE Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,36 @@ +Test coverage tool + + Copyright (C) 2012 Red Hat + +This tool 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, or (at your option) +any later version. + +This tool 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 IcedTea; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,138 @@ +# Test coverage tool. +# +# Copyright (C) 2012 Red Hat +# +# +# +# This tool 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, or (at your option) +# any later version. +# +# This tool 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 this tool; see the file COPYING. If not, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA. +# +# Linking this library statically or dynamically with other modules is +# making a combined work based on this library. Thus, the terms and +# conditions of the GNU General Public License cover the whole +# combination. +# +# As a special exception, the copyright holders of this library give you +# permission to link this library with independent modules to produce an +# executable, regardless of the license terms of these independent +# modules, and to copy and distribute the resulting executable under +# terms of your choice, provided that you also meet, for each linked +# independent module, the terms and conditions of the license of that +# module. An independent module is a module which is not derived from +# or based on this library. If you modify this library, you may extend +# this exception to your version of the library, but you are not +# obligated to do so. If you do not wish to do so, delete this +# exception statement from your version. + +SOURCEPATH=src +CLASSDIR=bin +REPORTDIR=reports +DOCS=docs + +JAVA=java +JAVAC=javac + +# Name of file containing all API classes to be checked by this tool +CLASS_LIST=class_list.txt + +# Name of file where all API classes should be stored +ALL_CLASS_LIST=all_class_list.txt + +# File containing path to directory where (Mauve) test resides +TEST_DIRECTORY_FILE=test_directory.txt +TESTDIR=`cat $(TEST_DIRECTORY_FILE)` + +# Name of file containing path to rt.jar or other tested Java archive +PATH_TO_RT_JAR_FILE=path_to_rt_jar.txt +JARFILE=`cat $(PATH_TO_RT_JAR_FILE)` + + + +all: build report + +report: api_class_list public_method_list tested_method_list gen_report + +clean: + rm -f $(CLASSDIR)/*.class + rm -f $(REPORTDIR)/* + +build: $(CLASSDIR)/PrintClassList.class \ + $(CLASSDIR)/PrintPublicMethods.class \ + $(CLASSDIR)/PrintTestCoverage.class \ + $(CLASSDIR)/ReportGenerator.class + +api_class_list: $(REPORTDIR)/$(ALL_CLASS_LIST) + +public_method_list: $(CLASS_LIST) + if [ ! -f $(CLASS_LIST) ]; \ + then \ + echo "Please create file $(CLASS_LIST) containing list of classes to check"; \ + exit 1; \ + else \ + echo "Ok, file $(CLASS_LIST) exists, go to next step"; \ + fi + while read line; \ + do \ + rm -rf $(REPORTDIR)/$${line}_api.txt; \ + java -cp $(CLASSDIR) PrintPublicMethods $${line} > $(REPORTDIR)/$${line}_api.txt; \ + done < $(CLASS_LIST); + +tested_method_list: $(TEST_DIRECTORY_FILE) + if [ ! -f $(TEST_DIRECTORY_FILE) ]; \ + then \ + echo "Please create file $(TEST_DIRECTORY_FILE) containing path to test directory"; \ + exit 1; \ + else \ + echo "Ok, file $(TEST_DIRECTORY_FILE) exists, go to next step"; \ + fi + if [ ! -d $(TESTDIR) ]; \ + then \ + echo "$(TESTDIR) directory does not exists"; \ + exit 1; \ + else \ + echo "Ok, test directory $(TESTDIR) exists"; \ + fi + while read line; \ + do \ + rm -rf $(REPORTDIR)/$${line}_test.txt; \ + java -cp $(CLASSDIR) PrintTestCoverage $${line} > $(REPORTDIR)/$${line}_test.txt; \ + done < $(CLASS_LIST); + +$(CLASSDIR)/%.class: $(SOURCEPATH)/%.java + $(JAVAC) -d $(CLASSDIR) -sourcepath $(SOURCEPATH)/ $< + +# Target to make the file containing list of all API classes +$(REPORTDIR)/$(ALL_CLASS_LIST): $(PATH_TO_RT_JAR_FILE) + if [ ! -f $(PATH_TO_RT_JAR_FILE) ]; \ + then \ + echo "Please create file $(PATH_TO_RT_JAR_FILE) containing path rt.jar (or other jar)"; \ + exit 1; \ + else \ + echo "Ok, file $(PATH_TO_RT_JAR_FILE) exists, go to next step"; \ + fi + if [ ! -f $(JARFILE) ]; \ + then \ + echo "Jar file $(JARFILE) does not exists"; \ + exit 1; \ + else \ + echo "Ok, Jar file $(JARFILE) exists"; \ + fi + $(JAVA) -cp $(CLASSDIR) PrintClassList `cat $(PATH_TO_RT_JAR_FILE)` > $(REPORTDIR)/$(ALL_CLASS_LIST) + +gen_report: + cp -u $(SOURCEPATH)/index.html $(REPORTDIR) + cp -u $(SOURCEPATH)/style.css $(REPORTDIR) + java -cp $(CLASSDIR) ReportGenerator $(REPORTDIR)/$(ALL_CLASS_LIST) $(CLASS_LIST) $(REPORTDIR) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,52 @@ +Mauve test coverage tool +======================== + +This tool could be used to measure test coverage of Java standard API. +It's based on Mauve tests directory structure, but the tool is able to +work with other type of tests. + + + +How it works: +------------- +1. First of all, full names of all public class from rt.jar archive are + exported to text file (you could use this text file as basis for + creating list of selected API classes). + +2. In the second step the test tool lists all public methods from selected API + classes (this step is based on Reflection API functionality!). + +3. In the third step tests bytecode are investigated and all API calls + are registered (this means, that all tests should be compiled first). + +4. Results from step 2 and step 3 are compared and HTML report is generated. + + + +Building test coverage tool +--------------------------- + +This task is very straighforward, just type: +make build + + + +Running test coverage tool +-------------------------- + +First you need to configure the tool by editing following three +text files: + +path_to_rt_jar.txt: this file should contains full path to rt.jar +test_directory.txt: this file should contains path to test directory +class_list.txt: list of classes to be tested (you usually don't want + to test whole API) + +When these three files are configured, type: + +make report + +Results are stored in directory "reports", you want to open file "index.html" +in web browser. + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/class_list.txt Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,2 @@ +java.math.BigDecimal +java.math.BigInteger
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/path_to_rt_jar.txt Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,2 @@ +/usr/lib/jvm/java/jre/lib/rt.jar +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/PrintClassList.java Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,173 @@ +/* + Test coverage tool. + + Copyright (C) 2012 Red Hat + +This tool 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, or (at your option) +any later version. + +This tool 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 this tool; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. +*/ + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.util.Enumeration; +import java.util.Set; +import java.util.TreeSet; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * Generate and print list of all public classes which are stored in given JAR + * archive. This JAR archive should be accessible through classloader (this + * means that -cp should be used in certain situations). + * + * This tool could be used against "rt.jar" + * + * @author Pavel Tisnovsky + */ +public class PrintClassList { + + /** + * Generate and print sorted list of public classes. + * + * @param pathToJarArchive + * path to rt.jar or other JAR archive to be investigated. + */ + private static void generateClassList(String pathToJarArchive) { + try { + // open the archive and acquire all its entries + JarFile jarFile = new JarFile(pathToJarArchive); + Enumeration<JarEntry> entries = jarFile.entries(); + + // it's better to print sorted class names + // and TreeSet sorted its elements by default + Set<String> setOfClassNames = new TreeSet<String>(); + + // test each JAR entry + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String className = generateClassName(entry.getName()); + // only public classes are interesting + if (isPublicClass(className)) { + setOfClassNames.add(className); + } + } + // now we have the list filled, time to print it + for (String className : setOfClassNames) { + System.out.println(className); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Returns true if given className represents true class and the class is + * public. + * + * @param className + * class name + * @return true if className represents true class and the class is public + */ + @SuppressWarnings("unchecked") + private static boolean isPublicClass(String className) { + try { + Class clazz = Class.forName(className); + // interfaces are not our job + if (clazz.isInterface()) { + return false; + } + int classModifiers = clazz.getModifiers(); + // we are interested only in public classes + if (!Modifier.isPublic(classModifiers)) { + return false; + } + } + catch (ClassNotFoundException e) { + // it might happen because jar file could + // include other files + return false; + } + catch (UnsatisfiedLinkError e) { + // it might happen too + return false; + } + catch (ExceptionInInitializerError e) { + // it might happen too + return false; + } + catch (NoClassDefFoundError e) { + // it might happen too + return false; + } + return true; + } + + /** + * Tries to change given JAR entry into proper class name. + * + * @param name + * JAR entry name + * @return class name transformed from JAR entry name + */ + private static String generateClassName(String name) + { + String out = name; + int postfixIndex = out.indexOf(".class"); + // remove postfix if its present in name + if (postfixIndex > 0) + { + out = out.substring(0, postfixIndex); + } + // change path separator into dot separator + out = out.replace('/', '.'); + // I'm not sure about JAR files created on Windows platform + out = out.replace(File.separatorChar, '.'); + return out; + } + + /** + * Entry point to this tool. + * + * @param args + * first argument should contains path to JAR file. + */ + public static void main(String[] args) { + String pathToRtJar = "/usr/lib/jvm/java-1.6.0/jre/lib/rt.jar"; + if (args.length == 1) { + pathToRtJar = args[0]; + } + System.err.println("Path to Jar file is set to: " + pathToRtJar); + generateClassList(pathToRtJar); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/PrintPublicMethods.java Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,215 @@ +/* + Test coverage tool. + + Copyright (C) 2012 Red Hat + +This tool 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, or (at your option) +any later version. + +This tool 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 this tool; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. +*/ + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Set; +import java.util.TreeSet; + +/** + * This class generates a list containing all public methods and its argument + * types and return type for all classes specified in a file "class_list.txt" + * or in a file specified by a command line parameter. + * + * @author Pavel Tisnovsky + */ +public class PrintPublicMethods +{ + /** + * Get a Class instance for a given class name or null if something goes + * wrong. + * + * @param className + * name of a class (including package name) + * @return Class instance + */ + @SuppressWarnings("unchecked") + private static Class getClass(String className) { + Class clazz = null; + try { + clazz = Class.forName(className); + if (!clazz.isInterface() && Modifier.isPublic(clazz.getModifiers())) { + return clazz; + } + } + catch (ClassNotFoundException e) { + return null; + } + catch (UnsatisfiedLinkError e) { + return null; + } + catch (ExceptionInInitializerError e) { + return null; + } + catch (NoClassDefFoundError e) { + return null; + } + return null; + } + + /** + * Remove "public", "static", "final", "synchronized" and "native" prefixes + * from full method name; + * + * @param methodName + * method name with all prefixes + * @return method name without prefixes + */ + private static String acquireMethodName(String methodName) { + final String[] prefixes = new String[] {"public", "final", "native", "synchronized", "static"}; + String methodNameString = methodName; + for (String prefix : prefixes) { + methodNameString = removePrefix(methodNameString, prefix); + } + return removeThrowsFromDeclaration(methodNameString); + } + + /** + * Remove one given prefix from method name. + * + * @param methodName + * method name that could contains prefix. + * @param prefix + * prefix to be removed. + * @return method name without prefixes + */ + private static String removePrefix(String methodName, String prefix) { + String prefixStr = prefix + " "; + if (methodName.startsWith(prefixStr)) { + return methodName.substring(prefixStr.length()); + } + return methodName; + } + + /** + * Removes throws declaration from method name, i.e.: + * + * <pre> + * void java.lang.Object.wait() throws java.lang.InterruptedException + * </pre> + * + * is changed to: + * + * <pre> + * void java.lang.Object.wait() throws java.lang.InterruptedException + * </pre> + * + * @param methodName + * method name which could contain throws declaration + * @return method name without throws declaration + */ + private static String removeThrowsFromDeclaration(String methodName) { + int throwDeclarationIndex = methodName.indexOf(" throws "); + if (throwDeclarationIndex > 1) { + return methodName.substring(0, throwDeclarationIndex); + } + return methodName; + } + + /** + * Print all public method from given class name (if such class exists). + * + * @param className + * name of a class (including package name) + */ + @SuppressWarnings("unchecked") + private static Set<String> getAllPublicMethodsForClass(String className) { + Set<String> out = new TreeSet<String>(); + Class clazz = getClass(className); + if (clazz == null) { + return out; + } + Method[] methods = clazz.getDeclaredMethods(); + for (Method method : methods) { + if (Modifier.isPublic(method.getModifiers())) { + String methodName = acquireMethodName(method.toString()); + out.add(methodName); + } + } + return out; + } + + @SuppressWarnings("unchecked") + private static Set<String> getAllConstructors(String className) { + Set<String> out = new TreeSet<String>(); + Class clazz = getClass(className); + if (clazz == null) { + return out; + } + Constructor[] constructors = clazz.getConstructors(); + for (Constructor constructor : constructors) { + if (Modifier.isPublic(constructor.getModifiers())) { + String methodName = acquireMethodName(constructor.toString()); + out.add("<init> " + methodName); + } + } + return out; + } + + private static void printAllPublicMethodsAndConstructors(String className) + { + for (String methodSignature : getAllConstructors(className)) + { + System.out.println(methodSignature); + } + for (String methodSignature : getAllPublicMethodsForClass(className)) + { + System.out.println(methodSignature); + } + } + + /** + * Entry point to this tool. + * + * @param args + * first argument could contains path to file containing class + * list. + */ + public static void main(String[] args) { + if (args.length == 1) + { + printAllPublicMethodsAndConstructors(args[0].trim()); + } + else + { + System.err.println("Usage java PrintPublicMethods package.className"); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/PrintTestCoverage.java Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,873 @@ +/* + Test coverage tool. + + Copyright (C) 2012 Red Hat + +This tool 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, or (at your option) +any later version. + +This tool 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 this tool; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. +*/ + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +enum ConstantPoolTag +{ + CONSTANT_Class(7), + CONSTANT_Fieldref(9), + CONSTANT_Methodref(10), + CONSTANT_InterfaceMethodref(11), + CONSTANT_String(8), + CONSTANT_Integer(3), + CONSTANT_Float(4), + CONSTANT_Long(5), + CONSTANT_Double(6), + CONSTANT_NameAndType(12), + CONSTANT_Utf8(1); + + private int value; + + private ConstantPoolTag(int value) + { + this.value = value; + } + + public int getValue() + { + return this.value; + } + + public static ConstantPoolTag intToConstantPoolTag(int value) + { + for (ConstantPoolTag val : values()) + { + if (val.value == value) + { + return val; + } + } + return null; + } +} + +abstract class ConstantPoolRecord +{ + private ConstantPoolTag tag; + + public ConstantPoolRecord(ConstantPoolTag tag) + { + this.tag = tag; + } + + public ConstantPoolTag getTag() + { + return this.tag; + } + + public void setTag(ConstantPoolTag tag) + { + this.tag = tag; + } + + abstract boolean isRecordOccupyingTwoPoolEntries(); + + abstract public String toString(ConstantPoolRecord[] poolEntries); +} + +class ClassRecord extends ConstantPoolRecord +{ + private int classNameIndex; + + public ClassRecord(ConstantPoolTag tag, int classNameIndex) + { + super(tag); + this.setClassNameIndex(classNameIndex); + } + + @Override + boolean isRecordOccupyingTwoPoolEntries() + { + return false; + } + + public void setClassNameIndex(int classNameIndex) + { + this.classNameIndex = classNameIndex; + } + + public int getClassNameIndex() + { + return this.classNameIndex; + } + + @SuppressWarnings("boxing") + @Override + public String toString(ConstantPoolRecord[] poolEntries) + { + String className = getClassName(poolEntries); + return String.format("%2d Class %3d %s", this.getTag().getValue(), getClassNameIndex(), className); + } + + public String getClassName(ConstantPoolRecord[] poolEntries) + { + Utf8Record string = (Utf8Record)poolEntries[this.getClassNameIndex() - 1]; + return string.getString(); + } +} + +class FieldReferenceRecord extends ConstantPoolRecord +{ + private int classIndex; + private int nameTypeIndex; + + public FieldReferenceRecord(ConstantPoolTag tag, int classIndex, int nameTypeIndex) + { + super(tag); + this.setClassIndex(classIndex); + this.setNameTypeIndex(nameTypeIndex); + } + + @Override + boolean isRecordOccupyingTwoPoolEntries() + { + return false; + } + + public void setNameTypeIndex(int nameTypeIndex) + { + this.nameTypeIndex = nameTypeIndex; + } + + public int getNameTypeIndex() + { + return this.nameTypeIndex; + } + + public void setClassIndex(int classIndex) + { + this.classIndex = classIndex; + } + + public int getClassIndex() + { + return this.classIndex; + } + + @SuppressWarnings("boxing") + @Override + public String toString(ConstantPoolRecord[] poolEntries) + { + ClassRecord classRecord = (ClassRecord)poolEntries[this.getClassIndex() - 1]; + NameAndTypeRecord nameAndTypeRecord = (NameAndTypeRecord)poolEntries[this.getNameTypeIndex() -1]; + String className = classRecord.getClassName(poolEntries); + String name = nameAndTypeRecord.getName(poolEntries); + String descriptor = nameAndTypeRecord.getDescriptor(poolEntries); + + return String.format("%2d FieldRef %3d %3d %s.%s:%s", getTag().getValue(), getClassIndex(), getNameTypeIndex(), className, name, descriptor); + } +} + +@SuppressWarnings("boxing") +class MethodReferenceRecord extends ConstantPoolRecord +{ + final static Map<Character, String> typeMap = new HashMap<Character, String>(); + static + { + typeMap.put('B', "byte"); + typeMap.put('C', "char"); + typeMap.put('S', "short"); + typeMap.put('I', "int"); + typeMap.put('J', "long"); + typeMap.put('F', "float"); + typeMap.put('D', "double"); + typeMap.put('Z', "boolean"); + } + + public int classIndex; + public int nameTypeIndex; + + public MethodReferenceRecord(ConstantPoolTag tag, int classIndex, int nameTypeIndex) + { + super(tag); + this.classIndex = classIndex; + this.nameTypeIndex = nameTypeIndex; + } + + @Override + boolean isRecordOccupyingTwoPoolEntries() + { + return false; + } + + private int getClassIndex() + { + return this.classIndex; + } + + private int getNameTypeIndex() + { + return this.nameTypeIndex; + } + + public String getPrintableMethodSignature(ConstantPoolRecord[] poolEntries) + { + NameAndTypeRecord nameAndTypeRecord = (NameAndTypeRecord)poolEntries[this.getNameTypeIndex() -1]; + + String className = getClassName(poolEntries); + String name = nameAndTypeRecord.getName(poolEntries); + String descriptor = nameAndTypeRecord.getDescriptor(poolEntries); + + if (name.equals("<init>")) { + return "<init> " + className + formatParameters(descriptor); + } + return formatReturnType(descriptor) + " " + className + "." + name + formatParameters(descriptor); + } + + private String formatParameters(String descriptor) + { + String params = descriptor.substring(1 + descriptor.indexOf('('), descriptor.indexOf(')')); + StringBuffer out = new StringBuffer(); + int dimensionCount = 0; + + out.append('('); + int i = 0; + while (i < params.length()) + { + char ch = params.charAt(i); + if (typeMap.containsKey(ch)) + { + out.append(typeMap.get(ch)); + while (dimensionCount > 0) + { + dimensionCount--; + out.append("[]"); + } + } + if (ch == 'L') + { + i++; + while (params.charAt(i) != ';') + { + out.append(params.charAt(i) == '/' ? '.' : params.charAt(i)); + i++; + } + } + if (ch == '[') + { + dimensionCount++; + } + else + { + if (i < params.length() - 1) + { + out.append(','); + } + } + i++; + } + out.append(')'); + return out.toString(); + } + + private String formatReturnType(String descriptor) + { + String returnType = descriptor.substring(1 + descriptor.indexOf(')')); + StringBuffer out = new StringBuffer(); + int dimensionCount = 0; + int i = 0; + while (i < returnType.length()) + { + char ch = returnType.charAt(i); + if (typeMap.containsKey(ch)) + { + out.append(typeMap.get(ch)); + while (dimensionCount > 0) + { + dimensionCount--; + out.append("[]"); + } + } + if (ch == '[') + { + dimensionCount++; + } + if (ch == 'L') + { + i++; + while (returnType.charAt(i) != ';') + { + out.append(returnType.charAt(i) == '/' ? '.' : returnType.charAt(i)); + i++; + } + } + i++; + } + return out.toString(); + } + + public String getClassName(ConstantPoolRecord[] poolEntries) + { + ClassRecord classRecord = (ClassRecord)poolEntries[this.getClassIndex() - 1]; + return classRecord.getClassName(poolEntries).replace('/', '.'); + } + + @Override + public String toString(ConstantPoolRecord[] poolEntries) + { + ClassRecord classRecord = (ClassRecord)poolEntries[this.getClassIndex() - 1]; + NameAndTypeRecord nameAndTypeRecord = (NameAndTypeRecord)poolEntries[this.getNameTypeIndex() -1]; + String className = classRecord.getClassName(poolEntries); + String name = nameAndTypeRecord.getName(poolEntries); + String descriptor = nameAndTypeRecord.getDescriptor(poolEntries); + + return String.format("%2d MethodRef %3d %3d %s.%s:%s", getTag().getValue(), getClassIndex(), getNameTypeIndex(), className, name, descriptor); + } +} + +class InterfaceReferenceRecord extends ConstantPoolRecord +{ + public int classIndex; + public int nameTypeIndex; + + public InterfaceReferenceRecord(ConstantPoolTag tag, int classIndex, int nameTypeIndex) + { + super(tag); + this.classIndex = classIndex; + this.nameTypeIndex = nameTypeIndex; + } + + @Override + boolean isRecordOccupyingTwoPoolEntries() + { + return false; + } + + private int getClassIndex() + { + return this.classIndex; + } + + private int getNameTypeIndex() + { + return this.nameTypeIndex; + } + + @SuppressWarnings("boxing") + @Override + public String toString(ConstantPoolRecord[] poolEntries) + { + ClassRecord classRecord = (ClassRecord)poolEntries[this.getClassIndex() - 1]; + NameAndTypeRecord nameAndTypeRecord = (NameAndTypeRecord)poolEntries[this.getNameTypeIndex() -1]; + String className = classRecord.getClassName(poolEntries); + String name = nameAndTypeRecord.getName(poolEntries); + String descriptor = nameAndTypeRecord.getDescriptor(poolEntries); + + return String.format("%2d InterfaceRef %3d %3d %s.%s:%s", getTag().getValue(), getClassIndex(), getNameTypeIndex(), className, name, descriptor); + } +} + +class NameAndTypeRecord extends ConstantPoolRecord +{ + public int nameIndex; + public int descriptorIndex; + + public NameAndTypeRecord(ConstantPoolTag tag, int nameIndex, int descriptorIndex) + { + super(tag); + this.nameIndex = nameIndex; + this.descriptorIndex = descriptorIndex; + } + + public String getName(ConstantPoolRecord[] poolEntries) + { + Utf8Record classNameRecord = (Utf8Record)poolEntries[this.nameIndex - 1]; + return classNameRecord.getString(); + } + + public String getDescriptor(ConstantPoolRecord[] poolEntries) + { + Utf8Record descriptorRecord = (Utf8Record)poolEntries[this.descriptorIndex - 1]; + return descriptorRecord.getString(); + } + + @Override + boolean isRecordOccupyingTwoPoolEntries() + { + return false; + } + + @SuppressWarnings("boxing") + @Override + public String toString(ConstantPoolRecord[] poolEntries) + { + String name = getName(poolEntries); + String descriptor = getDescriptor(poolEntries); + return String.format("%2d Name and type %3d %3d %s %s", this.getTag().getValue(), this.nameIndex, this.descriptorIndex, name, descriptor); + } +} + +class StringRecord extends ConstantPoolRecord +{ + public int stringIndex; + + public StringRecord(ConstantPoolTag tag, int stringIndex) + { + super(tag); + this.stringIndex = stringIndex; + } + + @Override + boolean isRecordOccupyingTwoPoolEntries() + { + return false; + } + + private int getStringIndex() + { + return this.stringIndex; + } + + @SuppressWarnings("boxing") + @Override + public String toString(ConstantPoolRecord[] poolEntries) + { + Utf8Record utf8string = (Utf8Record) (poolEntries[this.getStringIndex() - 1]); + String string = utf8string.getString(); + return String.format("%2d Class %3d %s", getTag().getValue(), getStringIndex(), string); + } +} + +class IntegerRecord extends ConstantPoolRecord +{ + public int integerConstant; + + public IntegerRecord(ConstantPoolTag tag, int integerConstant) + { + super(tag); + this.integerConstant = integerConstant; + } + + @Override + boolean isRecordOccupyingTwoPoolEntries() + { + return false; + } + + @SuppressWarnings("boxing") + @Override + public String toString(ConstantPoolRecord[] poolEntries) + { + return String.format("%2d %-26s %d", getTag().getValue(), "Integer", this.integerConstant); + } +} + +class LongRecord extends ConstantPoolRecord +{ + public long longConstant; + + public LongRecord(ConstantPoolTag tag, int high, int low) + { + super(tag); + this.longConstant = (((long) high) << 32) + low; + } + + @Override + boolean isRecordOccupyingTwoPoolEntries() + { + return true; + } + + @SuppressWarnings("boxing") + @Override + public String toString(ConstantPoolRecord[] poolEntries) + { + return String.format("%2d %-26s %Ld", getTag().getValue(), "Long", this.longConstant); + } +} + +class FloatRecord extends ConstantPoolRecord +{ + public float floatConstant; + + public FloatRecord(ConstantPoolTag tag, int bits) + { + super(tag); + this.floatConstant = Float.intBitsToFloat(bits); + } + + @Override + boolean isRecordOccupyingTwoPoolEntries() + { + return false; + } + + @SuppressWarnings("boxing") + @Override + public String toString(ConstantPoolRecord[] poolEntries) + { + return String.format("%2d %-26s %f", getTag().getValue(), "Float", this.floatConstant); + } +} + +class DoubleRecord extends ConstantPoolRecord +{ + public double doubleConstant; + + public DoubleRecord(ConstantPoolTag tag, int highbits, int lowbits) + { + super(tag); + long val = (((long) highbits) << 32) + lowbits; + this.doubleConstant = Double.longBitsToDouble(val); + } + + @Override + boolean isRecordOccupyingTwoPoolEntries() + { + return true; + } + + @SuppressWarnings("boxing") + @Override + public String toString(ConstantPoolRecord[] poolEntries) + { + return String.format("%2d %-26s %f", getTag().getValue(), "Double", this.doubleConstant); + } +} + +class Utf8Record extends ConstantPoolRecord +{ + public String string; + + public Utf8Record(ConstantPoolTag tag, String string) + { + super(tag); + this.string= string; + } + + public String getString() + { + return this.string; + } + + @Override + boolean isRecordOccupyingTwoPoolEntries() + { + return false; + } + + @SuppressWarnings("boxing") + @Override + public String toString(ConstantPoolRecord[] poolEntries) + { + return String.format("%2d String \"%s\"", getTag().getValue(), this.getString()); + } +} + +/** + * + * @author Pavel Tisnovsky + */ +public class PrintTestCoverage +{ + /** + * Name of file containing directory to tests. + */ + private static final String TEST_DIRECTORY = "test_directory.txt"; + + private static int readByte(FileInputStream fin) throws IOException + { + return fin.read(); + } + + private static int readTwoBytes(FileInputStream fin) throws IOException + { + int i1 = readByte(fin); + int i2 = readByte(fin); + return (i1 << 8) | (i2); + } + + private static int readFourBytes(FileInputStream fin) throws IOException + { + int i1 = readByte(fin); + int i2 = readByte(fin); + int i3 = readByte(fin); + int i4 = readByte(fin); + return (i1 << 24) | (i2 << 16) | (i3 << 8) | (i4); + } + + @SuppressWarnings("boxing") + private static void processMagicNumber(FileInputStream fin) throws Exception + { + int magic = readFourBytes(fin); + System.err.format("Magic constant: 0x%x\n", magic); + if (magic != 0xcafebabe) + { + throw new Exception("Improper magic constant"); + } + } + + @SuppressWarnings("boxing") + private static void processClassVersion(FileInputStream fin) throws IOException + { + int minorVersion = readTwoBytes(fin); + int majorVersion = readTwoBytes(fin); + System.err.format("Major version: %d\n", majorVersion); + System.err.format("Minor version: %d\n", minorVersion); + } + + private static ConstantPoolRecord readConstantPoolEntry(FileInputStream fin) throws IOException + { + ConstantPoolTag tag = ConstantPoolTag.intToConstantPoolTag(readByte(fin)); + switch (tag) + { + case CONSTANT_Class: + { + int classNameIndex = readTwoBytes(fin); + return new ClassRecord(tag, classNameIndex); + } + case CONSTANT_Fieldref: + { + int classIndex = readTwoBytes(fin); + int nameTypeIndex = readTwoBytes(fin); + return new FieldReferenceRecord(tag, classIndex, nameTypeIndex); + } + case CONSTANT_InterfaceMethodref: + { + int classIndex = readTwoBytes(fin); + int nameTypeIndex = readTwoBytes(fin); + return new InterfaceReferenceRecord(tag, classIndex, nameTypeIndex); + } + case CONSTANT_Methodref: + { + int classIndex = readTwoBytes(fin); + int nameTypeIndex = readTwoBytes(fin); + return new MethodReferenceRecord(tag, classIndex, nameTypeIndex); + } + case CONSTANT_NameAndType: + { + int nameIndex = readTwoBytes(fin); + int descriptorIndex = readTwoBytes(fin); + return new NameAndTypeRecord(tag, nameIndex, descriptorIndex); + } + case CONSTANT_String: + { + int stringIndex = readTwoBytes(fin); + return new StringRecord(tag, stringIndex); + } + case CONSTANT_Integer: + { + int constant = readFourBytes(fin); + return new IntegerRecord(tag, constant); + } + case CONSTANT_Long: + { + int high = readFourBytes(fin); + int low = readFourBytes(fin); + return new LongRecord(tag, high, low); + } + case CONSTANT_Float: + { + int bits = readFourBytes(fin); + return new FloatRecord(tag, bits); + } + case CONSTANT_Double: + { + int highbits = readFourBytes(fin); + int lowbits = readFourBytes(fin); + return new DoubleRecord(tag, highbits, lowbits); + } + case CONSTANT_Utf8: + { + int length = readTwoBytes(fin); + StringBuffer buf = new StringBuffer(length); + for (int i = 0; i < length; i++) + { + buf.append((char)readByte(fin)); + } + return new Utf8Record(tag, buf.toString()); + } + default: + System.out.println("Unknown tag " + tag); + return null; + } + } + + @SuppressWarnings("boxing") + private static ConstantPoolRecord[] processContantPool(FileInputStream fin) throws IOException + { + ConstantPoolRecord[] poolEntries = null; + + int constantPoolCount = readTwoBytes(fin) - 1; + + System.err.format("\nConstant pool size: %d\n", constantPoolCount); + poolEntries = new ConstantPoolRecord[constantPoolCount]; + + int i = 0; + while (i < constantPoolCount) + { + ConstantPoolRecord record = readConstantPoolEntry(fin); + poolEntries[i] = record; + i += record.isRecordOccupyingTwoPoolEntries() ? 2 : 1; + //System.err.println(i + "\t" + record.getTag()); + } + + return poolEntries; + } + + @SuppressWarnings("unused") + private static void printConstantPool(ConstantPoolRecord[] poolEntries) + { + for (ConstantPoolRecord record : poolEntries) + { + if (record != null && !(record instanceof Utf8Record)) + { + System.out.println(record.toString(poolEntries)); + } + } + } + + private static void printMethodList(String testedClassName, ConstantPoolRecord[] poolEntries, Set<String> methodSet) + { + for (ConstantPoolRecord record : poolEntries) + { + if (record != null && (record instanceof MethodReferenceRecord)) + { + MethodReferenceRecord rec = (MethodReferenceRecord) record; + if (testedClassName.equals(rec.getClassName(poolEntries))) + { + methodSet.add(rec.getPrintableMethodSignature(poolEntries)); + } + } + } + } + + private static void printTestCoverageForOneTestClass(String testedClassName, File directory, String fileName, Set<String> methodSet) + { + FileInputStream fin = null; + try + { + fin = new FileInputStream(new File(directory, fileName)); + try + { + processMagicNumber(fin); + processClassVersion(fin); + ConstantPoolRecord[] poolEntries = processContantPool(fin); + //printConstantPool(poolEntries); + printMethodList(testedClassName, poolEntries, methodSet); + } + catch (Exception e) + { + e.printStackTrace(); + } + finally + { + try + { + fin.close(); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + catch (FileNotFoundException e) + { + e.printStackTrace(); + } + } + + private static void printTestCoverage(String pathToTests, String testedClassName) + { + Set<String> methodSet = new TreeSet<String>(); + String className = testedClassName.replace('.', '/'); + File list = new File(pathToTests, className); + if (!list.isDirectory()) + { + throw new RuntimeException(list.getAbsolutePath() + " is not a proper directory name"); + } + for (String fileName : list.list()) + { + if (fileName.endsWith(".class")) + { + System.err.println("Checked file: " + fileName); + printTestCoverageForOneTestClass(testedClassName, list, fileName, methodSet); + } + } + for (String method : methodSet) + { + System.out.println(method); + } + } + + private static String readPathToTest() throws IOException + { + BufferedReader fin = new BufferedReader(new FileReader(TEST_DIRECTORY)); + String directory = fin.readLine(); + return directory; + } + + private static void doPrintTestCoverage(String className) + { + String pathToTests = null; + try + { + pathToTests = readPathToTest(); + printTestCoverage(pathToTests, className); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + public static void main(String[] args) + { + if (args.length == 1) + { + String className = args[0].trim(); + doPrintTestCoverage(className); + } + else + { + System.err.println("Usage java PrintTestCoverage package.className"); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/ReportGenerator.java Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,426 @@ +/* + Test coverage tool. + + Copyright (C) 2012 Red Hat + +This tool 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, or (at your option) +any later version. + +This tool 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 this tool; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. +*/ + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Set; +import java.util.TreeSet; + + + +/** + * Report generator which process .txt files generated by PrintPublicMethods and + * PrintTestCoverage classes. + * + * @author Pavel Tisnovsky + */ +public class ReportGenerator +{ + private static Set<String> readAllClasses(String allClassListFileName) + { + BufferedReader reader = null; + Set<String> allClasses = new TreeSet<String>(); + try + { + reader = new BufferedReader(new FileReader(allClassListFileName)); + String line; + while ((line = reader.readLine()) != null) + { + allClasses.add(line); + } + } + catch (FileNotFoundException e) + { + e.printStackTrace(); + } + catch (IOException e) + { + e.printStackTrace(); + } + finally + { + try + { + if (reader != null) + { + reader.close(); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + } + return allClasses; + } + + private static Set<String> preparePackageNames(Set<String> allClasses) + { + Set<String> packages = new TreeSet<String>(); + for (String className : allClasses) + { + String packageName = className.substring(0, className.lastIndexOf('.')); + if (!packageName.startsWith("com.") && !packageName.startsWith("sun")) + { + packages.add(packageName); + } + } + return packages; + } + + private static void printPackageListToFile(String reportDirectory, Set<String> allClasses, Set<String> packageNames) + { + BufferedWriter fout = null; + try + { + fout = new BufferedWriter(new FileWriter(new File(reportDirectory, "all_packages.html"))); + fout.write("<html>\n"); + fout.write("<body>\n"); + fout.write("<h1>Package list</h1>\n"); + fout.write("<a target='ClassesListFrame' href='all_classes.html'>all classes</a><br /><br />\n"); + for (String packageName : packageNames) + { + fout.write("<a target='ClassesListFrame' href='" + packageName + ".html'>" + packageName + "</a><br />\n"); + } + fout.write("</body>\n"); + fout.write("</html>\n"); + } + catch (IOException e) + { + e.printStackTrace(); + } + finally + { + try + { + if (fout != null) + { + fout.close(); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + + private static void createFileForPackage(String reportDirectory, String packageName, Set<String> testedClasses) + { + BufferedWriter fout = null; + try + { + fout = new BufferedWriter(new FileWriter(new File(reportDirectory, packageName + ".html"))); + fout.write("<html>\n"); + fout.write("<body>\n"); + fout.write("<h1>Class list</h1>\n"); + for (String className : testedClasses) + { + if (className.startsWith(packageName)) + { + fout.write("<a target='ResultsFrame' href='" + className + ".html'>" + className + "</a><br>\n"); + } + } + fout.write("</body>\n"); + fout.write("</html>\n"); + } + catch (IOException e) + { + e.printStackTrace(); + } + finally + { + try + { + if (fout != null) + { + fout.close(); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + + private static void createFileForClass(String reportDirectory, String testClass, Set<String> allMethods, Set<String> apiMethods, Set<String> testedMethods) + { + BufferedWriter fout = null; + try + { + fout = new BufferedWriter(new FileWriter(new File(reportDirectory, testClass + ".html"))); + fout.write("<html>\n"); + fout.write("<head>\n"); + fout.write("<title>" + testClass + "</title>\n"); + fout.write("</head>\n"); + fout.write("<body>\n"); + fout.write("<h1>Class " + testClass + "</h1>\n"); + fout.write("<table>\n"); + fout.write("<tr><th>Method</th><th><a href='"+testClass+"_api.txt'>API</a></th><th><a href='"+testClass+"_test.txt'>Tested</a></th></tr>\n"); + for (String methodName : allMethods) + { + fout.write("<tr><td>" + constructPrintedMethodName(methodName) + "</td>"); + fout.write(printMethodCoverage(methodName, apiMethods)); + fout.write(printMethodCoverage(methodName, testedMethods)); + fout.write("</tr>\n"); + } + fout.write("</table>\n"); + fout.write("</body>\n"); + fout.write("</html>\n"); + } + catch (IOException e) + { + e.printStackTrace(); + } + finally + { + try + { + if (fout != null) + { + fout.close(); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + + private static String constructPrintedMethodName(String methodName) + { + String printedMethodName = methodName.replace("<", "<").replace(">", ">"); + int returnTypeEnds = printedMethodName.indexOf(' '); + int parametersBegin = printedMethodName.indexOf('('); + String returnType = printedMethodName.substring(0, printedMethodName.indexOf(' ')); + String name = printedMethodName.substring(returnTypeEnds, parametersBegin); + String params = printedMethodName.substring(parametersBegin); + + return String.format( + "<span style='color:#000080'>%s</span>" + + "<span style='color:#008000'>%s</span>" + + "<span style='color:#804000'>%s</span>", returnType, name, params); + } + + private static String printMethodCoverage(String methodName, Set<String> methodSet) + { + if (methodSet.contains(methodName)) + { + return "<td style='background:#80ff80'>present</td>"; + } + return "<td style='background:#ff8080'>absent</td>"; + } + + private static Set<String> prepareUsedPackageNames(Set<String> allPackageNames, Set<String> testedClasses) + { + Set<String> out = new TreeSet<String>(); + for (String packageName : allPackageNames) + { + for (String testClass : testedClasses) + { + if (testClass.startsWith(packageName)) + { + out.add(packageName); + break; + } + } + } + return out; + } + + private static void printReportForAllPackages(String reportDirectory, Set<String> usedPackageNames, + Set<String> testedClasses) + { + for (String packageName : usedPackageNames) + { + createFileForPackage(reportDirectory, packageName, testedClasses); + } + } + + private static void printReportForAllClasses(String reportDirectory, Set<String> testedClasses) + { + for (String testedClass : testedClasses) + { + Set<String> apiMethods = readApiMethods(reportDirectory, testedClass); + Set<String> testedMethods = readTestedMethods(reportDirectory, testedClass); + Set<String> allMethods = new TreeSet<String>(); + allMethods.addAll(apiMethods); + allMethods.addAll(testedMethods); + createFileForClass(reportDirectory, testedClass, allMethods, apiMethods, testedMethods); + } + } + + private static Set<String> readApiMethods(String reportDirectory, String testedClass) + { + File fileName = new File(reportDirectory, testedClass + "_api.txt"); + return readMethods(fileName); + } + + private static Set<String> readTestedMethods(String reportDirectory, String testedClass) + { + File fileName = new File(reportDirectory, testedClass + "_test.txt"); + return readMethods(fileName); + } + + private static Set<String> readMethods(File fileName) + { + BufferedReader reader = null; + Set<String> allMethods = new TreeSet<String>(); + try + { + reader = new BufferedReader(new FileReader(fileName)); + String line; + while ((line = reader.readLine()) != null) + { + allMethods.add(line); + } + } + catch (FileNotFoundException e) + { + e.printStackTrace(); + } + catch (IOException e) + { + e.printStackTrace(); + } + finally + { + try + { + if (reader != null) + { + reader.close(); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + } + return allMethods; + } + + private static void printReportForAllClassesInOneFile(String reportDirectory, Set<String> usedPackageNames, + Set<String> testedClasses) + { + BufferedWriter fout = null; + try + { + fout = new BufferedWriter(new FileWriter(new File(reportDirectory, "all_classes.html"))); + fout.write("<html>\n"); + fout.write("<body>\n"); + fout.write("<h1>Classes list</h1>"); + for (String packageName : usedPackageNames) + { + fout.write("<h2>Package " + packageName + "</h2>\n"); + for (String className : testedClasses) + { + if (className.startsWith(packageName)) + { + fout.write("<a target='ResultsFrame' href='" + className + ".html'>" + className + "</a><br>\n"); + } + } + } + fout.write("</body>\n"); + fout.write("</html>\n"); + } + catch (IOException e) + { + e.printStackTrace(); + } + finally + { + try + { + if (fout != null) + { + fout.close(); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + } + } + + private static void prepareReport(String allClassListFileName, String testedClassListFileName, + String reportDirectory) + { + Set<String> allClasses = readAllClasses(allClassListFileName); + Set<String> testedClasses = readAllClasses(testedClassListFileName); + Set<String> allPackageNames = preparePackageNames(allClasses); + Set<String> usedPackageNames = prepareUsedPackageNames(allPackageNames, testedClasses); + + System.out.println("All class list: " + allClassListFileName); + System.out.println("Read " + allClasses.size() + " class names"); + + System.out.println("Tested class list: " + testedClassListFileName); + System.out.println("Read " + testedClasses.size() + " class names"); + + System.out.println("Setting list of " + allPackageNames.size() + " all package names"); + System.out.println("Setting list of " + usedPackageNames.size() + " used package names"); + + System.out.println("Report directory: " + reportDirectory); + + printPackageListToFile(reportDirectory, allClasses, usedPackageNames); + printReportForAllClassesInOneFile(reportDirectory, usedPackageNames, testedClasses); + printReportForAllPackages(reportDirectory, usedPackageNames, testedClasses); + printReportForAllClasses(reportDirectory, testedClasses); + } + + public static void main(String[] args) + { + if (args.length != 3) + { + System.err.println("Usage allClassListFileName classListFileName reportDirectory"); + System.exit(1); + } + String allClassListFileName = args[0]; + String testedClassListFileName = args[1]; + String reportDirectory = args[2]; + prepareReport(allClassListFileName, testedClassListFileName, reportDirectory); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/index.html Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,20 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd"> +<html> + <head> + <title>Test coverage report</title> + </head> + <frameset cols="20%,80%" title=""> + <frameset rows="40%,60%" title=""> + <frame src="all_packages.html" name="PackageListFrame" title="Package List" scrolling="yes"> + <frame src="all_classes.html" name="ClassesListFrame" title="All public classes" scrolling="yes"> + </frameset> + <frame src="all_results.html" name="ResultsFrame" title="Test coverage" scrolling="yes"> + <noframes> + <h2>Frame Alert</h2> + <p>This document is designed to be viewed using the frames + feature. If you see this message, you are using a + non-frame-capable web client.</p> + </noframes> + </frameset> +</html> +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/style.css Wed Jan 04 11:20:55 2012 +0100 @@ -0,0 +1,27 @@ +body {font-family: arial, helvetica, sans-serif; color:#000000; text-align:justify; background-color:#ffffff; margin-left: 0px; margin-top: 0px} +h1 {font-family: arial, helvetica, sans-serif; color:#000000; background:#80a0a0; text-align:center; padding-left: 1em; margin: 0} +h2 {font-family: arial, helvetica, sans-serif; color:#000000; background:#80a0a0; padding-left: 1em; padding-right:1cm} +h3 {font-family: arial, helvetica, sans-serif; color:#000000; background:#a0a080; padding-left: 1em; padding-right:1cm} +h4 {font-family: arial, helvetica, sans-serif; color:#000000; background:#c0c0a0; padding-left: 1em; padding-right:1cm; margin-bottom: 5px} +a {font-family: arial, helvetica, sans-serif; color:#0000ff; text-decoration:none} +a:link {color:#0000ff} +a:visited {color:#0000ff} +a:visited {color:#0000ff} +a:hover {color:#ffffff; background:#404040} +p {font-family: arial, helvetica, sans-serif; color:#000000; text-align:justify; padding-left:1em; padding-right:1em} +li {font-family: arial, helvetica, sans-serif; color:#000000; text-align:justify} +pre {} +tr {font-family: arial, helvetica, sans-serif; text-align:left} +td {font-family: arial, helvetica, sans-serif; text-align:left} +td.center {font-family: arial, helvetica, sans-serif; text-align:center} +th.center {font-family: arial, helvetica, sans-serif; text-align:center} + +.forms {background-color: #f0f0dd; vertical-align: top; width: 720px; border-collapse: collapse; border-color:#808080; margin-left:32px} + +.present-method {background-color:#006000} +.absent-method {background-color:#600000} + +.method-return-type {} +.method-name {} +.method-params {} +