Tuesday, October 15, 2013

Cloudfoundry makes continuous integration easy

Cloudfoundry works as a PAAS that makes dev opts work much easier for continuous integration.

Without a good PAAS, setting up a stable and reusable build environment requires a lot of efforts. For example, the build requires a tomcat container setup, the Jenkin slave machine needs to avoid the tomcat port conflict, the web application has other service dependencies for testing, needs profiles to separate the different environments and injecting to your pom.xml and so on.

I will describe an experiment of using cloudfoundry and maven to do a clean integration test.

Prerequisite:

  • Register a cloudfoundry account https://console.run.pivotal.io/register with a trial account or you can create a light weight cloudfoundry by using https://github.com/cloudfoundry/bosh-lite (Recommended since you can get better understanding cloudfoundry principles and components)

Setup the POM (example as https://github.com/datianshi/spring-mvc-service with cloudfoundry branch):
  • Add cloudfoundry credentials to your maven settings.xml
<servers>
        <server>
          <id>mycloudfoundry-instance</id>
          <username>${username}</username>
          <password>${password}<password>
        </server>
</servers>


  • Add cloudfoundry maven plugin (cf-maven-plugin) for pre-integration-test and post-integration-test
<plugin>
	<groupId>org.cloudfoundry</groupId>
	<artifactId>cf-maven-plugin</artifactId>
	<version>1.0.0.BUILD-SNAPSHOT</version>
	<configuration>
                    <server>mycloudfoundry-instance</server>
                    <target>http://10.244.0.14:9022</target>
                    <org>myorg</org>
                    <space>myspace</space>
                    <appname>${app}</appname>
                    <url>${app}.10.244.0.254.xip.io</url>
                    <memory>256</memory>
                    <buildpack>git://github.com/datianshi/java-buildpack.git</buildpack>
	</configuration>
	<executions>
		    <execution>
			  <id>pre-integration</id>
			  <phase>pre-integration-test</phase>
			  <goals>
			  	<goal>push</goal>
			  </goals>
			  </execution>
		    <execution>
			  <id>post-integration</id>
			  <phase>post-integration-test</phase>
			  <goals>
			  	<goal>delete</goal>
			  </goals>
	            </execution>			  	
	</executions>
</plugin>

The above plugin has two executions, one is set as pre-integration-test life cycle with a push goal to set up a tomcat environment. The application can be configured with appname, url and so on. What does it actually happen during push inside of cloud foundry? In short story, the cf client side upload the war and give that to the cloudfoundry (specified in the target url), and cloudfoundry is smart enough to create a ready tomcat container based on the build packs, url(it has internal DNS server) and app name. For the interest readers, please refer cloudfoundry architecture and how-applications-are-staged




  • Add maven fail safe plugin to run your integration tests.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
        <skip>true</skip>
</configuration>
<executions>
        <execution>
                <id>integration-tests</id>
                <goals>
                        <goal>integration-test</goal>
                        <goal>verify</goal>
                </goals>
                <configuration>
                        <skip>false</skip>
                        <includes>
                                <include>**/*IntegrationTest.java</include>
                        </includes>
                        <systemPropertyVariables>
                                <integrationUrl>http://${app}.10.244.0.254.xip.io</integrationUrl>
                        </systemPropertyVariables>
                </configuration>
        </execution>
</executions>
</plugin>

Since we parameterized the app name, we could just pass in the url to our test and the test knows which url to perform test. Because of the existence of router and dns server in cloudfoundry, we would not worry port configuration/collision for tomcat, since every jenkin build we just pass in different app name (E.g. build number), then the build executes concurrently.

To run the build: mvn clean install -Dapp=test

Cloudfoundry is also flexible to integrate with other application containers (Controlled by buildpack). Another feature called services (As your application dependencies E.g. mysql, redis....) could be configured and deployed to cloudfoundry environment so your application could be easily bind to during push with minimum configuration. For the readers interested with cloudfoundry deployment and release management, please refer bosh





Tuesday, July 30, 2013

Junit multiple users functionalities with TestRule interface

Recently I was trying to do some performance testing integrated with build. There are some use cases that I want to simulate multi users(Threads) behavior inside my junit test cases to have multiple threads executing the tests concurrently.

Then TestRule, a built in Junit interface which brought my attention, is easy and powerful to customize your test code behaviors.

TestRule
As I understand, the TestRule is an interface which developer can inject into test classes, which is similar to the aspectj Around point cut.

The code below implements TestRule and return an inner class as CustomStatement that injects some code before and after the testing code (base.evaluate())

public class SimpleTestRule implements TestRule{
 
 public Statement apply(Statement base, Description description) {
  return new CustomStatement(base);
 }
 
 private class CustomStatement extends Statement{
  private Statement base;
  
  public CustomStatement(Statement base) {
   this.base = base;
  }

  @Override
  public void evaluate() throws Throwable {
   //Before test execution
   System.out.println("Before Test Running");
   //Test execution
   base.evaluate();
   //After test execution
   System.out.println("After Test Running");
   
  }
  
 }
}

Use the above Custom Test Rule
public class SimpleTestRuleTest {
 
 @Rule
 public TestRule rule = new SimpleTestRule();
 
 @Test
 public void test(){
  System.out.println("My test is running");
 }
}

Code execution Result:

Before Test Running
My test is running
After Test Running


Multiple User TestRule

After understanding the above simple test rule, we can start create a more complicate rule to support multiple threading of tests.

The following code wrapped the test execution code (base.evaluate()) into an independent thread.

public class MultiUserTestRule implements TestRule{
 
 private final int numOfUsers;
 private final ExecutorService executor;
 
 public MultiUserTestRule(int poolSize, int numOfUsers){
  this.numOfUsers = numOfUsers;
  executor = Executors.newFixedThreadPool(poolSize);
 }

 public Statement apply(Statement base, Description description) {
  return new CustomStatement(base);
 }
 
 private class CustomStatement extends Statement{
  
  private Statement base;
  
  public CustomStatement(Statement base) {
   this.base = base;
  }

  @Override
  public void evaluate() throws Throwable {
   for(int i=0; i< numOfUsers; i++){
    executor.execute(new Thread(new Runnable() {
     
     public void run() {
      try {
       base.evaluate();
      } catch (Throwable e) {
       throw new IllegalArgumentException(e);
      }
     }
    }));
   }
  }
  
 }

}
Use the above test Rule
public class MultiUserTestRuleTest {
 /**
  * The thread pool size is 10
  * There are 5 users run the tests
  */
 @Rule
 public TestRule rule = new MultiUserTestRule(10, 5);
 
 @Test
 public void test(){
  System.out.println(Thread.currentThread().getName() + " is running the test");
 }
}
The above test case is trying to print out each current thread name for demo purpose.
The result is:
pool-1-thread-1 is running the test
pool-1-thread-4 is running the test
pool-1-thread-5 is running the test
pool-1-thread-2 is running the test
pool-1-thread-3 is running the test

