Mercurial > hg > MauveTestCoverage
changeset 5:5ef74c026b81
2012-01-17 Pavel Tisnovsky <ptisnovs@redhat.com>
* src/FileUtils.java:
Added new static methods used by other classes.
* src/PrintTestCoverage.java:
Added check if the .class file is really Mauve test.
More refactoring and Javadoc.
author | Pavel Tisnovsky <ptisnovs@redhat.com> |
---|---|
date | Tue, 17 Jan 2012 14:02:07 +0100 |
parents | 730c5549c0f9 |
children | 5849d5bfbee0 |
files | ChangeLog src/FileUtils.java src/PrintTestCoverage.java |
diffstat | 3 files changed, 375 insertions(+), 66 deletions(-) [+] |
line wrap: on
line diff
--- a/ChangeLog Tue Jan 10 14:08:47 2012 +0100 +++ b/ChangeLog Tue Jan 17 14:02:07 2012 +0100 @@ -1,3 +1,11 @@ +2012-01-17 Pavel Tisnovsky <ptisnovs@redhat.com> + + * src/FileUtils.java: + Added new static methods used by other classes. + * src/PrintTestCoverage.java: + Added check if the .class file is really Mauve test. + More refactoring and Javadoc. + 2012-01-10 Pavel Tisnovsky <ptisnovs@redhat.com> * src/FileUtils.java:
--- a/src/FileUtils.java Tue Jan 10 14:08:47 2012 +0100 +++ b/src/FileUtils.java Tue Jan 17 14:02:07 2012 +0100 @@ -38,7 +38,9 @@ import java.io.BufferedReader; import java.io.BufferedWriter; +import java.io.EOFException; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; @@ -54,6 +56,11 @@ class FileUtils { /** + * EOF constant used as special return value in some methods. + */ + private static final int EOF = -1; + + /** * Read content of given text file and return it as list of strings. No * exception is thrown during reading. * @@ -222,6 +229,7 @@ */ private static void writeAllLinesToTextFile(BufferedWriter bufferedWriter, List<String> lines) throws IOException { + // for all lines for (String line : lines) { bufferedWriter.write(line); @@ -230,4 +238,60 @@ } } + /** + * Read one byte from given file input stream with check if EOF is + * reached. + * + * @param fileInputStream instance of already opened FileInputStream + * @return + * @throws IOException + * thrown if an I/O error occurs + */ + static int readOneByte(FileInputStream fileInputStream) throws IOException + { + int i = fileInputStream.read(); + // -1 means that EOF is reached + if (i == EOF) + { + throw new EOFException("End of file reached!"); + } + return i; + } + + /** + * Read two bytes from given file input stream with check if EOF is + * reached. + * + * @param fileInputStream instance of already opened FileInputStream + * @return + * @throws IOException + * thrown if an I/O error occurs + */ + static int readTwoBytes(FileInputStream fileInputStream) throws IOException + { + int i1 = readOneByte(fileInputStream); + int i2 = readOneByte(fileInputStream); + // combine all two read bytes into a word + return (i1 << 8) | (i2); + } + + /** + * Read four bytes from given file input stream with check if EOF is + * reached. + * + * @param fileInputStream instance of already opened FileInputStream + * @return + * @throws IOException + * thrown if an I/O error occurs + */ + static int readFourBytes(FileInputStream fileInputStream) throws IOException + { + int i1 = readOneByte(fileInputStream); + int i2 = readOneByte(fileInputStream); + int i3 = readOneByte(fileInputStream); + int i4 = readOneByte(fileInputStream); + // combine all four read bytes into a word + return (i1 << 24) | (i2 << 16) | (i3 << 8) | (i4); + } + }
--- a/src/PrintTestCoverage.java Tue Jan 10 14:08:47 2012 +0100 +++ b/src/PrintTestCoverage.java Tue Jan 17 14:02:07 2012 +0100 @@ -47,6 +47,14 @@ import java.util.Set; import java.util.TreeSet; + + +/** + * This enumeration represents all record types which could be stored in a + * constant pool. + * + * @author Pavel Tisnovsky + */ enum ConstantPoolTag { CONSTANT_Class(7), @@ -61,18 +69,38 @@ CONSTANT_NameAndType(12), CONSTANT_Utf8(1); + /** + * Value represented one enumeration item. + */ private int value; + /** + * Constructor + * + * @param value + * enumeration value + */ private ConstantPoolTag(int value) { this.value = value; } + /** + * Getter for a field value. + * + * @return value of a field named value + */ public int getValue() { return this.value; } + /** + * Converts integer value to a enumeration item. + * + * @param value integer value + * @return selected enumeration item or null + */ public static ConstantPoolTag intToConstantPoolTag(int value) { for (ConstantPoolTag val : values()) @@ -86,27 +114,65 @@ } } +/** + * Abstract class which represents any constant pool record. + * + * @author Pavel Tisnovsky + */ abstract class ConstantPoolRecord { + /** + * Tag associated with each constant pool record. + */ private ConstantPoolTag tag; + /** + * Constructor common for all subclasses. + * + * @param tag + * tag associated with each constant pool record. + */ public ConstantPoolRecord(ConstantPoolTag tag) { this.tag = tag; } + /** + * Get tag associated with each constant pool record. + * + * @return value of the field 'tag' + */ public ConstantPoolTag getTag() { return this.tag; } + /** + * Set tag associated with each constant pool record. + * + * @param tag + * new value of the field 'tag' + */ public void setTag(ConstantPoolTag tag) { this.tag = tag; } + /** + * Returns true if this record occupies two constant pool entries. Only such + * entries storing long and double values need to return true. + * + * @return true if this record occupies two constant pool entries + */ abstract boolean isRecordOccupyingTwoPoolEntries(); + /** + * Converts constant pool entry into a printable string. + * + * @param poolEntries + * whole constant pool + * @return constant pool entry represented as a printable string + */ abstract public String toString(ConstantPoolRecord[] poolEntries); } @@ -599,42 +665,26 @@ } } + + /** - * + * This class is used to find out, which API methods are called from given + * tests. Bytecode is investigated: this means this tool is based on static + * coverage checking. + * * @author Pavel Tisnovsky */ public class PrintTestCoverage { /** - * Name of file containing directory to tests. + * Default 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); + int magic = FileUtils.readFourBytes(fin); System.err.format("Magic constant: 0x%x\n", magic); if (magic != 0xcafebabe) { @@ -645,80 +695,80 @@ @SuppressWarnings("boxing") private static void processClassVersion(FileInputStream fin) throws IOException { - int minorVersion = readTwoBytes(fin); - int majorVersion = readTwoBytes(fin); + int minorVersion = FileUtils.readTwoBytes(fin); + int majorVersion = FileUtils.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)); + ConstantPoolTag tag = ConstantPoolTag.intToConstantPoolTag(FileUtils.readOneByte(fin)); switch (tag) { case CONSTANT_Class: { - int classNameIndex = readTwoBytes(fin); + int classNameIndex = FileUtils.readTwoBytes(fin); return new ClassRecord(tag, classNameIndex); } case CONSTANT_Fieldref: { - int classIndex = readTwoBytes(fin); - int nameTypeIndex = readTwoBytes(fin); + int classIndex = FileUtils.readTwoBytes(fin); + int nameTypeIndex = FileUtils.readTwoBytes(fin); return new FieldReferenceRecord(tag, classIndex, nameTypeIndex); } case CONSTANT_InterfaceMethodref: { - int classIndex = readTwoBytes(fin); - int nameTypeIndex = readTwoBytes(fin); + int classIndex = FileUtils.readTwoBytes(fin); + int nameTypeIndex = FileUtils.readTwoBytes(fin); return new InterfaceReferenceRecord(tag, classIndex, nameTypeIndex); } case CONSTANT_Methodref: { - int classIndex = readTwoBytes(fin); - int nameTypeIndex = readTwoBytes(fin); + int classIndex = FileUtils.readTwoBytes(fin); + int nameTypeIndex = FileUtils.readTwoBytes(fin); return new MethodReferenceRecord(tag, classIndex, nameTypeIndex); } case CONSTANT_NameAndType: { - int nameIndex = readTwoBytes(fin); - int descriptorIndex = readTwoBytes(fin); + int nameIndex = FileUtils.readTwoBytes(fin); + int descriptorIndex = FileUtils.readTwoBytes(fin); return new NameAndTypeRecord(tag, nameIndex, descriptorIndex); } case CONSTANT_String: { - int stringIndex = readTwoBytes(fin); + int stringIndex = FileUtils.readTwoBytes(fin); return new StringRecord(tag, stringIndex); } case CONSTANT_Integer: { - int constant = readFourBytes(fin); + int constant = FileUtils.readFourBytes(fin); return new IntegerRecord(tag, constant); } case CONSTANT_Long: { - int high = readFourBytes(fin); - int low = readFourBytes(fin); + int high = FileUtils.readFourBytes(fin); + int low = FileUtils.readFourBytes(fin); return new LongRecord(tag, high, low); } case CONSTANT_Float: { - int bits = readFourBytes(fin); + int bits = FileUtils.readFourBytes(fin); return new FloatRecord(tag, bits); } case CONSTANT_Double: { - int highbits = readFourBytes(fin); - int lowbits = readFourBytes(fin); + int highbits = FileUtils.readFourBytes(fin); + int lowbits = FileUtils.readFourBytes(fin); return new DoubleRecord(tag, highbits, lowbits); } case CONSTANT_Utf8: { - int length = readTwoBytes(fin); + int length = FileUtils.readTwoBytes(fin); StringBuffer buf = new StringBuffer(length); for (int i = 0; i < length; i++) { - buf.append((char)readByte(fin)); + buf.append((char)FileUtils.readOneByte(fin)); } return new Utf8Record(tag, buf.toString()); } @@ -733,7 +783,7 @@ { ConstantPoolRecord[] poolEntries = null; - int constantPoolCount = readTwoBytes(fin) - 1; + int constantPoolCount = FileUtils.readTwoBytes(fin) - 1; System.err.format("\nConstant pool size: %d\n", constantPoolCount); poolEntries = new ConstantPoolRecord[constantPoolCount]; @@ -750,6 +800,65 @@ return poolEntries; } + /** + * Read and returns flags set for the class + * + * @param fin + * input stream for the bytecode + * @param poolEntries + * constant pool entries + * @return flags set for the class + * @throws IOException + */ + private static int processClassFlags(FileInputStream fin, ConstantPoolRecord[] poolEntries) throws IOException + { + // read flags set for the class + int flags = FileUtils.readTwoBytes(fin); + return flags; + } + + /** + * @param fin + * @param poolEntries + * @return + * @throws IOException + */ + private static String readClassOrSuperclassOrInterfaceName(FileInputStream fin, ConstantPoolRecord[] poolEntries) + throws IOException + { + // read index into constant pool + int index = FileUtils.readTwoBytes(fin); + // constant pool array is indexed from zero, but in bytecode, one-based indexing is used + index--; + ConstantPoolRecord poolRecord = poolEntries[index]; + if (poolRecord instanceof ClassRecord) + { + return ((ClassRecord)poolRecord).getClassName(poolEntries); + } + return null; + } + + private static String processClassName(FileInputStream fin, ConstantPoolRecord[] poolEntries) throws IOException + { + return readClassOrSuperclassOrInterfaceName(fin, poolEntries); + } + + private static String processSuperclassName(FileInputStream fin, ConstantPoolRecord[] poolEntries) throws IOException + { + return readClassOrSuperclassOrInterfaceName(fin, poolEntries); + } + + private static Set<String> processAllImplementedInterfaces(FileInputStream fin, ConstantPoolRecord[] poolEntries) throws IOException + { + int interfacesCount = FileUtils.readTwoBytes(fin); + Set<String> interfaceNames = new TreeSet<String>(); + for (int i = 0; i < interfacesCount; i++) + { + interfaceNames.add(readClassOrSuperclassOrInterfaceName(fin, poolEntries)); + } + return interfaceNames; + } + @SuppressWarnings("unused") private static void printConstantPool(ConstantPoolRecord[] poolEntries) { @@ -762,7 +871,7 @@ } } - private static void printMethodList(String testedClassName, ConstantPoolRecord[] poolEntries, Set<String> methodSet) + private static void determineAllCalledAPIMethods(String testedClassName, ConstantPoolRecord[] poolEntries, Set<String> methodSet) { for (ConstantPoolRecord record : poolEntries) { @@ -777,7 +886,7 @@ } } - private static void printTestCoverageForOneTestClass(String testedClassName, File directory, String fileName, Set<String> methodSet) + private static void determineTestCoverageForOneTestClass(String testedClassName, File directory, String fileName, Set<String> methodSet) { FileInputStream fin = null; try @@ -785,11 +894,7 @@ fin = new FileInputStream(new File(directory, fileName)); try { - processMagicNumber(fin); - processClassVersion(fin); - ConstantPoolRecord[] poolEntries = processContantPool(fin); - //printConstantPool(poolEntries); - printMethodList(testedClassName, poolEntries, methodSet); + processAllRelevantBytecodeParts(testedClassName, methodSet, fin); } catch (Exception e) { @@ -813,45 +918,176 @@ } } + /** + * Read all relevant parts of bytecode from a .class file. + * + * @param testedClassName + * tested class name. + * @param methodSet + * set to which all method should be stored. + * @param fileInputStream + * instance of FileInputStream opened for reading contents of + * bytecode. + * @throws Exception + * thrown if bytecode content is broken in any way. + * @throws IOException + * thrown if an I/O error occurs. + */ + private static void processAllRelevantBytecodeParts(String testedClassName, Set<String> methodSet, + FileInputStream fileInputStream) throws Exception, IOException + { + // first part of bytecode - magic number + processMagicNumber(fileInputStream); + // second part of bytecode - version and subversion + processClassVersion(fileInputStream); + // third part of bytecode - constant pool + ConstantPoolRecord[] poolEntries = processContantPool(fileInputStream); + // fourth part of bytecode - flags associated to the class + int flags = processClassFlags(fileInputStream, poolEntries); + // fifth part of bytecode - class name + String className = processClassName(fileInputStream, poolEntries); + // sixth part of bytecode - super class name + String superClassName = processSuperclassName(fileInputStream, poolEntries); + // sixth part of bytecode - list of implemented interfaces + Set<String> interfaceNames = processAllImplementedInterfaces(fileInputStream, poolEntries); + // process significant parts of bytecode + printBasicInfoAboutClass(flags, className, superClassName); + printAllImplementedInterfaces(interfaceNames); + // this test is needed to process only real tests, not other classes + boolean isRealTest = interfaceNames.contains("gnu/testlet/Testlet"); + if (isRealTest) + { + determineAllCalledAPIMethods(testedClassName, poolEntries, methodSet); + } + //printConstantPool(poolEntries); + System.err.println(); + } + + /** + * Print basic informations about the investigated class. + * + * @param flags + * flags set for a class read from bytecode. + * @param className + * class name read from bytecode. + * @param superClassName + * super class name read from bytecode. + */ + private static void printBasicInfoAboutClass(int flags, String className, String superClassName) + { + System.err.println("Class flags: " + flags); + System.err.println("className: " + className); + System.err.println("superClassName :" + superClassName); + } + + /** + * Print list of all interfaces implemented in investigated class. + * + * @param interfaceNames set containing interface names. + */ + private static void printAllImplementedInterfaces(Set<String> interfaceNames) + { + System.err.println("Implemented interfaces: " + interfaceNames.size()); + // print name of each implemented interface + for (String interfaceName : interfaceNames) + { + System.err.println(" " + interfaceName); + } + } + + /** + * Print test coverage for selected class. + * + * @param testedClassName + * tested class name. + * @param methodSet + * set to which all method should be stored + * @param list + * directory list + */ + private static void determineTestCoverageForSelectedClass(String testedClassName, Set<String> methodSet, File list) + { + // iterate through all files found in given directory + for (String fileName : list.list()) + { + // if the file seems to be real bytecode + if (fileName.endsWith(".class")) + { + System.err.println("Checked file: " + fileName); + // try to process it + determineTestCoverageForOneTestClass(testedClassName, list, fileName, methodSet); + } + } + } + + /** + * Print all API methods called from the tests. + * + * @param methodSet set filled with API method names + */ + private static void printAllAPIMethods(Set<String> methodSet) + { + // for all method names stored in methodSet + for (String method : methodSet) + { + System.out.println(method); + } + } + + /** + * Print test coverage for given class name. + * + * @param pathToTests + * path to directory where test classes are stored + * @param testedClassName + * name of tested class + */ 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 */ + // check if directory containing tests for given class name exists if (!list.isDirectory()) { + /* if does not exists, nothing bad happens */ 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); - } + determineTestCoverageForSelectedClass(testedClassName, methodSet, list); + // we have methodSet filled, let's print it + printAllAPIMethods(methodSet); } + /** + * Read path where test classes are stored from configuration file. + * + * @return path to directory where test classes are stored + * @throws IOException + * thrown in case of any I/O errors + */ private static String readPathToTest() throws IOException { + // read path where test classes are stored BufferedReader fin = new BufferedReader(new FileReader(TEST_DIRECTORY)); String directory = fin.readLine(); return directory; } + /** + * Print test coverage for given class name. + * + * @param className full class name + */ private static void doPrintTestCoverage(String className) { String pathToTests = null; try { + // read path where test classes are stored from configuration file pathToTests = readPathToTest(); + // then print test coverage printTestCoverage(pathToTests, className); } catch (IOException e) @@ -874,6 +1110,7 @@ String className = args[0].trim(); doPrintTestCoverage(className); } + // the name of class to check must be set on command line else { System.err.println("Usage java PrintTestCoverage package.className");