Copyright 2010 Simon Massey. All rights reserved. net.sf.chex4j is licensed
under the terms of the Eclipse Public License v1.0, as defined by the attached
file LICENSE-ECLIPSE.txt.net.sf.chex4j is licensed under the terms of the
Eclipse Public License v1.0, as defined by the attached file
LICENSE-ECLIPSE.txt.
Simon Massey (simon at my full name ( no punctuation ) dot org).
Description:
chex4j is a framework for documenting and enforcing the pre- and
post-conditions of method calls using classloader instrumentation. Inspired
by contract4j it is intended to enforce a form of Design By Contract. It
can be run during unit testing or on test servers with little or no overhead.
To use chex4j you add @Contract, @Pre and @Post conditions to methods
within your classes and interfaces:
@Contract
public class SimplePublicBankAccount {
@Pre(message = "Initial balance should be greater than or equal to zero.",
value = "amount.doubleValue() >= 0.0d")
public SimplePublicBankAccount(BigDecimal amount ){
this.balance = amount;
}
@Post(message = "Balance should be greater than zero.",
value = "$_.doubleValue() >= 0.0d")
public BigDecimal getBalance() {
return this.balance;
}
The annotations act as additional documentation in addition to your JavaDoc
and the implicit contract of your classes documented within your test
cases. If you annotate methods on an interface then load 3rd party code
then the contracts are compiled and injected into that 3rd party code
at class load time. There is an option to output the instrumented class
files so that they can be packaged up and deployed. When activated through
Chex4j will cause the method in question to throw an AssertionError
detailing the chex that failed - this reflects the fact that it is a
programmatic error to violate the constraint.
When you enable the chex4j javaagent within your IDE or test builds the
value attribute of the annotations will be compiled into java bytecode
using the jboss javassist framework and injected into the body of the
methods either at the beginning or end of the method. The syntax of the
injected code can be any valid Javassist code which evaluates to a boolean
e.g. $_ is a method return value:
whatever Any valid java code. Javassist appears to reverse engineer the source code of the method body being augmented.
$0, $1, $2, ... Actual parameters by position (else just use their name)
$args An array of parameters. The type of $args is Object[].
$$ All actual parameters. For example, m($$) is equivalent to m($1,$2,...)
$cflow(...) cflow variable
$r The result type. It is used in a cast expression.
$w The wrapper type. It is used in a cast expression.
$_ The resulting value
$sig An array of java.lang.Class objects representing the formal parameter types.
$type A java.lang.Class object representing the formal result type.
$class A java.lang.Class object representing the class currently edited.
see http://www.csg.is.titech.ac.jp/~chiba/javassist/tutorial/tutorial2.html#before
Running It:
chex4j is a javaagent which instruments classes at load time using the
JBoss Javaassist toolkit. This means that Javassist has to be loadable
when your classes are loaded.
The correct version of Javassit to use is named within the pom.xml file
which is within the chex4j jar file near:
/META-INF/maven/net.sf.chex4j/chex4j/pom.xml
this can be made accessible to the chex4j javaagent with a JVM argument
such as:
-Xbootclasspath/a:/path/to/javassist-3.11.0.GA.jar
The chex4j javaagent is be run from within the chex4j jar file with a VM
flag such as:
-javaagent:target/chex4j-core-1.0.0.jar=net.sf.chex4j...,chex4j.test...,dir=target/chex4j
where after the '=' is a comma separated list of package names for the
javaagent to byte code instrument. Note that you must add the "..." to
each package name which is the same syntax as the standard JVM -ea flag.
The optional 'dir=' flag is where to dump out the instrumented class files
in case you want to package and deploy those to a test server.
Take a look at the chex4j-test/build.xml to see an example of using ant to
fork a jvm enabling the javaagent as documented above.
Building It:
On the commandline a build looks like:
mvn install
The easiest way to run the junit test is to simply use your IDE to run the
test-instrumented-classes target in the chex4j-core/build.xml file.
I develop on Eclipse Galileo using Subclipse Plugin and m2eclispe version 0.9.8
plugin. To run the junit tests in-situ I click to run junit on ChexJavaAgentSuite
then I edit the run configuration option to add the VM Argument
-javaagent:../chex4j-core/target/chex4j-core-1.0.0.jar=net.sf.chex4j...,net.sf.chex4j.test...,dir=target/instrumented-classes
note that m2eclispe puts javassist jar file on the eclipse junit classpath.
The second test suite ChexOfflineSuite should be run *without* the VM
Argument.
To Do List (some or all before a release):
* Tested on Sun jdk1.5, 1.6 and 1.7 should perhaps test with IBM JVM and JRockit JVM.
* Write up junit/testNJ how-to for eclipse, netbeans and intelliJ.
* Try it with a container or two (tomcat and jetty). Dumping instrumented classes to disk and packaging them would make container support trivial.
* Many different checks with @PreChex { @Pre(value="balance > 0.0d", message="cannot be overdrawn"), @Pre("amount > 0.0d") }
Release Notes:
1.0.0-RC1
Release Candidate 1. Fixed missing Hamcrest jar on forked jvm chex-test.
1.0.0-FL20100815
Finally settled on a strategy for junit testing the javaagent: moved the
tests into a different project 'chex4j-test' as the main classes in that
project. The test project has skip-tests true but then forks a jvm with
ant which runs activates the javaagent and invokes junit test runner on
a test suite.
1.0.0-FL20100221
Have refactored to create OfflineClassTransformer to do build time class
transformations.
1.0.0-FL20100221
Broken out the main code into a module below a superpom and have added the
beginnings of maven plugin. The plugin is there to launch a new JVM with
the chex4j javaagent enabled during the course of the build.
For test builds throwing an AssertionEror might be a bit too strong. The
Assert class now looks for
-Dnet.sf.chex4j.internal.Assert.DONT_THROW_ERROR=true
and will only complain to System.err if it finds that entry.
1.0.0-FL20100216
Added ant build script (mostly generated with "mvn ant:ant") with a target
test-instrumented-classes which forks java and runs the instrumented classes
within junit.
Added skip tests 'true' to the maven surefile plug-in configuration.
Added an option to dump out the instrumented class files using an optional
dir= option to the javaagent:
-javaagent:target/chex4j-1.0.0-FL20100216.jar=net.sf.chex4j...,chex4j.test...,dir=target/chex4j
This should allow for packaging up the instrumented classes to run on a
test server. May also be a good route into ant integration.
1.0.0-FL20100214
For features see the JUnit tests under src/test/java.
@Pre and @Post seem to work on constructors, public class methods,
package protected methods, interface methods and in abstract class
hierarchies.
Works with linux jre1.5.0_21, jdk1.6.0_16 and jdk1.7.0_b83
as well as windows jre1.6.
To use this library you must specify the following VM argument:
-javaagent:path/to/chex4j-1.0.0-FL20100214.jar=com.x.y...,com.a.b...
where com.x.y and com.a.b are the package name of the classes that
you wish to instrument. You must add three full stops to the end of them
to match the syntax of the standard JVM -ea option.
"mvn test" does not run the tests successfully as the javaagent is not
being applied. Eclipse junit runner works fine with the javaagent flag.
This is likely to be classworlds/plexus classloader stuff. I will take a
look at alternative ways to fork the tests.
Enjoy,
Simon