Thursday, January 14, 2010

Run your junit tests concurrently with maven, junit (and maybe also spring3) in 5 minutes or less!

Surefire 2.5 is released, and it contains the concurrent junit patches. In this post I'll give the quick rundown of how to try out your current maven based build in a concurrent fashion. Just a few initial thoughts:

Will my tests run concurrently ?


Probably not, as is. Most existing test fixtures that people use have singletons and shared state that needs to be fixed first. This may be everything from a shared file, shared TCP/IP port or a static member variable in some base class that can't be static any more.

It took me about a day to fix these things in my current project. My project is large and complex. using all sorts of dark spring magics. Your mileage may vary.

What performance gain can I expect ?


For an IO-bound test (integration test/selenium test etc), the sky's the limit ;)

For a fairly optimized unit-test set, expect little or no gain - maybe 15-20%.
The reason for this is that until jdk7, all classloading and all classpath bound resource access is synchronized across the VM. Running unit tests is mostly about classloading and classpath resources :( If your unit-tests have other kinds of I/O bindings you may be luckier.

If you're using spring inside your test fixtures (like we do), you can probably squeeze a few drops of speed out of it by making lazy-init==true globally. You're going to test it all anyway, right ?

How to do it!


Make sure you upgrade to surefire-2.5:


<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.5</version>
</plugin>


Make sure you upgrade to junit 4.8.1 (still not in maven central repo, install locally):

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>


(You /can/ use 4.7 but it still has a couple of concurrency bugs in it that were fixed in 4.8.1)

Fix your surefire configuration:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
....
<parallel>classes</parallel>
</configuration>
</plugin>


Legal values for parallel are "classes, methods or both". Now you can run, but you should probably read the rest of this post first...

Classes, methods or both ?


From a concurrency perspective, "classes" is probably the easiest to start with, since it will probably run into the smallest number of troubles in your test fixtures. I really recommend you try to get both working, since you'll probably need to identify any additional issues raised by using "methods". You want a test-suite you can trust, right ?

The fine print


One drawback of the current default concurrency implementation in junit is that it does not allow you to constrain threads, which is desirable for almost all use cases I am aware of. But junit is extendable, and I have made a supplemental add-on that allows threads to be configured too. Surefire knows to invoke this one if it's present on your classpath, meaning you'll get additional options available:

The configurable-parallel-computer still has a few issues that are unsolved, so you
may consider dropping by the issue tracker to check if these will bother you before adding it to your project

git clone http://github.com/krosenvold/configurable-parallel-computer.git
cd configurable-parallel-computer
mvn install


<dependency>
<groupId>org.jdogma.junit</groupId>
<artifactId>configurable-parallel-computer</artifactId>
<version>1.5</version>
<scope>test</scope>
</dependency>




<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
....
<parallel>classes</parallel>
<threadCount>2</threadCount>
</configuration>
</plugin>


You can also use the setting "perCoreThreadCount", which scales threadcount per CPU core.

The configurable-parallel-computer project also contains a better output demultiplexer than the one in surefire (which has a ketchup-bottle behaviour). Surefire knows about this one too, so adding configurable-parallel-computer to your classpath will give you smooth project output. I'm sure we'll get the improved version into surefire at some later time.


Spring


If you're using spring you need to use a forked version of spring-test. The patch I submitted to spring is targeted for 3.1, whatever that may mean in practice:

git clone http://github.com/krosenvold/org.springframework.test.git
cd org.springframework.test
mvn install

You need to replace your dependency on "spring-test" to the forked version:


<dependency>
<groupId>org.rosenvold.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.0.0.RELEASE</version>
<scope>test</scope>
</dependency>

Remember to also suppress the transitively dependent spring-test artifacts, like this:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>3.0.0.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</exclusion>
</exclusions>
</dependency>


Make sure mvn dependency:tree does not contain the spring version of spring-test. Any inclusion of the 3.0 artifact WILL get you into trouble.

If you're using a Mock Session Scope based on the Spring Session Scope (with a custom ContextLoader), you need to make sure you override the synchronized methods in the base class with your own implementations, or your tests may deadlock on the session scope. This is a real bug in spring, but one that probably does not occur to often in real-life scenarios; but quite a lot when running concurrent tests.

Off you go !

Monday, October 5, 2009

Concurrent junit with maven and spring3 updated for RC1

To celebrate the release of spring rc1, I have updated the patches and the procedure for building custom versions of junit, surefire and spring3. Spring had about 500 commits since I created the initial patchset, and they were no longer applying cleanly.

The instructions in my original blogpost now reflect the revised procedure, and in about 20 minutes you can have it all up & running.


The original instructions can be found here.

Memo to self: How to patch an open source CVS/SVN project with GIT

Most CVS/SVN open source projects will prefer patches to be submitted as follows:

1. First patch contains a failing test
2. Second patch contains bugfix/improvement and possibly additional testcases and maybe also changes to the initial failing test case

The smart way to do this with git is as follows:

1. Clone project, if it's a SVN project use github's git-svn import (*much* perferred to using git svn clone yourself)
2. Create an initial feature branch masterForGit, where you add stuff that is nice for working this project in a git environment; typically a .gitignore file and any local scripts you need to be able to work with the project. Make sure this project compiles well and does not add unwanted files to git before proceeding (git does not handle empty directories same way as svn so you may need to correct this in this branch)
3. Create a feature branch failingTest based on masterForGit. In this branch you create the original failing
test what will be submitted as proof.
4. Create a feature branch bugFix that is branched off failingTest. In this branch you fix the bug and add any subsequent test cases.
5. When you're all done, you typically do diff between failingTest and masterForGit to get the patch file for "proof" bug. Then you diff bugFix and failingTest to get fix-patch. Since you created the "masterForGit" branch initially, none of the patches get polluted with temp-stuff you did locally.

And, if you end up maintaining this patchset, you can just update from svn through github and merge through all of your branches. Easy.

Thursday, October 1, 2009

Status of concurrency patches

AspectJ 1.6.6 is released and contains the patch I submitted there. 1 patch down.

The main patch for spring is scheduled for release 3.1. The patch no longer applies cleanly, but I will be updating it fairly soon, probably some time over the weekend. I may also try to publish a forked org.springframework.test to some maven repo by the time 3.0 goes release.

Surefire is another story. I am not sure anyone is actively maintaining this plugin any more. Anyone know any committers ? I have been considering forking the surefire plugin permanently and remove cruft, it sure has a lot of it.

As for the patches to junit itself they are submitted but still not accepted. There may be possible workarounds for this that I'll can consider when I revise the patches.

Sunday, July 5, 2009

Are four cores required to surpass one ?

My recent post about running unit tests in parallel has led to some interesting food for thought:

When running regular unit tests, running with 2 cores seems to be no faster than one core. 4 cores (and arguably a newer CPU architecture) had a significant impact, where performance nearly doubled. The hyperthreaded quad-core i7 920 was *slower* than a 3Ghz core 2 duo when running single threaded, but nearly twice as fast in a multithreaded scenario. What's happening ?

I looked into the issue with jprofiler, but there is no obvious lock contention or other clearly visible bottleneck. The only explanation I can think of so far is this:

The test I was running was extremely simplistic, and there was no real cpu requirement in running the test methods. So the overhead of creating thread pools and all that actually amounts to just as much computation as running the tests. Likely ?

After my summer vacation, I will try to find out. I probably need to run with more realistic test cases. I figured I'd build surefire with threads, because thats a fairly heavy build. In the mean time, your thoughts are welcome.

From legoland,

Kristian

Wednesday, July 1, 2009

Run your junit tests in parallel (with spring 3.0 and maven)

Swimming in a sea of patches...



Note; Most of the content in this post has been surpassed by a newer entry.

If you're willing to apply a few patches, you can now run your junit tests in parallel. There are two main use-cases for this; one is to make your regular unit tests run faster, the other is for io-bound tests, where you want to run several in parallel because you're waiting for external systems.

Just to whet your appetite, here are some numbers from a testcase (running 1000 instances of a simple unit-test-class):

Legend:
SC = SingleThreadedComputer (Computer, Junit47-snapshot class)
PC = ParallelComputer (ParallelComputer, Junit4.7-snapshot class)
CPC = ConfigurableParallelComputer (My project, see references)
C=Classes (one thread per class)
M=Methods (one thread per method)






































Core 2 duo 3.14GhzIntel i7 920
SC203451
PC(Classes, Method, Unlimited threads)406806
CPC(Methods,16 threads)218214
CPC(Classes, Unlimited threads)203116
CPC(Classes,Methods, Unlimited threads)266147

Running time in ms, 1000 simple tests

The table shows that for most dual-core solutions, running with parallel threads is currently never any faster than running non-threaded. Switching to the brand-new Intel i7 (4 hyperthreaded cores) has a marked effect, where simple unit test performance is doubled.

The lack of punch does, of course, change the moment your tests start doing IO (Integration tests, selenium tests and other web-tests).



The juicy stuff
svn co https://svn.openqa.org/svn/selenium-rc

Second build with mvn clean install: 102 seconds.
With 80 threads class-parallel on the i7: 37 seconds.

Class-parallel works easily on most tests.

A sea of patches


Getting all of this to work was not without its problems, though. To get this running, I had to patch just about half the libraries in use in my current stack. Furthermore, there's a huge difference between getting it to run and getting it to run without hiccups - every time. These patches insure that your infrastructure works as it should, but are your tests concurrent ?

Patches involved


See below for how to apply these patches in a simple manner.
Spring (3.0 trunk):
http://jira.springframework.org/browse/SPR-5863
AspectJ: Patch trunk with this patch (if using spring) (This patch is included in version 1.6.6)
https://bugs.eclipse.org/bugs/show_bug.cgi?id=281654
Junit4.7:
Several patches needed for thread safety:
http://github.com/KentBeck/junit/issues#issue/16
http://github.com/KentBeck/junit/issues#issue/18
Maven: You need a patched version of surefire that supports junit 4.7:
http://jira.codehaus.org/browse/SUREFIRE-555
You'll also probably want to be using the ConfigurableParallelComputer instead of the ParallelComputer that's included with JUnit.
git://github.com/krosenvold/configurable-parallel-computer.git

With these patches in place you're smooth sailing ;)

Applying the patches


Note: If you did this on the pre rc1 version of this patchset, I recommend that you start from a fresh copy of everything this time.
If you're using maven you could try this sequence:

git clone git://github.com/krosenvold/spring-framework-concurrent-junit-patch.git
git clone git://github.com/krosenvold/junit.git
git clone git://github.com/krosenvold/configurable-parallel-computer.git
git clone git://github.com/krosenvold/spring-test.git
git clone git://github.com/krosenvold/spring-concurrency-test.git
cd junit
ant
cd ../spring-framework-concurrent-junit-patch
./installJunit
cd ../spring-test
mvn install
cd ../configurable-parallel-computer
mvn install
cd ../spring-framework-concurrent-junit-patch
./checkoutSurefire
./buildSureFire
./applySureFirePatch
./buildSureFire




cd ../spring-concurrency-test
mvn install # Test project to verify that everything is ok


Your new versions


JUnit should be modified to 4.7-CONCURRENT
spring-test should have version number 3.0.0.RC1-CONCURRENT
Surefire should be modified to 2.5-SNAPSHOT
You must update your aspectj version to 1.6.6 in your project. Spring does a transitive
include on 1.6.5

You can see the version number for the spring artifacts when building spring. Probably something like 3.0.0.BUILD-20091005102426

Maven surefire settings



<plugin>
<artifactid>maven-surefire-plugin</artifactid>
<version>2.5-SNAPSHOT</version>
<configuration>
<parallel>classes</parallel>
<useUnlimitedThreads>true</useUnlimitedThreads>
<threadCount>80</threadCount>
<perCoreThreadCount>true</perCoreThreadCount>
</configuration>
</plugin>


UnlimitedThreads knocks out threadCount and perCoreThreadCount.

Look at the various "install" scripts to see the version numbers of the artifacts.
Simple, eh ?

And while we're at it: Half of the work with patching these projects has been about fighting with version control systems and custom made build scripts. So please; switch to GIT and maven: I can build any maven project without reading as much as a readme file. Maven allows me as a casual bystander to *easily* make contributions; I never need to read that 4 page "how to build" manual! Git is the only version control tool that respects my right/duty to patch, change and upgrade other projects. It allows me to do all these things without having to *ask* anyone. Repeat after me: Git and maven encourages free collaboration; a key idea of open source, right ?

http://twitter.com/krosenvold

Thursday, October 9, 2008

Using ajax transaction counts in selenium tests

Sometimes your "only hope" in making a selenium web test stable is to keep track of the number of ajax requests that have happened on the client side.

An example of this is the ajax request that normally returns exactly the same html as was present in the dom before the request. Although this may sound stupid, there are legitimate situations for this.

My test would look like this:

int transactionNumer = selenium.getCurrentAjaxTransactionNumber();
selenium.clickLink("SomeLink");
selenium.waitForAjaxTransactionNumberChange(transactionNumer);
-- rest of test ---


This test is free from timing issues, because it will not proceed to the rest of the test before the ajax request is completed.


public int getCurentAjaxTransactionNumber() {
String eval = selenium.getEval("window.ajaxRequestNumber");
return Integer.parseInt( eval);
}





In my ajax.js file, which wraps prototype, I will typically include two lines like this:

window.ajaxRequestNumber = 0;


Now you will have to find a way to patch into your ajax framework to update this value. For prototype, this would work:


Ajax.Responders.register({
onComplete: function() {
window.ajaxRequestNumber++;
}
});


Now you can track request numbers, and yes - they start with value 0 on page load.