Friday, February 18, 2011

Using Google Contracts for Java with IntelliJ IDEA.

The first step after obtaining the Google Contracts for Java Jar and adding it to your project, is to enable Annotation processing support in IDEA.

To do this go to open the Settings window (IntelliJ IDEA > preferences on Mac) and go to Compiler > Annotation Processors.

Now do the following steps:

  • Check the Enable Annotation processing option.
  • Select the Obtain processors from project classpath option.
  • In the Processed Modules table click the Add button and select the Module for which you want to enable Contracts for.
  • Hit Apply and you are ready to rock.


To test this you can add a basic contract annotation to a method in a class, here is one that I created using the @Requires annotation to ensure that the integer method parameter "c" has a value greater than zero:

import com.google.java.contract.Requires;

public class TestSomeContracts {

@Requires({"c > 0"})
public void testContract(int c) {

}

}

Now when you compile you wont get much feedback as to wether the annotation was processed or not, as the contract syntax is correct, so lets modify it a bit to generate a compile splat by changing the variable name of the precondition:

@Requires({"cs > 0"})
public void testContract(int c) {

}

When you build the module, you will now get a compilation failure which kinda integrates into IDEA, in that you can click on the error in the messages window and it will take you to the line that failed. Unfortunately IDEA wont highlight the line in your Editor or anything fancy like what you get in Eclipse, but it's good enough to work with.



Using a class with contracts.

After reverting and compiling the class I created a simple test case to test the contract by passing in data that violates the contract (ie. an integer less than 1):


import org.junit.Test;

public class TestSomeContractsTest {


@Test
public void testContract() {
new TestSomeContracts().testContract(-1);
}

}

I run the test… and... it passes!

In order to actually work, Google Contracts needs to do some bytecode shenanigans in order to actually enforce the contract definitions during runtime. Currently they have two modes of operation, an offline instrumenter which is a post compilation processor which weaves in the contracts into a compiled class, and a java agent.

For development the most convenient method to use is the java agent.

To use the agent in IDEA, click the Select Run/Debug Settings drop down and select the Edit configurations option. Expand the defaults entry and select the JUnit option (or TestNG or just plain Application) and add the following to the VM parameters field:

-javaagent:[path to the Google Contracts for Java jar file]

I Also remove an existing configuration so that it picks up the new option when I run the test again.

Now I run the test again and - voila - nice big splat when I run the test and pass invalid data to the method:

com.google.java.contract.PreconditionError: c > 0
at TestSomeContracts.com$google$java$contract$PH$TestSomeContracts$testContract(TestSomeContracts.java:9)
at TestSomeContracts.testContract(TestSomeContracts.java)
at TestSomeContractsTest.testContract(TestSomeContractsTest.java:9)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:65)

On a final note. Google contracts is still pretty fresh, so here's hoping IDE support will improve if the project takes off. I must also say that I'm not too fond of the current mechanisms for enforcing the contract. Hopefully Google might take a page from Lombok and do to weaving at compile time.

No comments: