Wednesday, December 15, 2010

How to run JUnit tests with a timeout

For my main project, OpenSHA, we have operational JUnit tests that test system status on an hourly basis (through cruise control). For these tests, we want to make sure that all core services are operational and responsive. To do this, I needed a way to run JUnit tests with a timeout.

For example, for a map making request, it should fail if it takes over 120 seconds (which is more than generous).

I didn't find a built in way to do this, so I created my own with threads and java reflection. To use it, you call a method like this:


@Test
public void doTest() throws Throwable {
TestUtils.runTestWithTimer("sleepTestMethod", this, 2);
}


where "sleepTestMethod" is the method that gets run as a JUnit test. Note that "sleepTestMethod" shouldn't have the @Test annotation, because then it will be run twice.

Here is an example failure:

junit.framework.AssertionFailedError: method 'runTest' exceeded timeout of 120 secs!
at util.TestUtils.runTestWithTimer(TestUtils.java:70)
at org.opensha.commons.mapping.gmt.TestGMT_Operational.testMakeMapUsingServletGMT_MapStringString(TestGMT_Operational.java:37)


Here is the code for TestUtils:


package util;

import static org.junit.Assert.fail;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class TestUtils {

private static class TestMethodThread implements Runnable {

private Method testMethod;
private Object testObj;

private Throwable exception;

public TestMethodThread(Method testMethod, Object testObj) {
this.testMethod = testMethod;
this.testObj = testObj;
}

@Override
public void run() {
try {
testMethod.invoke(testObj);
} catch (Throwable t) {
this.exception = t;
}
}

public Throwable getException() {
return exception;
}

}

/**
* This runs a JUnit 4 test method in a separate thread with a timer. If the timeout is
* exceeded, then the test fails.
*
* @param methodName the name of the test method
* @param testObj the object for which method methodName is to be called
* @param timeoutSeconds timeout in seconds until the test should fail
* @throws Throwable
*/
public static void runTestWithTimer(String methodName, Object testObj, int timeoutSeconds) throws Throwable {
// get the method
Method testMethod = testObj.getClass().getDeclaredMethod(methodName);

// make sure it's accessible (not private)
if (!testMethod.isAccessible())
testMethod.setAccessible(true);

// create the thread which will simply run this test
TestMethodThread testThread = new TestMethodThread(testMethod, testObj);
// start the thread
Thread t = new Thread(testThread);
t.start();
// record the start time in milis
long start = System.currentTimeMillis();

while (t.isAlive()) {
// seconds that the thread has been running
double timeSecs = (double)(System.currentTimeMillis() - start) / 1000d;
if (timeSecs > timeoutSeconds) {
// if we're here, then it's exceeded it's allotted time.
try {
// try calling interrupt to end any blocking operation
t.interrupt();
} catch (Throwable e) {
e.printStackTrace();
}
// now fail
fail("method '"+methodName+"' exceeded timeout of "+timeoutSeconds+" secs!");
}

// if we're here then it's still running, but within the time limit. Sleep for 500 milis
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// see if it ended with an exception, this exception might be an assertion failed exception
Throwable exception = testThread.getException();
if (exception != null) {
Throwable cause = exception.getCause();
// if it's an assertion error, throw that so the failure shows up nicely in JUnit as opposed
// to an error.
if (cause != null &&
(cause instanceof AssertionError || exception instanceof InvocationTargetException))
throw cause;
// otherwise it actually is an error (not a failure), throw the exception
throw exception;
}
}

}



I hope this is useful to someone!

Tuesday, October 26, 2010

Workaround for BASH twitter script with OAuth and SuperTweet.net

When twitter disabled basic authentication for OAuth, my BASH post-commit script for posting SVN commits to twitter was broken. I didn't feel like writing an OAuth bash client, and didn't want to install any 3rd party libraries, so I found this solution: http://www.supertweet.net/

Using supertweet, you create a password for supertweet and connect it to your twitter account. You can then perform command line status updates using curl without using OAuth. The best thing is that you never actually give supertweet your twitter password, and can disable it at any time. Here's the curl command (via http://mrblog.org/2010/05/20/an-answer-for-twitter-oauth-pacalypse/):

curl -u user:password -d "status=playing with cURL and the SuperTweet.net API" http://api.supertweet.net/1/statuses/update.xml

So with that in mind, I updated the SVN post commit script. I hope it helps!

Here is the new version:

#!/bin/bash

REPOS="$1"
REV="$2"

### USER SPECIFIC PROPERTIES...CHAGE THESE! ###
# this is the path to your SVN binaries, you'll probably have to change this
SVNDIR='/usr/local/subversion/default/bin/'
# this is the trac URL. be sure to excape special characters like ':' with '%3A' and '/' with '%2F'
TRAC_URL="https%3A%2F%2Fopensha.usc.edu%2Ftrac%2Fchangeset%2F${REV}"
# this is your bit.ly login name
BITLY_LOGIN="FILLMEIN!"
# this is your bit.ly API key
BITLY_API="FILLMEIN!"
# twitter usename
TWITTER_USERNAME="FILLMEIN!"
# password for supertweet.net
SUPERTWEET_PASSWORD="FILLMEIN!"

# ---------------------------------
# This gathers some info on the commit, and "tweets" it to the twitter account
# "opensha_svn" at: http://twitter.com/opensha_svn .
# This is half for fun, and half to make it easier for Kevin to track commits.
# ---------------------------------
COMMENT=`$SVNDIR/svnlook log -r${REV} ${REPOS}`
AUTHOR=`$SVNDIR/svnlook author -r${REV} ${REPOS}`
UP=`$SVNDIR/svnlook changed -r${REV} ${REPOS} | awk '{ print $1 }' | grep -c U`
ADD=`$SVNDIR/svnlook changed -r${REV} ${REPOS} | awk '{ print $1 }' | grep -c A`
DEL=`$SVNDIR/svnlook changed -r${REV} ${REPOS} | awk '{ print $1 }' | grep -c D`

numTrunk=`$SVNDIR/svnlook dirs-changed -r${REV} ${REPOS} | grep -c trunk`

if [[ $numTrunk -gt 0 ]];then
codebase="trunk"
else
codebase=`$SVNDIR/svnlook dirs-changed -r${REV} ${REPOS} | cut -f2 --delimiter="/" | head -n 1`
fi

tweet="$REV $AUTHOR $codebase (${UP}U, ${ADD}A, ${DEL}D): $COMMENT"

if [[ $TRAC_URL ]];then
bitly=`curl "http://api.bit.ly/v3/shorten?login=${BITLY_LOGIN}&apiKey=${BITLY_API}&uri=${TRAC_URL}&format=txt"`

bitlychars=`echo -n $bitly | wc --chars`
if [[ $bitlychars -gt 25 ]];then
bitlychars=0
bitly=""
fi
fi

if [[ `echo -n "$tweet $bitly" | wc -c` -gt 140 ]];then
let chars=136-$bitlychars
tweet="`echo -n $tweet | head -c $chars`... $bitly"
else
tweet="$tweet $bitly"
fi

echo "tweet of len `echo -n "$tweet" | wc -c`: $tweet"

curl --user ${TWITTER_USERNAME}:${SUPERTWEET_PASSWORD} --data status="${tweet}" http://api.supertweet.net/1/statuses/update.xml

Thursday, September 16, 2010

New OpenSHA web site/build process with Java Web Start

My primary project at the Southern California Earthquake Center is called OpenSHA, an open source framework for seismic hazard calculations. OpenSHA is a Java-based platform. Originally we used simple executable Jar files for our standalone applications, but we have recently migrated to using Java Web Start.

The benefits of Java Web Start include automatic download of updates, native desktop integration (Start Menu/Desktop shortcuts/Mac OS X dock).

We have two parallel releases, a stable release and nightly builds. The nightlies automatically built with Cruise Control. As many of our applications use both Apache Tomcat and Java RMI, we run parallel RMI registries and Tomcat contexts, one which is linked to the trunk of our repository, and one which uses the release branch for the current stable release. This allows for easy development of server based applications, without disrupting the stable releases.

The website is build with Drupal, a content management system.

Check out the new OpenSHA website here: http://www.opensha.org and try out an application!

Saturday, August 7, 2010

BASH post-commit script for posting SVN commits to twitter (with Trac /bit.ly link)

EDIT: Changes to the twitter API broke this script. See workaround and new script here: Workaround for BASH twitter script with OAuth and SuperTweet.net

Recently I got the idea of using twitter to follow commits to various SVN repositories. I thought this might be of interest to someone out there. It's free to use (under the Apache 2.0 license).

We have Trac pages set up for our projects, so I use bit.ly to shorten URLs to the changeset. If this isn't applicable to your project, you can remove it from the script. To get it working, simply place this script in the "hooks" directory of your SVN repository, and name it "post-commit". It must also be executable. I would recommend restricting read access to your user, and the apache user (or whatever SVN runs under).

Here's the format of the tweets: [revision number] [username] [branch] ([num files updated, added, delated]): [comment] [trac changeset url]

Tweets longer than 140 characters will be automatically shortened (without ruining the trac URL)

Here's an example:

opensha_svn 6920 kmilner releases (6U, 0A, 0D): merged from trunk: updated build version to 1.0.0 http://bit.ly/cVkq3W

Here's the code (don't forget to fill in the blanks with your information!):

#!/bin/bash

REPOS="$1"
REV="$2"

### USER SPECIFIC PROPERTIES...CHAGE THESE! ###
# this is the path to your SVN binaries, you'll probably have to change this
SVNDIR='/usr/local/subversion/default/bin/'
# this is the trac URL. be sure to excape special characters like ':' with '%3A' and '/' with '%2F'
# I left an example to help with formatting, you should fill it in!
TRAC_URL="https%3A%2F%2Fintensity.usc.edu%2Ftrac%2Fopensha%2Fchangeset%2F${REV}"
# this is your bit.ly login name
BITLY_LOGIN="FILLMEIN!"
# this is your bit.ly API key
BITLY_API="FILLMEIN!"
# twitter usename
TWITTER_USERNAME="FILLMEIN!"
# twitter password
TWITTER_PASSWORD="FILLMEIN!"

# ---------------------------------
# This gathers some info on the commit, and "tweets" it to the twitter account
# "opensha_svn" at: http://twitter.com/opensha_svn .
# This is half for fun, and half to make it easier for Kevin to track commits.
# ---------------------------------
COMMENT=`$SVNDIR/svnlook log -r${REV} ${REPOS}`
AUTHOR=`$SVNDIR/svnlook author -r${REV} ${REPOS}`
UP=`$SVNDIR/svnlook changed -r${REV} ${REPOS} | awk '{ print $1 }' | grep -c U`
ADD=`$SVNDIR/svnlook changed -r${REV} ${REPOS} | awk '{ print $1 }' | grep -c A`
DEL=`$SVNDIR/svnlook changed -r${REV} ${REPOS} | awk '{ print $1 }' | grep -c D`

numTrunk=`$SVNDIR/svnlook dirs-changed -r${REV} ${REPOS} | grep -c trunk`

if [[ $numTrunk -gt 0 ]];then
codebase="trunk"
else
codebase=`$SVNDIR/svnlook dirs-changed -r${REV} ${REPOS} | cut -f2 --delimiter="/" | head -n 1`
fi

tweet="$REV $AUTHOR $codebase (${UP}U, ${ADD}A, ${DEL}D): $COMMENT"

if [[ $TRAC_URL ]];then
bitly=`curl "http://api.bit.ly/v3/shorten?login=${BITLY_LOGIN}&apiKey=${BITLY_API}&uri=${TRAC_URL}&format=txt"`

bitlychars=`echo -n $bitly | wc --chars`
if [[ $bitlychars -gt 25 ]];then
bitlychars=0
bitly=""
fi
fi

if [[ `echo -n "$tweet $bitly" | wc -c` -gt 140 ]];then
let chars=136-$bitlychars
tweet="`echo -n $tweet | head -c $chars`... $bitly"
else
tweet="$tweet $bitly"
fi

echo "tweet of len `echo -n "$tweet" | wc -c`: $tweet"

curl --basic --user ${TWITTER_USERNAME}:${TWITTER_PASSWORD} --data status="${tweet}" https://twitter.com/statuses/update.xml

New Blog

I have a new blog! I had an old website for a while, but that was mostly just for learning php/linux server administration, but lately I've been wanting a place to post cool stuff that I come across. Most of it will probably be little tech tricks, rants on USC football, travel reports, etc. Anyway, I hope you enjoy it!