All the above code can be found here: https://github.com/datianshi/multiuser-junit

Tuesday, July 16, 2013

Spring MVC testing with session scoped beans

Spring MVC test is a new feature from Spring 3.2 that could help easy function/integration testing with the controller without bring up a real server. However, when controller autowiring  a session scoped bean and some controller request mapping methods have operations on the bean, how to make assertion on those operations becomes tricky. Here I will propose an approach to test against it.


Assume there is a session scoped bean configured as below:

@Component
@Scope(value= "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionScopeBean {
 
 private String value;

 public String getValue() {
  return value;
 }

 public void setValue(String value) {
  this.value = value;
 }
 
 
}
There is a controller autowired with the session scoped bean
@Controller
public class HomeController {
 
 @Autowired
 private SessionScopeBean sessionBean;
  
 @RequestMapping(value = "/sessionScope/{value}")
 public String test(@PathVariable String value){
  sessionBean.setValue(value);
  return "home";
 }
 
}
The above controller modify the value field of a session scope bean, then how would we retrieve that and assert the appropriate value in the session. Couple of tips here:


  • Spring saves the session scope bean as a session attribute "scopedTarget.${idOfTheBean}"
  • Spring mvc test provides request().sessionAttribute("attr", hamcrestMatcher) to assert the session
  • hamcrest matcher HasPropertyWithValue can be used to inject into the above expression


@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("classpath:test-appContext.xml")
public class ControllerTest {
 
 private MockMvc mockMvc;
 
 @Autowired
 private WebApplicationContext wac;
 
 
 
 @Before
 public void setup(){
  this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
 }
 
 @Test
 public void test() throws Exception{
  mockMvc.perform(get("/sessionScope/abc")).
   andExpect(view().name("home")).
   andExpect(request().sessionAttribute("scopedTarget.sessionScopeBean", hasProperty("value", is("abc"))));
 }
}
The above tests shows how the session attribute being asserted

Cheers

Sunday, March 10, 2013

Major Countries Military Action Principle

Found from a Chinese version:
1,美国,想打谁就打谁;
2,英国,美国打谁我打谁;
3,俄罗斯,谁骂我我打谁;
4,日本,谁打我我让美国打谁;
5,朝鲜,谁让我不痛快我就打韩国;
 6,韩国,谁打我我和美国一块军演吓唬谁;
7,中国,谁打我我就骂谁。

 Translate to English:
1. US, I will beat whoever I want.
2. UK, I will beat whoever US wants to beat.
3. Russia, I will beat whoever condemns me.
4. Japan, I will let US beat whoever beats me.
 5. North Korea, I will beat South Korea if anyone makes me unhappy.
6. South Korea, I will have military exercises with US whenever someone beats me.
7. China, I will condemn whoever beats me.

Thursday, March 7, 2013

How does IncompatibleClassChangeError happen (with sample code)



Introduction

When different teams working separately producing libraries consumed by each other ,you may run into a problem with the java.lang.IncompatibleClassChangeError exception.

What does this exception mean?

IncompatibleClassChangeError means the class in the classloader is not the same format as the one compiled. (class vs interface)

How does this happened?


E.G.  
  • Your application "app" with dependency jar b in class path.
  • A is your code in app which has a reference with class B and compiled with b.
  • The class B in b-beta version was refactored to an interface
  • When you run your application, the code is running and load your dependency with b-beta.
  • Since the B as an interface not same as the one compiled as a class. Bang!~ The application throws the exception.

Sample code with Maven



I made an example to demonstrate how this happened. The code sample can be found here:
https://github.com/datianshi/maven-inCompatibleClassChangeError

The five repos are: version1, version2, framework1, framework2, app

You can run maven clean install with the above sequences in your local box

Version 1 and Version2 have the same group id and artifact id, while the version is not the same (Here in order to simulate multiple version with different code locally, I created two repo for the same group and artifact). version1 has an interface Person, while version2 has a class Person.

Framework1 depends on version1 Person, while Framework2 depends on version2 Person.

The app depends on both framework1 and framework2. When maven loads the dependencies, it has to resolve the conflicts for version1 and version2, because they are having the same artifact and group. Maven uses "nearest win" policy to resolve the conflicts, then version1 wins.

When the app loads Framework2, it was looking for class Person, but the classloader contains an interface Person. The application failed like below.

mvn exec: java inside of app folder.


Caused by: java.lang.IncompatibleClassChangeError: Found interface org.freelance.incomp.core.Person, but class was expected
at org.freelance.version2.SayPerson.sayPerson(SayPerson.java:9)
at org.freelance.incomp.app.App.main(App.java:12)