Created
September 21, 2012 05:11
-
-
Save kawasima/3759828 to your computer and use it in GitHub Desktop.
Tomcat server with reporting coverage in realtime
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Collection; | |
import net.sourceforge.cobertura.coveragedata.ClassData; | |
import net.sourceforge.cobertura.coveragedata.ProjectData; | |
import net.sourceforge.cobertura.instrument.FirstPassMethodInstrumenter; | |
import org.apache.log4j.Logger; | |
import org.objectweb.asm.ClassAdapter; | |
import org.objectweb.asm.ClassVisitor; | |
import org.objectweb.asm.MethodVisitor; | |
import org.objectweb.asm.Opcodes; | |
@SuppressWarnings("rawtypes") | |
class ClassInstrumenter extends ClassAdapter | |
{ | |
private static final Logger logger = Logger | |
.getLogger(ClassInstrumenter.class); | |
private final static String hasBeenInstrumented = "net/sourceforge/cobertura/coveragedata/HasBeenInstrumented"; | |
private Collection ignoreRegexs; | |
private Collection ignoreBranchesRegexs; | |
private ProjectData projectData; | |
private ClassData classData; | |
private String myName; | |
private boolean instrument = false; | |
public String getClassName() | |
{ | |
return this.myName; | |
} | |
public boolean isInstrumented() | |
{ | |
return instrument; | |
} | |
public ClassInstrumenter(ProjectData projectData, final ClassVisitor cv, | |
final Collection ignoreRegexs, final Collection ignoreBranchesRegexs) | |
{ | |
super(cv); | |
this.projectData = projectData; | |
this.ignoreRegexs = ignoreRegexs; | |
this.ignoreBranchesRegexs = ignoreBranchesRegexs; | |
} | |
private boolean arrayContains(Object[] array, Object key) | |
{ | |
for (int i = 0; i < array.length; i++) | |
{ | |
if (array[i].equals(key)) | |
return true; | |
} | |
return false; | |
} | |
/** | |
* @param name In the format | |
* "net/sourceforge/cobertura/coverage/ClassInstrumenter" | |
*/ | |
public void visit(int version, int access, String name, String signature, | |
String superName, String[] interfaces) | |
{ | |
this.myName = name.replace('/', '.'); | |
this.classData = this.projectData.getOrCreateClassData(this.myName); | |
this.classData.setContainsInstrumentationInfo(); | |
// Do not attempt to instrument interfaces or classes that | |
// have already been instrumented | |
if (((access & Opcodes.ACC_INTERFACE) != 0) | |
|| arrayContains(interfaces, hasBeenInstrumented)) | |
{ | |
super.visit(version, access, name, signature, superName, | |
interfaces); | |
} | |
else | |
{ | |
instrument = true; | |
// Flag this class as having been instrumented | |
String[] newInterfaces = new String[interfaces.length + 1]; | |
System.arraycopy(interfaces, 0, newInterfaces, 0, | |
interfaces.length); | |
newInterfaces[newInterfaces.length - 1] = hasBeenInstrumented; | |
super.visit(version, access, name, signature, superName, | |
newInterfaces); | |
} | |
} | |
/** | |
* @param source In the format "ClassInstrumenter.java" | |
*/ | |
public void visitSource(String source, String debug) | |
{ | |
super.visitSource(source, debug); | |
classData.setSourceFileName(source); | |
} | |
public MethodVisitor visitMethod(final int access, final String name, | |
final String desc, final String signature, | |
final String[] exceptions) | |
{ | |
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, | |
exceptions); | |
if (!instrument) | |
return mv; | |
return mv == null ? null : new FirstPassMethodInstrumenter(classData, mv, | |
this.myName, access, name, desc, signature, exceptions, ignoreRegexs, | |
ignoreBranchesRegexs); | |
} | |
public void visitEnd() | |
{ | |
if (instrument && classData.getNumberOfValidLines() == 0) | |
logger.warn("No line number information found for class " | |
+ this.myName | |
+ ". Perhaps you need to compile with debug=true?"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.InputStream; | |
import java.util.Collection; | |
import java.util.Vector; | |
import net.sourceforge.cobertura.coveragedata.ProjectData; | |
import net.sourceforge.cobertura.util.IOUtil; | |
import org.apache.catalina.loader.WebappClassLoader; | |
import org.objectweb.asm.ClassReader; | |
import org.objectweb.asm.ClassWriter; | |
@SuppressWarnings("rawtypes") | |
public class CoberturaClassLoader extends WebappClassLoader { | |
public static String instrumentedPackageName; | |
private Collection ignoreRegexes = new Vector(); | |
private Collection ignoreBranchesRegexes = new Vector(); | |
private ProjectData projectData = null; | |
public CoberturaClassLoader(ClassLoader parent) { | |
super(parent); | |
projectData = new ProjectData(); | |
} | |
@SuppressWarnings("unchecked") | |
public Class loadClass(String className, boolean resolve) | |
throws ClassNotFoundException { | |
Class clazz = findLoadedClass(className); | |
if (clazz != null) { | |
return clazz; | |
} | |
if (className.startsWith(instrumentedPackageName)) { | |
return defineClass(className, resolve); | |
} else { | |
return getParent().loadClass(className); | |
} | |
} | |
private Class defineClass(String className, boolean resolve) throws ClassNotFoundException { | |
Class clazz; | |
String path = className.replace('.', '/') + ".class";; | |
InputStream is = parent.getResourceAsStream(path); | |
ClassWriter cw; | |
ClassInstrumenter cv; | |
try { | |
ClassReader cr = new ClassReader(is); | |
cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); | |
cv = new ClassInstrumenter(projectData, cw, ignoreRegexes, ignoreBranchesRegexes); | |
cr.accept(cv, 0); | |
} catch(Throwable t) { | |
throw new ClassNotFoundException(t.getMessage()); | |
} finally { | |
IOUtil.closeInputStream(is); | |
} | |
if (is != null) { | |
clazz = defineClass(className, cw.toByteArray()); | |
if (resolve) { | |
resolveClass(clazz); | |
} | |
return clazz; | |
} | |
return null; | |
} | |
protected Class defineClass(String className, byte[] bytes) { | |
return defineClass(className, bytes, 0, bytes.length); | |
} | |
public ProjectData getProjectData() { | |
return projectData; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.File; | |
import net.sourceforge.cobertura.coveragedata.ProjectData; | |
import net.sourceforge.cobertura.coveragedata.TouchCollector; | |
import net.sourceforge.cobertura.reporting.ComplexityCalculator; | |
import net.sourceforge.cobertura.reporting.html.HTMLReport; | |
import net.sourceforge.cobertura.util.FileFinder; | |
import org.apache.catalina.Context; | |
import org.apache.catalina.core.AprLifecycleListener; | |
import org.apache.catalina.core.StandardContext; | |
import org.apache.catalina.core.StandardServer; | |
import org.apache.catalina.loader.WebappLoader; | |
import org.apache.catalina.startup.Tomcat; | |
public class EmbeddedServer { | |
private static final int PORT = 8082; | |
private static final String CONTEXT_PATH = "/myapp"; | |
private static final File COVERAGE_REPORT_DIR = new File("target/coverage"); | |
private static final String INSTRUMENTED_PACKAGE_NAME = "com.example"; | |
private static final int REPORT_INTERVAL_SECONDS = 30; | |
public static void main(String[] args) throws Exception { | |
String appBase = new File("src/main/webapp").getAbsolutePath(); | |
Tomcat tomcat = new Tomcat(); | |
tomcat.setPort(PORT); | |
tomcat.setBaseDir("."); | |
tomcat.getHost().setAppBase(appBase); | |
StandardServer server = (StandardServer) tomcat.getServer(); | |
AprLifecycleListener listener = new AprLifecycleListener(); | |
server.addLifecycleListener(listener); | |
Context context = tomcat.addWebapp(CONTEXT_PATH, appBase); | |
WebappLoader webappLoader = new WebappLoader(); | |
CoberturaClassLoader.instrumentedPackageName = INSTRUMENTED_PACKAGE_NAME; | |
webappLoader.setLoaderClass(CoberturaClassLoader.class.getName()); | |
webappLoader.setDelegate(((StandardContext) context).getDelegate()); | |
context.setLoader(webappLoader); | |
new CoverageMonitor(webappLoader).start(); | |
tomcat.start(); | |
tomcat.getServer().await(); | |
} | |
public static class CoverageMonitor extends Thread { | |
private WebappLoader webappLoader; | |
ComplexityCalculator complexity; | |
FileFinder finder; | |
public CoverageMonitor(WebappLoader webappLoader) { | |
this.webappLoader = webappLoader; | |
finder = new FileFinder(); | |
finder.addSourceDirectory("src/main/java"); | |
complexity = new ComplexityCalculator(finder); | |
} | |
@Override | |
public void run() { | |
while(true) { | |
CoberturaClassLoader cl = (CoberturaClassLoader)webappLoader.getClassLoader(); | |
if (cl != null) { | |
ProjectData data = cl.getProjectData(); | |
TouchCollector.applyTouchesOnProjectData(data); | |
try { | |
new HTMLReport(data, COVERAGE_REPORT_DIR, finder, complexity, "UTF-8"); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
try { | |
Thread.sleep(REPORT_INTERVAL_SECONDS * 1000); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment