diff --git a/org.intrace/build.xml b/org.intrace/build.xml index 3dff713..a7b351b 100644 --- a/org.intrace/build.xml +++ b/org.intrace/build.xml @@ -239,7 +239,7 @@ - + @@ -395,7 +395,7 @@ - @@ -415,6 +415,16 @@ + + + + + + + + + + diff --git a/org.intrace/lib/intrace-agent.jar b/org.intrace/lib/intrace-agent.jar index f616136..3c260c4 100644 Binary files a/org.intrace/lib/intrace-agent.jar and b/org.intrace/lib/intrace-agent.jar differ diff --git a/org.intrace/src/org/intrace/agent/AgentSettings.java b/org.intrace/src/org/intrace/agent/AgentSettings.java index a9fb162..645983b 100644 --- a/org.intrace/src/org/intrace/agent/AgentSettings.java +++ b/org.intrace/src/org/intrace/agent/AgentSettings.java @@ -5,6 +5,7 @@ import java.util.Locale; import java.util.Map; +import org.intrace.output.trace.TraceHandler; import org.intrace.shared.AgentConfigConstants; /** @@ -12,9 +13,11 @@ * * where argx is of the form value-parameter */ -public class AgentSettings +public class AgentSettings implements VerboseLogger { - // Static settings which control agent startup + + +// Static settings which control agent startup private int serverPort = 9123; private int callbackPort = -1; private boolean waitStart = false; @@ -23,27 +26,39 @@ public class AgentSettings private int actualServerPort = -1; // Dynamic settings - private String[] classRegex = new String[0]; - private String[] excludeClassRegex = new String[0]; + private InstrCriteria classesToInclude = null; + public InstrCriteria getClassesToInclude() { + return classesToInclude; + } + + +public InstrCriteria getClassesToExclude() { + return classesToExclude; +} + +private InstrCriteria classesToExclude = null; private boolean instruEnabled = true; private boolean saveTracedClassfiles = false; private boolean verboseMode = false; - public AgentSettings(String args) - { - parseArgs(args); - } + public AgentSettings(AgentSettings oldInstance) { // Copy all static state and dynamic settings actualServerPort = oldInstance.getActualServerPort(); - classRegex = oldInstance.getClassRegex(); - excludeClassRegex = oldInstance.getExcludeClassRegex(); + this.classesToInclude = oldInstance.classesToInclude; + this.classesToExclude = oldInstance.classesToExclude; + + instruEnabled = oldInstance.isInstrumentationEnabled(); saveTracedClassfiles = oldInstance.saveTracedClassfiles(); verboseMode = oldInstance.isVerboseMode(); } + public AgentSettings(String args) + { + parseArgs(args); + } public void parseArgs(String args) @@ -111,7 +126,8 @@ else if (arg.toLowerCase(Locale.ROOT).equals(AgentConfigConstants.START_ACTIVATE else if (arg.startsWith(AgentConfigConstants.CLASS_REGEX)) { String classRegexStr = arg.replace(AgentConfigConstants.CLASS_REGEX, ""); - classRegex = classRegexStr.split("\\|"); + this.classesToInclude = new InstrCriteria(classRegexStr); + this.classesToInclude.verboseLogger = this; } else if (arg.startsWith(AgentConfigConstants.EXCLUDE_CLASS_REGEX)) { @@ -119,7 +135,8 @@ else if (arg.startsWith(AgentConfigConstants.EXCLUDE_CLASS_REGEX)) .replace( AgentConfigConstants.EXCLUDE_CLASS_REGEX, ""); - excludeClassRegex = classExcludeRegexStr.split("\\|"); + this.classesToExclude = new InstrCriteria(classExcludeRegexStr); + this.classesToExclude.verboseLogger = this; } } @@ -150,12 +167,18 @@ public int getCallbackPort() public String[] getClassRegex() { - return classRegex; + String[] rc = {}; + if (this.classesToInclude!=null) + rc = this.classesToInclude.getClassRegex(); + return rc; } public String[] getExcludeClassRegex() { - return excludeClassRegex; + String[] rc = {}; + if (this.classesToExclude!=null) + rc = this.classesToInclude.getClassRegex(); + return rc; } public boolean isInstrumentationEnabled() @@ -178,8 +201,17 @@ public String toString() { // Output key settings String currentSettings = ""; - currentSettings += "Class Regex : " + Arrays.toString(classRegex) + "\n"; - currentSettings += "Exclude Class Regex : " + Arrays.toString(excludeClassRegex) + "\n"; + + String includeString = ""; + if (this.classesToInclude != null) + includeString = this.classesToInclude.toString(); + currentSettings += "Include Class Regex : " + includeString + "\n"; + + String excludeString = ""; + if (this.classesToExclude != null) + excludeString = this.classesToExclude.toString(); + currentSettings += "Exclude Class Regex : " + excludeString + "\n"; + currentSettings += "Tracing Enabled : " + instruEnabled + "\n"; currentSettings += "Save Traced Class Files : " + saveTracedClassfiles + "\n"; @@ -191,10 +223,20 @@ public Map getSettingsMap() Map settingsMap = new HashMap(); settingsMap.put(AgentConfigConstants.INSTRU_ENABLED, Boolean.toString(instruEnabled)); + + String includeSettings = ""; + if (this.classesToInclude!=null) { + includeSettings = this.classesToInclude.toString(); + } settingsMap.put(AgentConfigConstants.CLASS_REGEX, - getPatternString(classRegex)); + includeSettings); + + String excludeSettings = ""; + if (this.classesToExclude!=null) { + includeSettings = this.classesToExclude.toString(); + } settingsMap.put(AgentConfigConstants.EXCLUDE_CLASS_REGEX, - getPatternString(excludeClassRegex)); + excludeSettings); settingsMap.put(AgentConfigConstants.VERBOSE_MODE, Boolean.toString(verboseMode)); settingsMap.put(AgentConfigConstants.SAVE_TRACED_CLASSFILES, @@ -203,18 +245,13 @@ public Map getSettingsMap() settingsMap.put(AgentConfigConstants.START_WAIT, Boolean.toString(waitStart)); return settingsMap; } + + + public void logVerbose(String v) { + if (isVerboseMode()) + { + TraceHandler.INSTANCE.writeTraceOutput("DEBUG: " + v); + } + } - private String getPatternString(String[] parts) - { - StringBuilder strBuilder = new StringBuilder(); - for (int ii = 0; ii < parts.length; ii++) - { - strBuilder.append(parts[ii]); - if (ii < (parts.length - 1)) - { - strBuilder.append("|"); - } - } - return strBuilder.toString(); - } } diff --git a/org.intrace/src/org/intrace/agent/ClassTransformer.java b/org.intrace/src/org/intrace/agent/ClassTransformer.java index 114a116..98eb80d 100644 --- a/org.intrace/src/org/intrace/agent/ClassTransformer.java +++ b/org.intrace/src/org/intrace/agent/ClassTransformer.java @@ -112,7 +112,8 @@ private byte[] getInstrumentedClassBytes(String xiClassName, InstrumentedClassWriter writer = new InstrumentedClassWriter(xiClassName, cr, analysis, - shouldInstrument); + shouldInstrument, + settings); cr.accept(writer, 0); return writer.toByteArray(); @@ -163,7 +164,7 @@ private boolean isToBeConsideredForInstrumentation( return false; } - // Don't sensitive classes + // Don't instrument sensitive classes if (isSensitiveClass(className)) { if (settings.isVerboseMode()) @@ -183,10 +184,9 @@ private boolean isToBeConsideredForInstrumentation( return false; } - // Don't modify classes which match the exclude regex - if ((settings.getExcludeClassRegex() == null) - || matches(settings.getExcludeClassRegex(), className)) - { + + if (this.settings.getClassesToExclude() != null && + this.settings.getClassesToExclude().allMethodsSpecified(className)) { if (settings.isVerboseMode()) { TraceHandler.INSTANCE.writeTraceOutput("DEBUG: Ignoring class matching the active exclude regex: " @@ -699,7 +699,7 @@ public void instrumentKlasses(Set klasses) } catch (Throwable e) { - String error = "Error instrumenting [" + klass.klass.getName() + "]"; + String error = "Exception [" + e.getMessage() + "] instrumenting [" + klass.klass.getName() + "]"; if (settings.isVerboseMode()) TraceHandler.INSTANCE.writeTraceOutput("DEBUG: !! " + error); System.err.println(error); diff --git a/org.intrace/src/org/intrace/agent/InstrCriteria.java b/org.intrace/src/org/intrace/agent/InstrCriteria.java new file mode 100644 index 0000000..00e55e7 --- /dev/null +++ b/org.intrace/src/org/intrace/agent/InstrCriteria.java @@ -0,0 +1,232 @@ +package org.intrace.agent; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.intrace.output.trace.TraceHandler; + +/** + * Contains criteria that the 'user' specifies to request certain classes to + * be instrumented. + * @author erikostermueller + * + */ +public class InstrCriteria { + + public VerboseLogger verboseLogger = null; + /** + * Each item in this Map is a classname that has methods. + */ + final Map > myInstrCriteria = new Hashtable>(); + static final String CLASS_METHOD_DELIMITER = "#"; + private static final String CRITERIA_DELIM = "|"; + private static final String REGEX_CRITERIA_DELIM = "\\"+CRITERIA_DELIM; + private static final String INSTRUMENT_ALL_METHODS = "INSTR_ALL_METHODS"; + private String[] classNamesOnly = null; + private List classNamesOnlyList = new ArrayList(); + private String originalCriteria = null; + public InstrCriteria(String criteria) { + this.originalCriteria = criteria; + criteria = criteria.replace('{', '['); + String[] tmp = criteria.split(this.REGEX_CRITERIA_DELIM); + + for (String s : tmp) + addClassOrMethod(s); + + } + /* + * Example: org.intracetest.agent.ArgumentTypes#charArrayArg([C)V + * + */ + static class SimpleMethod { + boolean ynAllMethods = false; + /* + * example: charArrayArg + */ + private String name; + /* + * example: ([C)V + * + */ + private String args; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getArgs() { + String rc = ""; + if (args!=null) + rc = this.args.replace('[', '{'); + return rc; + } + + public void setArgs(String args) { + this.args = args; + } + + public String toString() { + return name+getArgs(); + } + + public void setNameAndArgs(String methodNameAndArgs) { + int firstLeftParen = methodNameAndArgs.indexOf("("); + if (firstLeftParen >= 0) { + name = methodNameAndArgs.substring(0, firstLeftParen); + args = methodNameAndArgs.substring(firstLeftParen); + //System.out.println("in setNameAndArgs just added [" + methodNameAndArgs + "] as [" + this.toString() + "]"); + + } else + name = methodNameAndArgs; + } + } + public String toString() { + StringBuilder sb = new StringBuilder(); + int count = 0; + for(String className : this.getClassRegex()) { + + List methods = this.myInstrCriteria.get(className); + for(SimpleMethod myMethod : methods) { + if (++count > 1) sb.append(CRITERIA_DELIM); //This line add this | to make crit1|crit2|crit3 + if (myMethod.ynAllMethods) + sb.append(className); + else { + sb.append(className); + sb.append(CLASS_METHOD_DELIMITER); + sb.append( myMethod.toString() ); + } + } + } + return sb.toString(); + } + public String[] getClassRegex() { + if (classNamesOnly==null) { + Set keys = this.myInstrCriteria.keySet(); + classNamesOnly = keys.toArray( new String[0] ); + } + return classNamesOnly; + + } + /** + * Example: org.intracetest.agent.ArgumentTypes#charArrayArg([C)V + * + * @param myclass: example: org.intracetest.agent.ArgumentTypes + * @param method: example: charArrayArg + * @param arguments: example: ([C)V + * @return + */ + public boolean thisMethodSpecified(String myClass, String method, String arguments) { + List allMethods = null; + arguments = arguments.replace('[', '{'); + boolean rc = false; + if (this.allMethodsSpecified(myClass)) { + rc = true; + }else { + allMethods = this.myInstrCriteria.get(myClass); + if (findMethod(allMethods, method,arguments)!=null) + rc = true; + } + logVerbose("instrument method? [" + rc + + "] class[" + myClass + + "] method[" + method + + "] + method args [" + arguments + + "] count of methods instrumented [" + ( (allMethods!=null) ? allMethods.size() : "zero" )+ + "]"); + return rc; + + } + public int methodCountPerClass(String myClass) { + int count = 0; + List allMethods = this.myInstrCriteria.get(myClass); + if (allMethods!=null) { + count = allMethods.size(); + } + return count; + + } + private void logVerbose(String s) { + if (this.verboseLogger!=null) { + this.verboseLogger.logVerbose(s); + } + } + public boolean allMethodsSpecified(String myClass) { + boolean rc = false; + List allMethods = this.myInstrCriteria.get(myClass); + if (allMethods !=null) + for(SimpleMethod method : allMethods) { + if (method.ynAllMethods) + rc = true; + } + //logVerbose("instrument all methods for this class? [" + rc + "] class[" + myClass + "]"); + return rc; + } + /** + * + * The bug lies here: + * Comparing fullMethodNameCriteria [byteArrayArg({B)V] to [byteArrayArg([B)V] + * + * @param methods + * @param nameCriteria + * @param argsCriteria + * @return + */ + private SimpleMethod findMethod(List methods, String nameCriteria, String argsCriteria){ + SimpleMethod rc = null; + if (nameCriteria !=null && argsCriteria !=null) { + String fullMethodNameCriteria = nameCriteria + argsCriteria; + if (methods != null) + for(SimpleMethod sm : methods) { + //logVerbose("Comparing fullMethodNameCriteria [" + fullMethodNameCriteria + "] to [" + sm.toString() + "]"); + if (fullMethodNameCriteria.equals(sm.toString())) { + rc = sm; + break; + } + } + } + return rc; + } + /** + * trying to split this into two: MyClass#myMethod(D)V, but MyClass is also allowable here + * @param methodOrClass + */ + private void addClassOrMethod(String methodOrClass) { + //System.out.println("###addClassOrMethod [" + methodOrClass + "]"); + SimpleMethod method = new SimpleMethod(); + + List allMethods = null; + + String[] parts = methodOrClass.split(CLASS_METHOD_DELIMITER); + if (parts.length >= 1 && parts[0]!=null) { + allMethods = this.myInstrCriteria.get(parts[0]); + if (allMethods == null) { + allMethods = new ArrayList(); + //this.logVerbose("in addClassOrMethod just created allMethods hash[" + allMethods.hashCode() + "]myInstrCriteriaHash[" + myInstrCriteria.hashCode() + "]"); + this.myInstrCriteria.put(parts[0], allMethods); + } + + switch (parts.length) { + case 1: //just the package and class name were specified, meaning that all methods should be instrumented. + method.ynAllMethods = true; + break; + case 2: //a method was specified after a # sign...only instrument this specific method. + method.setNameAndArgs(parts[1]); + break; + default: + throw new RuntimeException("Was expecting to find either zero or one of the [" + this.CLASS_METHOD_DELIMITER + "] character inside of [" + methodOrClass + "]"); + } + } else { + throw new RuntimeException("Error adding this method/class [" + methodOrClass + "]"); + } + allMethods.add(method); +// System.out.println("## Just added methodName[" + method.name + "] methodArg [" + method.args + "]methodArgGetter [" + method.getArgs() + "]"); +// System.out.println("@@@@@@@Class count [" + myInstrCriteria.size() + "] for class[" + parts[0] + "] count is [" + allMethods.size() + "]"); + } +} diff --git a/org.intrace/src/org/intrace/agent/InstrumentedClassWriter.java b/org.intrace/src/org/intrace/agent/InstrumentedClassWriter.java index b033eb7..c707020 100644 --- a/org.intrace/src/org/intrace/agent/InstrumentedClassWriter.java +++ b/org.intrace/src/org/intrace/agent/InstrumentedClassWriter.java @@ -9,6 +9,7 @@ import java.util.Set; import org.intrace.output.AgentHelper; +import org.intrace.output.trace.TraceHandler; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; @@ -27,6 +28,7 @@ public class InstrumentedClassWriter extends ClassWriter private final ClassAnalysis analysis; private final boolean threadClass; private final boolean shouldInstrument; + private AgentSettings settings = null; /** * cTor @@ -37,13 +39,14 @@ public class InstrumentedClassWriter extends ClassWriter * @param analysis */ public InstrumentedClassWriter(String xiClassName, ClassReader xiReader, - ClassAnalysis xiAnalysis, boolean xiShouldInstrument) + ClassAnalysis xiAnalysis, boolean xiShouldInstrument, AgentSettings settings) { super(xiReader, COMPUTE_MAXS); className = xiClassName; analysis = xiAnalysis; shouldInstrument = xiShouldInstrument; threadClass = xiClassName.equals("java.lang.Thread"); + this.settings = settings; } /** @@ -53,18 +56,52 @@ public InstrumentedClassWriter(String xiClassName, ClassReader xiReader, public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + + if (settings.isVerboseMode()) + { + + /** The following code provides invaluable debug for conjuring right syntax for method parameters. + * http://hsqldb.org/doc/src/org/hsqldb/jdbc/JDBCConnection.html#isWrapperFor(java.lang.Class) + [14:44:45.065]:[19]:access:1 + [14:44:45.065]:[19]:name:isWrapperFor + [14:44:45.065]:[19]:desc:(Ljava/lang/Class;)Z <<<<<==== complicate syntax for method specification + [14:44:45.065]:[19]:signature:(Ljava/lang/Class<*>;)Z + + http://hsqldb.org/doc/src/org/hsqldb/jdbc/JDBCConnection.html#prepareStatement(java.lang.String, java.lang.String[]) + [14:44:45.068]:[19]:access:33 + [14:44:45.068]:[19]:name:prepareStatement + [14:44:45.069]:[19]:desc:(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement; <<<<<==== complicated syntax for method specification + [14:44:45.069]:[19]:signature:null + + */ + StringBuilder sb = new StringBuilder(); + sb.append(this.className); + sb.append(InstrCriteria.CLASS_METHOD_DELIMITER); + sb.append(name); + sb.append(desc); + TraceHandler.INSTANCE.writeTraceOutput("DEBUG: method signature: " + sb.toString()); + } MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); - // Extract analysis results for this method - Set branchTraceLines = analysis.methodReverseGOTOLines.get(name - + desc); - Integer entryLine = analysis.methodEntryLines.get(name + desc); + // If this class/method is not excluded, but then consider including it. + // If all methods are allowed (by not specifying any methods) or this specific method is defined, then instrument it. - if (!threadClass || !name.equals("getUncaughtExceptionHandler")) - { - mv = new InstrumentedMethodWriter(mv, access, name, desc, - branchTraceLines, entryLine); + if (this.settings.getClassesToExclude()==null + ||!this.settings.getClassesToExclude().thisMethodSpecified(this.className, name, desc)) { + if (this.settings.getClassesToInclude().thisMethodSpecified(this.className, name, desc) ) { + // Extract analysis results for this method + Set branchTraceLines = analysis.methodReverseGOTOLines.get(name + + desc); + Integer entryLine = analysis.methodEntryLines.get(name + desc); + + if (!threadClass || !name.equals("getUncaughtExceptionHandler")) + { + //System.out.println("Instrumenting class [" + this.className + "] method [" + name + "] args [" + desc + "]"); + mv = new InstrumentedMethodWriter(mv, access, name, desc, + branchTraceLines, entryLine); + } + } } // Transform the method return mv; diff --git a/org.intrace/src/org/intrace/agent/VerboseLogger.java b/org.intrace/src/org/intrace/agent/VerboseLogger.java new file mode 100644 index 0000000..ae1c80e --- /dev/null +++ b/org.intrace/src/org/intrace/agent/VerboseLogger.java @@ -0,0 +1,5 @@ +package org.intrace.agent; + +public interface VerboseLogger { + public void logVerbose(String v); +} diff --git a/org.intrace/src/org/intrace/client/gui/helper/InTraceUI.java b/org.intrace/src/org/intrace/client/gui/helper/InTraceUI.java index 866270d..6fb9097 100644 --- a/org.intrace/src/org/intrace/client/gui/helper/InTraceUI.java +++ b/org.intrace/src/org/intrace/client/gui/helper/InTraceUI.java @@ -491,7 +491,8 @@ private void savePatterns() }, getListFromString(settingsData.classRegex), getListFromString(settingsData.classExcludeRegex), - ALLOW_CLASSES); + ALLOW_ALL); + //ALLOW_CLASSES); placeDialogInCenter(sWindow.getBounds(), regexInput.sWindow); } }); @@ -2113,10 +2114,13 @@ public void setRegex(final String includePattern, final String excludePattern) @Override public void run() { - settingsData.classRegex = includePattern; - settingsData.classExcludeRegex = excludePattern; - controlThread.sendMessage(AgentConfigConstants.CLASS_REGEX + includePattern - + AgentConfigConstants.EXCLUDE_CLASS_REGEX + excludePattern); + String tmpInclude = includePattern.replace('[', '{'); + String tmpExclude = excludePattern.replace('[', '{'); + + settingsData.classRegex = tmpInclude; + settingsData.classExcludeRegex = tmpExclude; + controlThread.sendMessage(AgentConfigConstants.CLASS_REGEX + tmpInclude + + AgentConfigConstants.EXCLUDE_CLASS_REGEX + tmpExclude); controlThread.sendMessage("getsettings"); } }); diff --git a/org.intrace/testsrc/org/intracetest/agent/AgentSettingsTest.java b/org.intrace/testsrc/org/intracetest/agent/AgentSettingsTest.java index a27be0e..420e77e 100644 --- a/org.intrace/testsrc/org/intracetest/agent/AgentSettingsTest.java +++ b/org.intrace/testsrc/org/intracetest/agent/AgentSettingsTest.java @@ -1,6 +1,7 @@ package org.intracetest.agent; import java.util.Map; +import org.intrace.agent.InstrCriteria; import junit.framework.TestCase; @@ -9,6 +10,10 @@ public class AgentSettingsTest extends TestCase { + private String M1 = "org.intracetest.agent.ArgumentTypes#boolArrayArrayArg({{Z)V"; + private String M2 = "org.intracetest.agent.ArgumentTypes#objArrayArg({Ljava/lang/Object;)V"; + private String M3 = "org.intracetest.agent.ArgumentTypes#doubleArg(D)V"; + public void testAgentSettings() { AgentSettings as = new AgentSettings( @@ -22,8 +27,12 @@ public void testAgentSettings() + "true"); assertNotNull(as.getClassRegex()); assertEquals(as.getClassRegex().length, 2); - assertEquals(as.getClassRegex()[0], "foo"); - assertEquals(as.getClassRegex()[1], "bar"); + + assertTrue( "foo".equals(as.getClassRegex()[0]) || "foo".equals(as.getClassRegex()[1]) ); + assertTrue( "bar".equals(as.getClassRegex()[0]) || "bar".equals(as.getClassRegex()[1]) ); + assertTrue( as.getClassesToInclude().allMethodsSpecified("foo") ); + assertFalse( as.getClassesToInclude().allMethodsSpecified("unknownClass")); + assertEquals(as.isInstrumentationEnabled(), true); assertEquals(as.saveTracedClassfiles(), true); assertEquals(as.isVerboseMode(), true); @@ -31,8 +40,12 @@ public void testAgentSettings() as = new AgentSettings(as); assertNotNull(as.getClassRegex()); assertEquals(as.getClassRegex().length, 2); - assertEquals(as.getClassRegex()[0], "foo"); - assertEquals(as.getClassRegex()[1], "bar"); + assertTrue( "foo".equals(as.getClassRegex()[0]) || "foo".equals(as.getClassRegex()[1]) ); + assertTrue( "bar".equals(as.getClassRegex()[0]) || "bar".equals(as.getClassRegex()[1]) ); + + assertTrue( as.getClassesToInclude().allMethodsSpecified("foo") ); + assertFalse( as.getClassesToInclude().allMethodsSpecified("unknownClass")); + assertEquals(as.isInstrumentationEnabled(), true); assertEquals(as.saveTracedClassfiles(), true); assertEquals(as.isVerboseMode(), true); @@ -41,11 +54,106 @@ public void testAgentSettings() assertNotNull(toString); Map settingsMap = as.getSettingsMap(); - assertEquals(settingsMap.get(AgentConfigConstants.CLASS_REGEX), "foo|bar"); + + assertTrue( settingsMap.get(AgentConfigConstants.CLASS_REGEX).equals("foo|bar") || + settingsMap.get(AgentConfigConstants.CLASS_REGEX).equals("bar|foo") ); + assertEquals(settingsMap.get(AgentConfigConstants.INSTRU_ENABLED), "true"); assertEquals(settingsMap.get(AgentConfigConstants.SAVE_TRACED_CLASSFILES), "true"); assertEquals(settingsMap.get(AgentConfigConstants.VERBOSE_MODE), "true"); } + public void testInstrCriteria() { + InstrCriteria ic = new InstrCriteria("foo|bar"); + + assertTrue(ic.allMethodsSpecified("foo") ); //This is criteria for 'foo' class, no methods here. When no methods were specified, then instrument all methods. + assertTrue(ic.allMethodsSpecified("bar") ); //This is criteria for the 'bar' class; no methods here. When no methods were specified, then instrument all methods. + assertFalse(ic.allMethodsSpecified("doesnotexist") ); //If class name was never specified, then no methods should be instrumented. + + assertTrue("foo".equals(ic.getClassRegex()[0]) || "foo".equals(ic.getClassRegex()[1]) ); + assertTrue("bar".equals(ic.getClassRegex()[0]) || "bar".equals(ic.getClassRegex()[1]) ); + + assertEquals("Added one class with a single 'instrument all', but count isn't right", + 1, + ic.methodCountPerClass("foo") + ); + + assertEquals("Added one class with a single 'instrument all', but count isn't right", + 1, + ic.methodCountPerClass("bar") + ); + + assertTrue("toString in InstrCriteria is not working", ic.toString().equals("foo|bar") || ic.toString().equals("bar|foo") ); + } + + public void testIfWeCanParseMethodCriteria() { + InstrCriteria ic2 = new InstrCriteria("org.hsqldb.jdbc.JDBCConnection#onStartEscapeSequence(Ljava/lang/String;Ljava/lang/StringBuffer;I)I"); + assertFalse( ic2.allMethodsSpecified("org.hsqldb.jdbc.JDBCConnection") ); + + assertTrue( ic2.thisMethodSpecified("org.hsqldb.jdbc.JDBCConnection", "onStartEscapeSequence", "(Ljava/lang/String;Ljava/lang/StringBuffer;I)I")); + assertFalse( ic2.thisMethodSpecified("Xorg.hsqldb.jdbc.JDBCConnection", "onStartEscapeSequence", "(Ljava/lang/String;Ljava/lang/StringBuffer;I)I")); + assertFalse( ic2.thisMethodSpecified("org.hsqldb.jdbc.JDBCConnection", "XonStartEscapeSequence", "(Ljava/lang/String;Ljava/lang/StringBuffer;I)I")); + assertFalse( ic2.thisMethodSpecified("org.hsqldb.jdbc.JDBCConnection", "onStartEscapeSequence", "X(Ljava/lang/String;Ljava/lang/StringBuffer;I)I")); + } + public void testIfWeCanParseMix() { + + InstrCriteria ic2 = new InstrCriteria("org.hsqldb.jdbc.JDBCConnection#onStartEscapeSequence(Ljava/lang/String;Ljava/lang/StringBuffer;I)I|foo"); + assertFalse( ic2.allMethodsSpecified("org.hsqldb.jdbc.JDBCConnection") ); + + assertTrue( ic2.thisMethodSpecified("org.hsqldb.jdbc.JDBCConnection", "onStartEscapeSequence", "(Ljava/lang/String;Ljava/lang/StringBuffer;I)I")); + assertFalse( ic2.thisMethodSpecified("Xorg.hsqldb.jdbc.JDBCConnection", "onStartEscapeSequence", "(Ljava/lang/String;Ljava/lang/StringBuffer;I)I")); + assertFalse( ic2.thisMethodSpecified("org.hsqldb.jdbc.JDBCConnection", "XonStartEscapeSequence", "(Ljava/lang/String;Ljava/lang/StringBuffer;I)I")); + assertFalse( ic2.thisMethodSpecified("org.hsqldb.jdbc.JDBCConnection", "onStartEscapeSequence", "X(Ljava/lang/String;Ljava/lang/StringBuffer;I)I")); + + assertTrue(ic2.allMethodsSpecified("foo") ); //If not methods were specified, then instrument all methods. + assertFalse(ic2.allMethodsSpecified("doesnotexist") ); //If class name was never specified, then no methods should be instrumented. + + //assertEquals(ic2.getClassRegex()[0], "foo"); + assertTrue("foo".equals(ic2.getClassRegex()[0]) || "foo".equals(ic2.getClassRegex()[1]) ); + assertTrue("org.hsqldb.jdbc.JDBCConnection".equals(ic2.getClassRegex()[0]) || "org.hsqldb.jdbc.JDBCConnection".equals(ic2.getClassRegex()[1]) ); + + + + } + public void testIfWeCanParseThreeComplicatedMethods() { + String myCriteria = M1+"|"+M2+"|"+M3; + //System.out.println("@@@@@@@Start of testIfWeCanParseThreeComplicatedMethods() "); + InstrCriteria ic = new InstrCriteria(myCriteria); + //System.out.println("@@@@@@@@@@@@@@Before assert for testIfWeCanParseThreeComplicatedMethods() "); + assertEquals("Added three methods for a single class, but didn't find the right count of methods", + 3, + ic.methodCountPerClass("org.intracetest.agent.ArgumentTypes") + ); + //System.out.println("@@@@@@@@@@@@@@ AFTER assert for testIfWeCanParseThreeComplicatedMethods() "); + } + public void testMethodsFromDifferentClasses() { + InstrCriteria ic = new InstrCriteria("ArgumentTypes#byteArrayArg({B)V|OtherTypes#byteArrayArg({B)V"); + assertFalse( ic.allMethodsSpecified("ArgumentTypes") ); + assertFalse( ic.allMethodsSpecified("OtherTypes") ); + + String[] myClasses = ic.getClassRegex(); + assertTrue( "ArgumentTypes".equals(myClasses[0]) || "ArgumentTypes".equals(myClasses[1]) ); + assertTrue( "OtherTypes".equals(myClasses[0]) || "OtherTypes".equals(myClasses[1]) ); + + assertTrue( ic.thisMethodSpecified("ArgumentTypes","byteArrayArg", "({B)V") ); + assertTrue( ic.thisMethodSpecified("OtherTypes", "byteArrayArg", "({B)V") ); + + assertTrue( ic.toString().equals("ArgumentTypes#byteArrayArg({B)V|OtherTypes#byteArrayArg({B)V") + || ic.toString().equals("OtherTypes#byteArrayArg({B)V|ArgumentTypes#byteArrayArg({B)V") ); + + } + public void testMethodsFromSameClasses() { + InstrCriteria ic = new InstrCriteria("MyTypes#bar({B)V|MyTypes#foo({B)V"); + assertFalse( ic.allMethodsSpecified("MyTypes") ); + + assertTrue( ic.thisMethodSpecified("MyTypes","foo", "({B)V") ); + assertTrue( ic.thisMethodSpecified("MyTypes", "bar", "({B)V") ); + + assertTrue( ic.toString().equals("MyTypes#bar({B)V|MyTypes#foo({B)V") + || ic.toString().equals("MyTypes#foo({B)V|MyTypes#bar({B)V") ); + + + } + } diff --git a/org.intrace/testsrc/org/intracetest/agent/AgentTest.java b/org.intrace/testsrc/org/intracetest/agent/AgentTest.java index 56e7cc9..97514f5 100644 --- a/org.intrace/testsrc/org/intracetest/agent/AgentTest.java +++ b/org.intrace/testsrc/org/intracetest/agent/AgentTest.java @@ -214,8 +214,8 @@ public Object answer() throws Throwable // Setup agent testSetting(AgentConfigConstants.INSTRU_ENABLED, "false"); - testSetting(AgentConfigConstants.CLASS_REGEX, "BranchPatterns"); - testSetting(AgentConfigConstants.VERBOSE_MODE, "false"); + testSetting(AgentConfigConstants.CLASS_REGEX, "org.intracetest.agent.BranchPatterns"); + testSetting(AgentConfigConstants.VERBOSE_MODE, "true"); testSetting(AgentConfigConstants.SAVE_TRACED_CLASSFILES, "true"); testSetting(AgentConfigConstants.INSTRU_ENABLED, "true"); @@ -241,6 +241,8 @@ public Object answer() throws Throwable traceLine = capturedTrace.poll(); } + assertEquals(12,parsedTraceData.size()); + // Verify the trace assertNotNull(parsedTraceData.get("switchblock")); { @@ -320,6 +322,7 @@ public Object answer() throws Throwable } } + public void testArgumentTypes() throws Throwable { // Create and init the mock @@ -329,7 +332,7 @@ public void testArgumentTypes() throws Throwable // Setup agent testSetting(AgentConfigConstants.INSTRU_ENABLED, "false"); - testSetting(AgentConfigConstants.CLASS_REGEX, "ArgumentTypes"); + testSetting(AgentConfigConstants.CLASS_REGEX, "org.intracetest.agent.ArgumentTypes"); testSetting(AgentConfigConstants.VERBOSE_MODE, "false"); testSetting(AgentConfigConstants.SAVE_TRACED_CLASSFILES, "true"); testSetting(AgentConfigConstants.INSTRU_ENABLED, "true"); @@ -494,7 +497,9 @@ public void testArgumentTypes() throws Throwable private void parseLine(Map parsedTraceData, String traceLine) { - System.out.println("Parse: " + traceLine); + //System.out.println("Parse: " + traceLine); + if (traceLine.contains("DEBUG")) + return; String[] traceParts = traceLine.split(":##:"); String traceType = traceParts[0]; String traceLineData = traceParts[1]; diff --git a/org.intrace/testsrc/org/intracetest/agent/ArgumentTypes.java b/org.intrace/testsrc/org/intracetest/agent/ArgumentTypes.java index 07e9ddd..b6babc0 100644 --- a/org.intrace/testsrc/org/intracetest/agent/ArgumentTypes.java +++ b/org.intrace/testsrc/org/intracetest/agent/ArgumentTypes.java @@ -70,96 +70,228 @@ public String toString() } } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#byteArg(B)V
+        
+ */ + private void byteArg(byte arg) { setProperty(Byte.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#byteArrayArg({B)V
+        
+ */ + private void byteArrayArg(byte[] arg) { setProperty(Arrays.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#shortArg(S)V
+        
+ */ + private void shortArg(short arg) { setProperty(Short.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#shortArrayArg({S)V
+        
+ */ + private void shortArrayArg(short[] arg) { setProperty(Arrays.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#intArg(I)V
+        
+ */ + private void intArg(int arg) { setProperty(Integer.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#intArrayArg({I)V
+        
+ */ + private void intArrayArg(int[] arg) { setProperty(Arrays.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#longArg(J)V
+        
+ */ + private void longArg(long arg) { setProperty(Long.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#longArrayArg({J)V
+        
+ */ + private void longArrayArg(long[] arg) { setProperty(Arrays.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#floatArg(F)V
+        
+ */ + private void floatArg(float arg) { setProperty(Float.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#floatArrayArg({F)V
+        
+ */ + private void floatArrayArg(float[] arg) { setProperty(Arrays.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#doubleArg(D)V
+        
+ */ + private void doubleArg(double arg) { setProperty(Double.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#doubleArrayArg({D)V
+        
+ */ + private void doubleArrayArg(double[] arg) { setProperty(Arrays.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#boolArg(Z)V
+        
+ */ + private void boolArg(boolean arg) { setProperty(Boolean.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#boolArrayArg({Z)V
+        
+ */ + private void boolArrayArg(boolean[] arg) { setProperty(Arrays.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#boolArrayArrayArg({{Z)V
+	
+ */ private void boolArrayArrayArg(boolean[][] arg) { setProperty(Arrays.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#charArg(C)V
+        
+ */ + private void charArg(char arg) { setProperty(Character.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#charArrayArg({C)V
+        
+ */ + private void charArrayArg(char[] arg) { setProperty(Arrays.toString(arg)); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#objArg(Ljava/lang/Object;)V
+        
+ */ + private void objArg(Object arg) { setProperty(arg.toString()); } +/** + * InTrace Method specifier syntax: +
+org.intracetest.agent.ArgumentTypes#objArrayArg({Ljava/lang/Object;)V
+        
+ */ + private void objArrayArg(Object[] arg) { setProperty(Arrays.toString(arg)); @@ -169,4 +301,20 @@ private void setProperty(String value) { System.setProperty("test-key", value); } + + public static void main(String args[]) throws Exception { + System.out.println("Currently running sample code in background thread."); + System.out.println("Make sure this program has -javaagent:./path/to/intrace-agent.jar on its command line."); + System.out.println("Start InTrace GUI, download-able at https://mchr3k.github.io/org.intrace/"); + System.out.println("'Connect' & then configure GUI to trace any of these patterns:"); + System.out.println("org.intracetest.agent.ArgumentTypes#byteArrayArg({B)V -- to trace this single method"); + System.out.println("org.intracetest.agent.ArgumentTypes#shortArg(S)V -- to trace this single method"); + System.out.println("org.intracetest.agent.ArgumentTypes -- to trace all methods in class ArgumentTypes"); + System.out.println("Press Ctrl+C to quit this program."); + Runnable r = new ArgumentTypes(); + while(true) { + new Thread(r).start(); + Thread.sleep(2000); + } + } } diff --git a/org.intrace/testsrc/org/intracetest/agent/TracingSingleMethodTest.java b/org.intrace/testsrc/org/intracetest/agent/TracingSingleMethodTest.java new file mode 100644 index 0000000..60dc485 --- /dev/null +++ b/org.intrace/testsrc/org/intracetest/agent/TracingSingleMethodTest.java @@ -0,0 +1,386 @@ +package org.intracetest.agent; + +import static org.easymock.EasyMock.isA; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +import junit.framework.TestCase; + +import org.easymock.IAnswer; +import org.easymock.EasyMock; +import org.intrace.output.AgentHelper; +import org.intrace.output.IInstrumentationHandler; +import org.intrace.shared.AgentConfigConstants; +import org.intrace.shared.TraceConfigConstants; + +/** + * This test tests the Agent. To run this test you must run the test with this + * JVM argument: "-javaagent:built/traceagent_test.jar=" + */ +public class TracingSingleMethodTest extends TestCase +{ + private Receiver receiver; + private Sender sender; + private Socket socket; + + private String M1 = "org.intracetest.agent.ArgumentTypes#boolArrayArrayArg({{Z)V"; + private String M2 = "org.intracetest.agent.ArgumentTypes#objArrayArg({Ljava/lang/Object;)V"; + private String M3 = "org.intracetest.agent.ArgumentTypes#doubleArg(D)V"; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + deleteOldClassFiles(); + AgentHelper.setInstrumentationHandler(null); + // Wait for agent to startup + Thread.sleep(500); + connectToAgent(); + testSetting(AgentConfigConstants.INSTRU_ENABLED, "false"); + } + + private void deleteOldClassFiles() + { + File genbin = new File("./genbin/"); + File[] files = genbin.listFiles(); + if (files != null) + { + for (int i = 0; i < files.length; i++) + { + if (files[i].isDirectory()) + { + deleteDirectory(files[i]); + } + else + { + files[i].delete(); + } + } + } + } + + public static boolean deleteDirectory(File path) + { + if (path.exists()) + { + File[] files = path.listFiles(); + for (int i = 0; i < files.length; i++) + { + if (files[i].isDirectory()) + { + deleteDirectory(files[i]); + } + else + { + files[i].delete(); + } + } + } + return (path.delete()); + } + + + @SuppressWarnings("unchecked") + private void testSetting(String configConstant, String configValue) + throws Exception + { + + Map settingsResponseMap = sendAndReceiveSettings(configConstant, configValue); + //System.out.println("Compar1 act: " + settingsResponseMap.get(configConstant) ); + //System.out.println("Compar2 exp: " + configValue); + assertEquals(configValue, settingsResponseMap.get(configConstant)); + } + + @SuppressWarnings("unchecked") + private Map sendAndReceiveSettings(String configConstant, String configValue) + throws Exception + { + // Set setting + sender.sendMessage(configConstant + configValue); + Object okResponse = receiver.incomingMessages.take(); + assertNotNull(okResponse); + assertTrue(okResponse instanceof String); + assertEquals(okResponse, "OK"); + + // Get settings + sender.sendMessage("getsettings"); + Object settingsResponse = receiver.incomingMessages.take(); + assertNotNull(settingsResponse); + assertTrue(settingsResponse instanceof Map); + Map settingsResponseMap = (Map) settingsResponse; + return settingsResponseMap; + } + + + + public void testArgumentTypesForOneMethod() throws Throwable { + + // Create and init the mock + final BlockingQueue capturedTrace = new LinkedBlockingQueue(); + IInstrumentationHandler testHandler = new ArgCapture(capturedTrace); + AgentHelper.setInstrumentationHandler(testHandler); + + // Setup agent + testSetting(AgentConfigConstants.INSTRU_ENABLED, "false"); + + //The intArg() method takes a single int parameter...let's trace just this one method. + testSetting(AgentConfigConstants.CLASS_REGEX, "org.intracetest.agent.ArgumentTypes#intArg(I)V"); + testSetting(AgentConfigConstants.VERBOSE_MODE, "false"); + testSetting(AgentConfigConstants.SAVE_TRACED_CLASSFILES, "true"); + testSetting(AgentConfigConstants.INSTRU_ENABLED, "true"); + + // Run Patterns thread + ArgumentTypes argTypes = new ArgumentTypes(); + Thread patternThread = new Thread(argTypes); + patternThread.start(); + patternThread.join(5 * 1000); + if (argTypes.th != null) + { + throw argTypes.th; + } + + // Parse the trace + Map parsedTraceData = new HashMap(); + String traceLine = capturedTrace.poll(); + while (traceLine != null) + { + parseLine(parsedTraceData, traceLine); + traceLine = capturedTrace.poll(); + } + + /** + Method-level tracing to the rescue! + This map contains trace lines (3 of them) for exactly 1 method invocation + because the new method-level-tracing feature specified only one method. + */ + assertEquals("Exactly 1 TraceData object for method intArg() should have been in this map",1,parsedTraceData.size() ); + + assertNotNull(parsedTraceData.get("intArg")); + { + TraceData trData = parsedTraceData.get("intArg"); + assertEquals(1, trData.args.size()); + assertEquals("4", trData.args.get(0)); + } + + +} + + + + private void parseLine(Map parsedTraceData, + String traceLine) + { + //System.out.println("Parse: " + traceLine); + if (traceLine.contains("DEBUG")) + return; + String[] traceParts = traceLine.split(":##:"); + String traceType = traceParts[0]; + String traceLineData = traceParts[1]; + traceLineData = traceLineData.substring(1, traceLineData.length() - 1); + String[] dataParts = traceLineData.split(","); + String methodSig; + if (traceType.equals("Throwable")) + { + methodSig = dataParts[2].trim(); + } + else + { + methodSig = dataParts[1].trim(); + } + + TraceData traceData = parsedTraceData.get(methodSig); + if (traceData == null) + { + traceData = new TraceData(); + parsedTraceData.put(methodSig, traceData); + } + + if (traceType.equals("Enter")) + { + traceData.seenEnter = true; + } + else if (traceType.equals("Exit")) + { + traceData.seenExit = true; + } + else if (traceType.equals("Branch")) + { + traceData.branchLines.add(Integer.parseInt(dataParts[2].trim())); + } + else if (traceType.equals("Throwable")) + { + if (traceLine.contains("Caught")) + { + traceData.caughtLines.add(Integer.parseInt(dataParts[3].trim())); + } + } + else if (traceType.startsWith("Arg")) + { + traceData.args.add(dataParts[2].trim()); + } + } + + + @Override + protected void tearDown() throws Exception + { + receiver.stop(); + sender.stop(); + socket.close(); + super.tearDown(); + } + + private void connectToAgent() throws Exception + { + String host = "localhost"; + int port = Integer.parseInt(System.getProperty("org.intrace.port")); + + socket = new Socket(); + socket.connect(new InetSocketAddress(host, port)); + + // Start threads + receiver = new Receiver(socket.getInputStream()); + receiver.start(); + sender = new Sender(socket.getOutputStream()); + sender.start(); + } + + private static class Sender implements Runnable + { + private final OutputStream outputStream; + private final BlockingQueue outgoingMessages = new LinkedBlockingQueue(); + private Thread th; + + public Sender(OutputStream outputStream) + { + this.outputStream = outputStream; + } + + public void stop() + { + try + { + outputStream.close(); + } + catch (IOException e) + { + // Throw away + } + th.interrupt(); + } + + public void start() + { + th = new Thread(this); + th.setDaemon(true); + th.setName("Sender"); + th.start(); + } + + @Override + public void run() + { + try + { + while (true) + { + String message = outgoingMessages.take(); + ObjectOutputStream objOut = new ObjectOutputStream(outputStream); + objOut.writeObject(message); + objOut.flush(); + } + } + catch (Exception e) + { + // Do something + } + } + + public void sendMessage(String message) + { + try + { + outgoingMessages.put(message); + } + catch (InterruptedException e) + { + // Do nothing + } + } + } + + private static class Receiver implements Runnable + { + private final InputStream inputStream; + private final BlockingQueue incomingMessages = new LinkedBlockingQueue(); + private Thread th; + + public Receiver(InputStream inputStream) + { + this.inputStream = inputStream; + } + + public void stop() + { + try + { + inputStream.close(); + } + catch (IOException e) + { + // Throw away + } + th.interrupt(); + } + + public void start() + { + th = new Thread(this); + th.setDaemon(true); + th.setName("Receiver"); + th.start(); + } + + @Override + public void run() + { + try + { + while (true) + { + ObjectInputStream objIn = new ObjectInputStream(inputStream); + Object receivedMessage = objIn.readObject(); + // System.out.println("Test received: " + receivedMessage); + if (receivedMessage instanceof Map) + { + Map receivedMap = (Map) receivedMessage; + if (receivedMap.containsKey(AgentConfigConstants.NUM_PROGRESS_ID)|| + receivedMap.containsKey(AgentConfigConstants.STID)) + { + continue; + } + } + incomingMessages.put(receivedMessage); + } + } + catch (Exception e) + { + // Do nothing + } + } + } +} diff --git a/org.intrace/testsrc/org/intracetest/agent/TracingThreeMethodsTest.java b/org.intrace/testsrc/org/intracetest/agent/TracingThreeMethodsTest.java new file mode 100644 index 0000000..56fe28b --- /dev/null +++ b/org.intrace/testsrc/org/intracetest/agent/TracingThreeMethodsTest.java @@ -0,0 +1,387 @@ +package org.intracetest.agent; + +import static org.easymock.EasyMock.isA; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +import junit.framework.TestCase; + +import org.easymock.IAnswer; +import org.easymock.EasyMock; +import org.intrace.output.AgentHelper; +import org.intrace.output.IInstrumentationHandler; +import org.intrace.shared.AgentConfigConstants; +import org.intrace.shared.TraceConfigConstants; + +/** + * This test tests the Agent. To run this test you must run the test with this + * JVM argument: "-javaagent:built/traceagent_test.jar=" + */ +public class TracingThreeMethodsTest extends TestCase +{ + private Receiver receiver; + private Sender sender; + private Socket socket; + + private String M1 = "org.intracetest.agent.ArgumentTypes#boolArrayArrayArg({{Z)V"; + private String M2 = "org.intracetest.agent.ArgumentTypes#objArrayArg({Ljava/lang/Object;)V"; + private String M3 = "org.intracetest.agent.ArgumentTypes#doubleArg(D)V"; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + deleteOldClassFiles(); + AgentHelper.setInstrumentationHandler(null); + // Wait for agent to startup + Thread.sleep(500); + connectToAgent(); + testSetting(AgentConfigConstants.INSTRU_ENABLED, "false"); + } + + private void deleteOldClassFiles() + { + File genbin = new File("./genbin/"); + File[] files = genbin.listFiles(); + if (files != null) + { + for (int i = 0; i < files.length; i++) + { + if (files[i].isDirectory()) + { + deleteDirectory(files[i]); + } + else + { + files[i].delete(); + } + } + } + } + + public static boolean deleteDirectory(File path) + { + if (path.exists()) + { + File[] files = path.listFiles(); + for (int i = 0; i < files.length; i++) + { + if (files[i].isDirectory()) + { + deleteDirectory(files[i]); + } + else + { + files[i].delete(); + } + } + } + return (path.delete()); + } + + + @SuppressWarnings("unchecked") + private void testSetting(String configConstant, String configValue) + throws Exception + { + + Map settingsResponseMap = sendAndReceiveSettings(configConstant, configValue); + //System.out.println("Compar1 act: " + settingsResponseMap.get(configConstant) ); + //System.out.println("Compar2 exp: " + configValue); + assertEquals(configValue, settingsResponseMap.get(configConstant)); + } + + @SuppressWarnings("unchecked") + private Map sendAndReceiveSettings(String configConstant, String configValue) + throws Exception + { + // Set setting + sender.sendMessage(configConstant + configValue); + Object okResponse = receiver.incomingMessages.take(); + assertNotNull(okResponse); + assertTrue(okResponse instanceof String); + assertEquals(okResponse, "OK"); + + // Get settings + sender.sendMessage("getsettings"); + Object settingsResponse = receiver.incomingMessages.take(); + assertNotNull(settingsResponse); + assertTrue(settingsResponse instanceof Map); + Map settingsResponseMap = (Map) settingsResponse; + return settingsResponseMap; + } + + + + public void testArgumentTypesForThreeMethods() throws Throwable { + + // Create and init the mock + final BlockingQueue capturedTrace = new LinkedBlockingQueue(); + IInstrumentationHandler testHandler = new ArgCapture(capturedTrace); + AgentHelper.setInstrumentationHandler(testHandler); + + // Setup agent + testSetting(AgentConfigConstants.INSTRU_ENABLED, "false"); + + String myCriteria = M1 + "|" + M2 + "|" + M3; + //let's trace just these 3 methods + testSetting(AgentConfigConstants.CLASS_REGEX, myCriteria); + Map settingsResponseMap = sendAndReceiveSettings(AgentConfigConstants.CLASS_REGEX, myCriteria); + assertNotNull( settingsResponseMap); + String unsortedRegex = settingsResponseMap.get(AgentConfigConstants.CLASS_REGEX); + assertNotNull(unsortedRegex); + String[] expected = { M1, M2, M3 }; + String[] actual = unsortedRegex.split("\\|"); + Arrays.sort(expected); + Arrays.sort(actual); + assertTrue(Arrays.equals(expected,actual)); + + + testSetting(AgentConfigConstants.VERBOSE_MODE, "false"); + testSetting(AgentConfigConstants.SAVE_TRACED_CLASSFILES, "true"); + testSetting(AgentConfigConstants.INSTRU_ENABLED, "true"); + + // Run Patterns thread + ArgumentTypes argTypes = new ArgumentTypes(); + Thread patternThread = new Thread(argTypes); + patternThread.start(); + patternThread.join(5 * 1000); + if (argTypes.th != null) + { + throw argTypes.th; + } + + // Parse the trace + Map parsedTraceData = new HashMap(); + String traceLine = capturedTrace.poll(); + while (traceLine != null) + { + parseLine(parsedTraceData, traceLine); + traceLine = capturedTrace.poll(); + } + + /** + Method-level tracing to the rescue! + This map contains only 1 entry because the new method-level-tracing feature specified only one method. + */ + assertEquals("Exactly three TraceData objects should have been in this map",3,parsedTraceData.size() ); +} + + + private void parseLine(Map parsedTraceData, + String traceLine) + { + //System.out.println("Parse: " + traceLine); + if (traceLine.contains("DEBUG")) + return; + String[] traceParts = traceLine.split(":##:"); + String traceType = traceParts[0]; + String traceLineData = traceParts[1]; + traceLineData = traceLineData.substring(1, traceLineData.length() - 1); + String[] dataParts = traceLineData.split(","); + String methodSig; + if (traceType.equals("Throwable")) + { + methodSig = dataParts[2].trim(); + } + else + { + methodSig = dataParts[1].trim(); + } + + TraceData traceData = parsedTraceData.get(methodSig); + if (traceData == null) + { + traceData = new TraceData(); + parsedTraceData.put(methodSig, traceData); + } + + if (traceType.equals("Enter")) + { + traceData.seenEnter = true; + } + else if (traceType.equals("Exit")) + { + traceData.seenExit = true; + } + else if (traceType.equals("Branch")) + { + traceData.branchLines.add(Integer.parseInt(dataParts[2].trim())); + } + else if (traceType.equals("Throwable")) + { + if (traceLine.contains("Caught")) + { + traceData.caughtLines.add(Integer.parseInt(dataParts[3].trim())); + } + } + else if (traceType.startsWith("Arg")) + { + traceData.args.add(dataParts[2].trim()); + } + } + + + @Override + protected void tearDown() throws Exception + { + receiver.stop(); + sender.stop(); + socket.close(); + super.tearDown(); + } + + private void connectToAgent() throws Exception + { + String host = "localhost"; + int port = Integer.parseInt(System.getProperty("org.intrace.port")); + + socket = new Socket(); + socket.connect(new InetSocketAddress(host, port)); + + // Start threads + receiver = new Receiver(socket.getInputStream()); + receiver.start(); + sender = new Sender(socket.getOutputStream()); + sender.start(); + } + + private static class Sender implements Runnable + { + private final OutputStream outputStream; + private final BlockingQueue outgoingMessages = new LinkedBlockingQueue(); + private Thread th; + + public Sender(OutputStream outputStream) + { + this.outputStream = outputStream; + } + + public void stop() + { + try + { + outputStream.close(); + } + catch (IOException e) + { + // Throw away + } + th.interrupt(); + } + + public void start() + { + th = new Thread(this); + th.setDaemon(true); + th.setName("Sender"); + th.start(); + } + + @Override + public void run() + { + try + { + while (true) + { + String message = outgoingMessages.take(); + ObjectOutputStream objOut = new ObjectOutputStream(outputStream); + objOut.writeObject(message); + objOut.flush(); + } + } + catch (Exception e) + { + // Do something + } + } + + public void sendMessage(String message) + { + try + { + outgoingMessages.put(message); + } + catch (InterruptedException e) + { + // Do nothing + } + } + } + + private static class Receiver implements Runnable + { + private final InputStream inputStream; + private final BlockingQueue incomingMessages = new LinkedBlockingQueue(); + private Thread th; + + public Receiver(InputStream inputStream) + { + this.inputStream = inputStream; + } + + public void stop() + { + try + { + inputStream.close(); + } + catch (IOException e) + { + // Throw away + } + th.interrupt(); + } + + public void start() + { + th = new Thread(this); + th.setDaemon(true); + th.setName("Receiver"); + th.start(); + } + + @Override + public void run() + { + try + { + while (true) + { + ObjectInputStream objIn = new ObjectInputStream(inputStream); + Object receivedMessage = objIn.readObject(); + // System.out.println("Test received: " + receivedMessage); + if (receivedMessage instanceof Map) + { + Map receivedMap = (Map) receivedMessage; + if (receivedMap.containsKey(AgentConfigConstants.NUM_PROGRESS_ID)|| + receivedMap.containsKey(AgentConfigConstants.STID)) + { + continue; + } + } + incomingMessages.put(receivedMessage); + } + } + catch (Exception e) + { + // Do nothing + } + } + } +}