Terracotta Forge View a printable version of the current page.  
  Testing Terracotta with Maven

Creating a new Maven project with Terracotta

Terracotta provides two Maven archetypes that can be used to generate a new project structure for projects that use Terracotta. The archetypes are the pojo-archetype, for a basic Java project and the webapp-archetype, for web applications using Terracotta.

To use the pojo archetype, invoke it with mvn like this:

mvn org.apache.maven.plugins:maven-archetype-plugin:1.0-alpha-7:create \
  -DarchetypeGroupId=org.terracotta.maven.archetypes \
  -DarchetypeArtifactId=pojo-archetype \
  -DarchetypeVersion=1.2.1 \
  -DremoteRepositories=http://www.terracotta.org/download/reflector/maven2 \
  -DgroupId=com.donkey \
  -DartifactId=bingo \
  -Dversion=1.0.0

This will create a subdirectory named bingo with a full Maven project structure for a Java app. In expected Maven style the structure will look like:

|-src
   |---main
   |-----assembly
   |-----java
   |-------com
   |---------donkey
   |-----------demo
   |-----resources
   |-------META-INF
   |---site
   |-----apt
   |-------docs
   |-----fml
   |---test
   |-----java
   |-------com
   |---------donkey

There is a demo Java class and a demo unit test provided. At the root is a pom.xml defining the Maven project and a tc-config.xml for the project. You can import this project into Eclipse using the m2eclipse Eclipse plugin. Once the project has been imported, you should be able to run the demo unit test.

If you are using the Terracotta Eclipse plugin, you can also add the Terracotta nature to this project and run the unit test as a Terracotta JUnit test.

Integrating Terracotta into an existing Maven project

It's quite possible that you created a Maven project without starting from a Terracotta archetype, and that's perfectly ok too. This section focuses on how to integrate the tc-maven-plugin into your pre-existing project.

First you'll need to include the Terracotta repository so you can get the tc-maven-plugin, the Terracotta product jars, and any Terracotta Integration Modules you need to download:

<repositories>
    <repository>
      <id>terracotta-repository</id>
      <url>http://www.terracotta.org/download/reflector/maven2</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>
  <pluginRepositories>
    <pluginRepository>
      <id>terracotta-repository</id>
      <url>http://www.terracotta.org/download/reflector/maven2</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </pluginRepository>
  </pluginRepositories>

Then you also need to include the tc-maven-plugin in your Maven pom.xml:

<build>
    <plugins>
      <plugin>
        <groupId>org.terracotta.maven.plugins</groupId>
        <artifactId>tc-maven-plugin</artifactId>
        <version>${tc-maven-plugin.version}</version>
        <configuration>
          <workingDirectory>${project.build.directory}</workingDirectory>
          <className>com.donkey.App</className>
          <numberOfNodes>2</numberOfNodes>
          <startServer>true</startServer>
        </configuration>
      </plugin>
    </plugins>
  </build>

Note here the use of ${} properties - ${project.build.directory} is a standard Maven property that you don't need to declare. But ${tc-maven-plugin.version} is a custom property that you will want to define in a properties block:

<properties>
    <tc-maven-plugin.version>1.2.1</tc-maven-plugin.version>
  </properties>

How to change the Terracotta version used by the plugin

The tc-maven-plugin version is bound to a version of Terracotta (as the tc-maven-plugin relies on some code in the Terracotta base source and depends on those jars). It is possible however to modify the Terracotta version that should be used when in your project without modifying the ${tc-maven-plugin.version} by instead specifying custom dependencies that override the default ones defined in tc-maven-plugin:

<dependencies>
    <dependency>
      <groupId>org.terracotta</groupId>
      <artifactId>terracotta</artifactId>
      <version>2.7.1-SNAPSHOT</version>
    </dependency>
    <dependency>
      <groupId>org.terracotta</groupId>
      <artifactId>tcconfig</artifactId>
      <version>2.7.1-SNAPSHOT</version>
    </dependency>
  </dependencies>

Using the tc-maven-plugin

You can find the full details on the tc-maven-plugin web site but some common tc-maven-plugin goals are:

  • mvn tc:bootjar - build bootjar
  • mvn tc:start - start the Terracotta server
  • mvn tc:stop - stop the Terracotta server
  • mvn tc:admin - start admin console
  • mvn tc:run - runs L1s, server, etc
  • mvn tc:test - runs Surefire/JUnit tests (like tc:run but for junit test)

The two key goals here are tc:run and tc:test. Both goals are doing the same kind of thing but differ by either a Java application (by class name) or a JUnit test.

By just adding the tc-maven-plugin to your pom, the tc-maven-plugin configuration applies to the whole pom - you specify a class to run in the pom and that's what will be run with "mvn run". That's relatively inflexible. With tc:run, that's usually ok - you are trying to typically "run your app with Terracotta" when you use this goal.

With tc:test, you have similar flexibility issues - only the tc-config.xml file specified at the root will be used, so your are very limited on the configurations you can unit test with.

Writing integration tests that use Terracotta

A more flexible approach is to use the Terracotta system test framework to test in your project. The easiest way to do this is to create a new test-only project external to your main project and use tim-system-tests-parent as your parent in your pom:

<parent>
    <groupId>org.terracotta.forge</groupId>
    <artifactId>tim-system-tests-parent</artifactId>
    <version>1.3.0-SNAPSHOT</version>
  </parent>

This will set up most of what you need automatically. You then need to import your original project by dependency. The tim-system-tests-parent will pull in a bunch of extra dependencies on the Terracotta system test framework, which is an extension to Junit.

Here's a sample pom - this pom is for tim-messagebus-system-tests which is a system test project for tim-messagebus:

<?xml version="1.0" encoding="UTF-8" ?>
<!--

   All content copyright (c) 2003-2008 Terracotta, Inc.,
   except as may otherwise be noted in a separate copyright notice.
   All rights reserved.

-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.terracotta.forge</groupId>
        <artifactId>tim-system-tests-parent</artifactId>
        <version>1.3.0-SNAPSHOT</version>
    </parent>

    <groupId>org.terracotta.modules</groupId>
    <artifactId>tim-messagebus-system-tests</artifactId>
    <version>1.4.0-SNAPSHOT</version>
    <name>Terracotta MessageBus System Tests</name>

    <dependencies>
        <dependency>
            <groupId>org.terracotta.modules</groupId>
            <artifactId>tim-messagebus</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>0.9.9</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

 <repositories>
    <repository>
      <id>terracotta-repository</id>
      <url>http://www.terracotta.org/download/reflector/maven2</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>

</project>

Next, you will need to create a test class that will start your app. Place this class in

PROJECT_HOME/src/test/java/com/tctest/MyTest.java

The JUnit testing framework convention is to end test classes in "Test". Since the Terracotta system test framework extends JUnit, we use this convention as well. The classes to be included are really defined by the surefire plugin and its includes property. The default includes are */Test.java, **/*Test.java, and **/*TestCase.java.

This class should return the actual app test that is being tested in getApplicationClass() and you should enter the number of nodes that will be run:

public class MyTest extends TransparentTestBase {
  public static final int NODE_COUNT = 3;

  public void doSetUp(final TransparentTestIface tt) throws Exception {
    tt.getTransparentAppConfig().setClientCount(NODE_COUNT);
    tt.initializeTestRunner();
  }

  protected Class getApplicationClass() {
    return App.class;
  }
}

Finally, write the actual app:

// this is the actual app under test run in each "node"
  public static class App extends AbstractErrorCatchingTransparentApp {
    // specify fields, some of which may be roots here
    
    public App(String appId, ApplicationConfig cfg, ListenerProvider listenerProvider) {
      super(appId, cfg, listenerProvider);

      // configure roots and do other app constructor logic
    }

    public static void visitL1DSOConfig(final ConfigVisitor visitor, final DSOClientConfigHelper config) {
      // programatically configure (tc-config.xml) the app here
    }

    protected void runTest() throws Throwable {
       // do stuff
    }
  }
}

Configuration examples

Within the visitL1DSOConfig() method, you need to programmatically specify the equivalent of a tc-config.xml. Below are some helpful examples:

Including a Terracotta Integration Module

String tim = "tim-mytim";
    config.addModule(tim, TimInfo.timVersion(tim));

The TimInfo class utility can be found in the tim-util forge project.

Specifying a root field in your TestApp class

This specifies that TestApp.barrier is a root field. You can call spec.addRoot() more times to add more roots in this class. Each class that has roots will require you to call config.getOrCreateSpec again.

public static void visitL1DSOConfig(ConfigVisitor visitor, DSOClientConfigHelper config) {
  String testClass = MyTestApp.class.getName();
  TransparencyClassSpec spec = config.getOrCreateSpec(testClass);
  spec.addRoot("barrier", "barrier");
}

Specifying a write-level autolock on a method in your TestApp class

String testClass = MyTestApp.class.getName();
    spec = config.getOrCreateSpec(testClass);
    config.addWriteAutolock("* " + testClass+".foo(..)");

The last method is creating an Aspectwerkz expression that returns any type, then specifies a test class, then specifies the foo method taking any number and type of arguments.

Specifying a read-level autolock

String fooClass = Foo.class.getName();
    spec = config.getOrCreateSpec(fooClass);
    config.addReadAutolock("* " + fooClass+".get()");

The last method is creating an Aspectwerkz expression that returns any type, then specifies the foo class, then specifies the foo method taking no arguments.

Specifying a class to add to the bootjar

TransparencyClassSpec spec = config.getOrCreateSpec("a.b.TheClass");
    spec.markPreInstrumented();
    config.addUserDefinedBootSpec(spec.getClassName(), spec);

Running Terracotta integration tests

Assuming you've used the tim-system-tests-parent, you can just do:

mvn -Psystem-tests integration-test

to run all tests or

mvn -Psystem-tests integration-test -Dtest=FooTest

to run a single test (note that the package for FooTest does not need to be specified).

Remote debugging with Maven

Debugging unit tests:

mvn -Dmaven.surefire.debug

Debugging with tc-maven-plugin run goal:

mvn -Djvmdebug=true tc:run

Labels

 
(None)