view src/PrintTestCoverage.java @ 2:8612fcdfab82

2012-01-06 Pavel Tisnovsky <ptisnovs@redhat.com> * src/PrintClassList.java: Fixed: closing Java archive when it is read Refactoring, added JavaDoc to all methods * src/PrintTestCoverage.java: Fixed exception thrown if the log file does not exists It's ok because some classes are not covered by tests at all.
author Pavel Tisnovsky <ptisnovs@redhat.com>
date Fri, 06 Jan 2012 12:25:10 +0100
parents 29318fe8d6d0
children 61f453c6b172
line wrap: on
line source

/*
  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);

        /* check if directory containing tests for given class name exists */
        if (!list.isDirectory())
        {
            System.err.println(list.getAbsolutePath() + " is not a proper directory name");
            return;
        }
        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");
        }
    }

}