23 June 2013

Testing cron expression

Many people, using spring scheduling, write
@Scheduled("* * 3 * * ?")
public void myCronJob() {...
and then they wait a few days to check logs if the job is triggered correctly. And what about:
0 0/5 14,18,3-39,52 ? JAN,MAR,SEP MON-FRI 2002-2010
Fortunately, testing a cron expression is simple. But first we need a constant:
public static final String EVERYDAY_3_AM = "* * 3 * * ?"
And now, with spring's scheduling, we can use org.springframework.scheduling.support.CronSequenceGenerator
import static org.fest.assertions.api.Assertions.assertThat;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.springframework.scheduling.support.CronSequenceGenerator;

import com.googlecode.zohhak.api.Coercion;
import com.googlecode.zohhak.api.TestWith;
import com.googlecode.zohhak.api.runners.ZohhakRunner;

@RunWith(ZohhakRunner.class)
public class CronTest {

  static CronSequenceGenerator everyday_3am;

  @TestWith({
    "2013-06-10 22:20,    2013-06-11 03:00",
    "2013-06-13 01:12,    2013-06-13 03:00"
  })
  public void should_trigger_at_the_nearest_3_AM(Date now, Date nearest_3am) {

    // when
    Date nextExecution = everyday_3am.next(now);
  
    //then
    assertThat(nextExecution).isEqualTo(nearest_3am);
  }

  @BeforeClass
  static public void parseExpression() {
    everyday_3am = new CronSequenceGenerator(Constants.EVERYDAY_3_AM);
  }

  @Coercion
  public Date coerce(String date) throws ParseException {
    return new SimpleDateFormat("yyyy-MM-dd hh:mm").parse(date);
  }
}
We use static variable just to avoid multiple parsing of the same expression, as for complex scenarios there might be many parameters.
The same can be achieved with quartz library. To do this just replace CronSequenceGenerator with org.quartz.CronExpression
Date nextExecution = everyday_3am.getNextValidTimeAfter(now);
everyday_3am = new CronExpression(Constants.EVERYDAY_3_AM);

9 June 2013

CodeGenerationException and proxies

Recently I saw one of tests failed with the message:
Exception of type java.lang.IllegalStateException expected but was not thrown. 
Instead an exception of type class org.mockito.cglib.core.CodeGenerationException 
with message 'java.lang.reflect.InvocationTargetException-->null' was thrown.
But let's start from the beginning. We have a spring web application that contains only 2 classes. First one is a standard session scoped component:
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MySessionComponent {
 
    public void doError() {
        throw new IllegalStateException(); 
    }

    public void doNothing() {}
}
Nothing new, right? Second component is a standard singleton:
@Component
public class MySingleton {
 
    @Autowired MySessionComponent mySessionComponent;

    public void sampleAction() {
        mySessionComponent.doNothing();
    }
}
That's the whole application. Now let's test it.
import org.junit.Test;
import static com.googlecode.catchexception.apis.CatchExceptionBdd.*;

public class MySessionComponentTest {

    @Test
    public void test() {
        when(new MySessionComponent()).doError();
        thenThrown(IllegalStateException.class);
    }
}
I use catch exception v1.0.4 and test passes. Now, let's do an integration test:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class MySingletonTest {
 
    @Configuration @ComponentScan static class TestAppContext {} // just registers 2 components
 
    @Autowired MySingleton myController;
 
    @Test
    public void test() {
        myController.sampleAction();
    }
}
All tests pass. And now, let's suppose that we need, for whatever reason, to combine those tests:
@Test
public void test() {
    myController.sampleAction();

    when(new MySessionComponent()).doError();
    thenThrown(IllegalStateException.class);
}
And we get the exception. WTF? After some time spent with debugger I found InvocationTargetException thrown inside org.mockito.cglib.core.AbstractClassGenerator. The problem is there is no cause nor detailed message and therefore it's not propagated anywhere so you can't find the real reason in any logs. However this exception has target field and there we can find:
java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): 
attempted  duplicate class definition for name: "MySessionComponent$$FastClassByCGLIB$$441a78f3"
At first spring creates proxy for MySessionComponent in order to autowire beans with different scopes. Then catch-exception tries to create proxy for the same class. It seems that both frameworks generates the same name for the class and two classes with same name are not allowed within one classloader.

When we change the order of method invocations (the order of creating proxies)
@Test
public void deleteEbook() {
    when(new MySessionComponent()).doError();
    thenThrown(IllegalStateException.class);

    myController.sampleAction();
}
spring throws an exception but now you can see the real cause in the stacktrace:
org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
 at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:237)
...
Caused by: java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: "MySessionComponent$$FastClassByCGLIB$$441a78f3"
Btw, this behavior is strange because cglib claims it can detect name clashes. Maybe it's about repackaging cglib in almost every framework?