If you have not yet used BMUnit . . .
The BMUnit package integrates Byteman with the popular Java unit and integration test automation tools, JUnit and TestNG. BMUnit allows you to automatically install the Byteman agent and upload/unload Byteman rules into/from your test JVM using a few simple annotations on test classes or methods. If you have not already used BMUnit then take a look at the Byteman fault injection tutorial. It provides a complete worked example which demonstrates how easy it is to use BMUnit and Byteman to engineer reliable, repeatable tests. The example code is a simple, thread-parallel application which would be very difficult to test without using Byteman. It provides a great example of the power of fault injection as a test methodology.Configuring BMUnit via system properties
Up until now the environment in which both BMUnit and its underlying Byteman agent execute your tests has only been configurable via system property settings. Not only is this a clumsy and error-prone control model, it also allows only one configuration to be defined which gets used for all tests.Switching on BMUnit trace
For example, if you want to trace operations of BMUnit, watching it upload and unload Byteman rules or checking that it has indeed installed the Byteman agent into your test JVM you would modify your maven pom as follows1: <systemProperties>
2: <property>
3: <name>org.jboss.byteman.contrib.bmunit.verbose</name>
4: <value>true</value>
5: </property>
6: </systemProperties>
After applying this change to the pom in the junit directory of the tutorial you will see BMUnit trace output as the tests are executed [adinn@zenade byteman-tutorial2]$ mvn -P junit test
/usr/lib/jvm/java-1.7.0
[INFO] Scanning for projects...
. . .
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running org.my.BytemanJUnitTests
BMUNit : loading agent id = 31122
byteman jar is /home/adinn/.m2/repository/org/jboss/byteman/byteman/2.1.4/byteman-2.1.4.jar
BMUNit : loading file script = target/test-classes/trace.btm
testPipeLine:
#INPUT
hello world!
. . .
#END
entered run for org.my.pipeline.impl.CharSequenceSink
. . .
exited run for org.my.pipeline.impl.CharSequenceSink
#OUTPUT
hello mum!
. . .
#END
BMUNit : unloading file script = target/test-classes/trace.btm
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.443 sec
. . .
As you can see, BMunit tells you it is loading the Byteman agent into the Java process with pid 31122 and then prints the location of the Byteman jar. It reports that it is uploading a rule script before running the first test and then tells you when it unloads the test script.Switching on Byteman verbose trace
For more detailed trace you could also enable the Byteman agent's verbose trace [adinn@zenade byteman-tutorial2]$ mvn -P junit test
/usr/lib/jvm/java-1.7.0
[INFO] Scanning for projects...
. . .
Running org.my.BytemanJUnitTests
BMUNit : loading agent id = 5025
byteman jar is /ssd/home/adinn/.m2/repository/org/jboss/byteman/byteman/2.1.4/byteman-2.1.4.jar
TransformListener() : accepting requests on localhost:9091
BMUNit : loading file script = target/test-classes/trace.btm
TransformListener() : handling connection on port 9091
testPipeLine:
org.jboss.byteman.agent.Transformer : possible trigger for rule trace input characters in class org.my.pipeline.impl.CharSequenceSource
RuleTriggerMethodAdapter.injectTriggerPoint : inserting trigger into org.my.pipeline.impl.CharSequenceSource.<init>(java.lang.CharSequence) void for rule trace input characters
org.jboss.byteman.agent.Transformer : inserted trigger for trace input characters in class org.my.pipeline.impl.CharSequenceSource
org.jboss.byteman.agent.Transformer : possible trigger for rule trace Thread.run entry in class org.my.pipeline.core.SourceProcessor
RuleTriggerMethodAdapter.injectTriggerPoint : inserting trigger into org.my.pipeline.core.SourceProcessor.run() void for rule trace Thread.run entry
org.jboss.byteman.agent.Transformer : inserted trigger for trace Thread.run entry in class org.my.pipeline.core.SourceProcessor
org.jboss.byteman.agent.Transformer : possible trigger for rule trace Thread.run exit in class org.my.pipeline.core.SourceProcessor
RuleTriggerMethodAdapter.injectTriggerPoint : inserting trigger into org.my.pipeline.core.SourceProcessor.run() void for rule trace Thread.run exit
RuleTriggerMethodAdapter.injectTriggerPoint : inserting trigger into org.my.pipeline.core.SourceProcessor.run() void for rule trace Thread.run exit
org.jboss.byteman.agent.Transformer : inserted trigger for trace Thread.run exit in class org.my.pipeline.core.SourceProcessor
Rule.execute called for trace input characters_0
HelperManager.install for helper class org.jboss.byteman.rule.helper.Helper
calling activated() for helper class org.jboss.byteman.rule.helper.Helper
Default helper activated
calling installed(trace input characters) for helper classorg.jboss.byteman.rule.helper.Helper
Installed rule using default helper : trace input characters
trace input characters execute
#INPUT
hello world!
. . .
This time the agent's trace messages show it opening a server socket and listening for requests. After the BMUnit trace for the first file load you can see the agent's socket listener handling the connection for the upload. Next, as various rule target classes get loaded, the agent prints messages showing rule code being injected into the target methods. The inject messages are followed by tracing of an execution event for the first rule and the associated "activated" and "installed" helper lifecycle events. Once lifecycle management has completed the rule finally gets executed and the test continues, printing its output.Other configuration choices
There are a variety of other system properties which can be used to configure different aspects of how BMUnit and Byteman operate. For example, it is possible to reset the hostname and port used by the agent listener when it waits for rules to be uploaded. These same settings are also used by BMUNit to dispatch upload and unload requests. You can also set the default directory in which to look for rule scripts, disable automatic loading of the agent and so on.Better configuration via annotations
This configuration capability is very useful but it suffers from a few drawbacks. As mentioned above, it just provides a single global setting for all tests in the test execution. So, even though you might only be interested in tracing what BMUnit or the Byteman agent is doing when executing a specific failing test you have enable tracing for all tests and then wade through the output to find the relevant printout.A second problem is that configuring the relevant properties is error prone. It is very easy to mis-spell the long property names employed by Byteman and BMUnit. Also, unlike the annotations used to mark up BMUnit tests these properties get configured in the pom (or ant build) file, separate from the tests themselves.
The @BMUNitConfig annotation
The latest version of BMUnit (2.2.0) implements a new configuration annotation, @BMUnitConfig which addresses all these problems. Annotation attributes can be used to specify all the different BMUnit and Byteman behaviours currently managed using system properties. An @BMUnitConfig annotation can be attached to a test class in order to specify the configuration for all tests in that class. It can also be attached to a test method, overriding the class level settings just for the duration of that specific test. The same annotation works with both JUnit and TestNG.So, for example you could modify the JUnit example test class executed in the above example as follows
1: @RunWith(BMUnitRunner.class)
2: @BMUnitConfig(bmunitVerbose=true, agentPort="99999")
3: @BMScript(value="trace", dir="target/test-classes")
4: public class BytemanJUnitTests
5: {
6: @Test
7: @BMUnitConfig(bmunitVerbose=false, debug=true)
8: public void testPipeline() throws Exception
9: {
10: . . .
Class level configuration
The initial, class-level annotation configures a different agent port to the default. So, when the test runs and the agent gets installed it will listen on port 99999. BMUnit will also use the same port to upload and unload rules specified via @BMRule or @BMScript annotations.The class annotation also sets the bmunitVerbose attribute to true. This has the same effect as configuring the system property which controls BMUnit trace. So, when the test is run BMUnit will trace auto-loading of the agent and rule upload/unload.
Method level configuration
The annotation on test method testPipeline() resets the bmunitVerbose attribute to false, disabling BMUnit trace during execution of this specific test. It also sets the debug attribute to true, enabling Byteman debug trace.Byteman debug trace is normally disabled, which means that calls to built-in method debug don't print any output. Switching on debug like this is very useful when you are trying to understand what your rules are doing. You can add debug messages to all your rules but you will still only see debug messages associated with the rules fired during this test.
Notice that the annotation fields have much simpler names than the corresponding system properties. Between your IDE and the javac compiler any misspellings will be discovered very quickly.
This comment has been removed by a blog administrator.
ReplyDelete