/* * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, * Version 1.0, and under the Eclipse Public License, Version 1.0 * (http://h2database.com/html/license.html). * Initial Developer: H2 Group */ package org.h2.test.coverage; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.util.ArrayList; import org.h2.util.New; /** * Tool to instrument java files with profiler calls. The tool can be used for * profiling an application and for coverage testing. This class is not used at * runtime of the tested application. */ public class Coverage { private static final String IMPORT = "import " + Coverage.class.getPackage().getName() + ".Profile"; private ArrayList<String> files = New.arrayList(); private ArrayList<String> exclude = New.arrayList(); private Tokenizer tokenizer; private Writer writer; private Writer data; private String token = ""; private String add = ""; private String file; private int index; private int indent; private int line; private String last; private String word, function; private boolean perClass; private boolean perFunction = true; private void printUsage() { System.out.println("Usage:\n" + "- copy all your source files to another directory\n" + " (be careful, they will be modified - don't take originals!)\n" + "- java " + getClass().getName() + " <directory>\n" + " this will modified the source code and create 'profile.txt'\n" + "- compile the modified source files\n" + "- run your main application\n" + "- after the application exits, a file 'notCovered.txt' is created,\n" + " which contains the class names, function names and line numbers\n" + " of code that has not been covered\n\n" + "Options:\n" + "-r recurse all subdirectories\n" + "-e exclude files\n" + "-c coverage on a per-class basis\n" + "-f coverage on a per-function basis\n" + "<dir> directory name (. for current directory)"); } /** * This method is called when executing this application. * * @param args the command line parameters */ public static void main(String... args) { new Coverage().run(args); } private void run(String... args) { if (args.length == 0 || args[0].equals("-?")) { printUsage(); return; } Coverage c = new Coverage(); int recurse = 1; for (int i = 0; i < args.length; i++) { String s = args[i]; if (s.equals("-r")) { // maximum recurse is 100 subdirectories, that should be enough recurse = 100; } else if (s.equals("-c")) { c.perClass = true; } else if (s.equals("-f")) { c.perFunction = true; } else if (s.equals("-e")) { c.addExclude(args[++i]); } else { c.addDir(s, recurse); } } try { c.data = new BufferedWriter(new FileWriter("profile.txt")); c.processAll(); c.data.close(); } catch (Exception e) { e.printStackTrace(); } } private void addExclude(String fileName) { exclude.add(fileName); } private boolean isExcluded(String s) { for (String e : exclude) { if (s.startsWith(e)) { return true; } } return false; } private void addDir(String path, int recurse) { File f = new File(path); if (f.isFile() && path.endsWith(".java")) { if (!isExcluded(path)) { files.add(path); } } else if (f.isDirectory() && recurse > 0) { for (String name : f.list()) { addDir(path + "/" + name, recurse - 1); } } } private void processAll() { int len = files.size(); long time = System.currentTimeMillis(); for (int i = 0; i < len; i++) { long t2 = System.currentTimeMillis(); if (t2 - time > 1000 || i >= len - 1) { System.out.println((i + 1) + " of " + len + " " + (100 * i / len) + "%"); time = t2; } String fileName = files.get(i); processFile(fileName); } } private void processFile(String name) { file = name; int i; i = file.lastIndexOf('.'); if (i != -1) { file = file.substring(0, i); } while (true) { i = file.indexOf('/'); if (i < 0) { i = file.indexOf('\\'); } if (i < 0) { break; } file = file.substring(0, i) + "." + file.substring(i + 1); } if (name.endsWith("Coverage.java") || name.endsWith("Tokenizer.java") || name.endsWith("Profile.java")) { return; } File f = new File(name); File fileNew = new File(name + ".new"); try { writer = new BufferedWriter(new FileWriter(fileNew)); Reader r = new BufferedReader(new FileReader(f)); tokenizer = new Tokenizer(r); indent = 0; try { process(); } catch (Exception e) { r.close(); writer.close(); e.printStackTrace(); printError(e.getMessage()); throw e; } r.close(); writer.close(); File backup = new File(name + ".bak"); backup.delete(); f.renameTo(backup); File copy = new File(name); fileNew.renameTo(copy); if (perClass) { nextDebug(); } } catch (Exception e) { e.printStackTrace(); printError(e.getMessage()); } } private void read() throws IOException { last = token; String write = token; token = null; tokenizer.initToken(); int i = tokenizer.nextToken(); if (i != Tokenizer.TYPE_EOF) { token = tokenizer.getString(); if (token == null) { token = "" + ((char) i); } else if (i == '\'') { // mToken="'"+getEscape(mToken)+"'"; token = tokenizer.getToken(); } else if (i == '\"') { // mToken="\""+getEscape(mToken)+"\""; token = tokenizer.getToken(); } else { if (write == null) { write = ""; } else { write = write + " "; } } } if (write == null || (!write.equals("else ") && !write.equals("else") && !write.equals("super ") && !write.equals("super") && !write.equals("this ") && !write.equals("this") && !write.equals("} ") && !write.equals("}"))) { if (add != null && !add.equals("")) { writeLine(); write(add); if (!perClass) { nextDebug(); } } } add = ""; if (write != null) { write(write); } } private void readThis(String s) throws IOException { if (!token.equals(s)) { throw new IOException("Expected: " + s + " got:" + token); } read(); } private void process() throws IOException { boolean imp = false; read(); do { while (true) { if (token == null || token.equals("{")) { break; } else if (token.equals(";")) { if (!imp) { write(";" + IMPORT); imp = true; } } read(); } processClass(); } while (token != null); } private void processInit() throws IOException { do { if (token.equals("{")) { read(); processInit(); } else if (token.equals("}")) { read(); return; } else { read(); } } while (true); } private void processClass() throws IOException { int type = 0; while (true) { if (token == null) { break; } else if (token.equals("class")) { read(); type = 1; } else if (token.equals("=")) { read(); type = 2; } else if (token.equals("static")) { word = "static"; read(); type = 3; } else if (token.equals("(")) { word = last + "("; read(); if (!token.equals(")")) { word = word + token; } type = 3; } else if (token.equals(",")) { read(); word = word + "," + token; } else if (token.equals(")")) { word = word + ")"; read(); } else if (token.equals(";")) { read(); type = 0; } else if (token.equals("{")) { read(); if (type == 1) { processClass(); } else if (type == 2) { processInit(); } else if (type == 3) { writeLine(); setLine(); processFunction(); writeLine(); } } else if (token.equals("}")) { read(); break; } else { read(); } } } private void processBracket() throws IOException { do { if (token.equals("(")) { read(); processBracket(); } else if (token.equals(")")) { read(); return; } else { read(); } } while (true); } private void processFunction() throws IOException { function = word; writeLine(); do { processStatement(); } while (!token.equals("}")); read(); writeLine(); } private void processBlockOrStatement() throws IOException { if (!token.equals("{")) { write("{ //++"); writeLine(); setLine(); processStatement(); write("} //++"); writeLine(); } else { read(); setLine(); processFunction(); } } private void processStatement() throws IOException { while (true) { if (token.equals("while") || token.equals("for") || token.equals("synchronized")) { read(); readThis("("); processBracket(); indent++; processBlockOrStatement(); indent--; return; } else if (token.equals("if")) { read(); readThis("("); processBracket(); indent++; processBlockOrStatement(); indent--; if (token.equals("else")) { read(); indent++; processBlockOrStatement(); indent--; } return; } else if (token.equals("try")) { read(); indent++; processBlockOrStatement(); indent--; while (true) { if (token.equals("catch")) { read(); readThis("("); processBracket(); indent++; processBlockOrStatement(); indent--; } else if (token.equals("finally")) { read(); indent++; processBlockOrStatement(); indent--; } else { break; } } return; } else if (token.equals("{")) { if (last.equals(")")) { // process anonymous inner classes (this is a hack) read(); processClass(); return; } else if (last.equals("]")) { // process object array initialization (another hack) while (!token.equals("}")) { read(); } read(); return; } indent++; processBlockOrStatement(); indent--; return; } else if (token.equals("do")) { read(); indent++; processBlockOrStatement(); readThis("while"); readThis("("); processBracket(); readThis(";"); setLine(); indent--; return; } else if (token.equals("case")) { add = ""; read(); while (!token.equals(":")) { read(); } read(); setLine(); } else if (token.equals("default")) { add = ""; read(); readThis(":"); setLine(); } else if (token.equals("switch")) { read(); readThis("("); processBracket(); indent++; processBlockOrStatement(); indent--; return; } else if (token.equals("class")) { read(); processClass(); return; } else if (token.equals("(")) { read(); processBracket(); } else if (token.equals("=")) { read(); if (token.equals("{")) { read(); processInit(); } } else if (token.equals(";")) { read(); setLine(); return; } else if (token.equals("}")) { return; } else { read(); } } } private void setLine() { add += "Profile.visit(" + index + ");"; line = tokenizer.getLine(); } private void nextDebug() throws IOException { if (perFunction) { int i = function.indexOf("("); String func = i < 0 ? function : function.substring(0, i); String fileLine = file + "." + func + "("; i = file.lastIndexOf('.'); String className = i < 0 ? file : file.substring(i + 1); fileLine += className + ".java:" + line + ")"; data.write(fileLine + " " + last + "\r\n"); } else { data.write(file + " " + line + "\r\n"); } index++; } private void writeLine() throws IOException { write("\r\n"); for (int i = 0; i < indent; i++) { writer.write(' '); } } private void write(String s) throws IOException { writer.write(s); // System.out.print(s); } private void printError(String error) { System.out.println(""); System.out.println("File:" + file); System.out.println("ERROR: " + error); } }