/*
* Copyright 2009-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.step;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.util.Assert;
/**
* Tests for {@link AbstractStep}.
*/
public class NonAbstractStepTests {
AbstractStep tested = new EventTrackingStep();
StepExecutionListener listener1 = new EventTrackingListener("listener1");
StepExecutionListener listener2 = new EventTrackingListener("listener2");
JobRepositoryStub repository = new JobRepositoryStub();
/**
* Sequence of events encountered during step execution.
*/
final List<String> events = new ArrayList<String>();
final StepExecution execution = new StepExecution(tested.getName(), new JobExecution(new JobInstance(1L,
"jobName"), new JobParameters()));
/**
* Fills the events list when abstract methods are called.
*/
private class EventTrackingStep extends AbstractStep {
public EventTrackingStep() {
setBeanName("eventTrackingStep");
}
@Override
protected void open(ExecutionContext ctx) throws Exception {
events.add("open");
}
@Override
protected void doExecute(StepExecution context) throws Exception {
assertSame(execution, context);
events.add("doExecute");
context.setExitStatus(ExitStatus.COMPLETED);
}
@Override
protected void close(ExecutionContext ctx) throws Exception {
events.add("close");
}
}
/**
* Fills the events list when listener methods are called, prefixed with the name of the listener.
*/
private class EventTrackingListener implements StepExecutionListener {
private String name;
public EventTrackingListener(String name) {
this.name = name;
}
private String getEvent(String event) {
return name + "#" + event;
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
assertSame(execution, stepExecution);
events.add(getEvent("afterStep(" + stepExecution.getExitStatus().getExitCode() + ")"));
stepExecution.getExecutionContext().putString("afterStep", "afterStep");
return stepExecution.getExitStatus();
}
@Override
public void beforeStep(StepExecution stepExecution) {
assertSame(execution, stepExecution);
events.add(getEvent("beforeStep"));
stepExecution.getExecutionContext().putString("beforeStep", "beforeStep");
}
}
/**
* Remembers the last saved values of execution context.
*/
private static class JobRepositoryStub extends JobRepositorySupport {
ExecutionContext saved = new ExecutionContext();
static long counter = 0;
@Override
public void updateExecutionContext(StepExecution stepExecution) {
Assert.state(stepExecution.getId() != null, "StepExecution must already be saved");
saved = stepExecution.getExecutionContext();
}
@Override
public void add(StepExecution stepExecution) {
if (stepExecution.getId() == null) {
stepExecution.setId(counter);
counter++;
}
}
}
@Before
public void setUp() throws Exception {
tested.setJobRepository(repository);
repository.add(execution);
}
@Test
public void testBeanName() throws Exception {
AbstractStep step = new AbstractStep() {
@Override
protected void doExecute(StepExecution stepExecution) throws Exception {
}
};
assertNull(step.getName());
step.setBeanName("foo");
assertEquals("foo", step.getName());
}
@Test
public void testName() throws Exception {
AbstractStep step = new AbstractStep() {
@Override
protected void doExecute(StepExecution stepExecution) throws Exception {
}
};
assertNull(step.getName());
step.setName("foo");
assertEquals("foo", step.getName());
step.setBeanName("bar");
assertEquals("foo", step.getName());
}
/**
* Typical step execution scenario.
*/
@Test
public void testExecute() throws Exception {
tested.setStepExecutionListeners(new StepExecutionListener[] { listener1, listener2 });
tested.execute(execution);
int i = 0;
assertEquals("listener1#beforeStep", events.get(i++));
assertEquals("listener2#beforeStep", events.get(i++));
assertEquals("open", events.get(i++));
assertEquals("doExecute", events.get(i++));
assertEquals("listener2#afterStep(COMPLETED)", events.get(i++));
assertEquals("listener1#afterStep(COMPLETED)", events.get(i++));
assertEquals("close", events.get(i++));
assertEquals(7, events.size());
assertEquals(ExitStatus.COMPLETED, execution.getExitStatus());
assertTrue("Execution context modifications made by listener should be persisted",
repository.saved.containsKey("beforeStep"));
assertTrue("Execution context modifications made by listener should be persisted",
repository.saved.containsKey("afterStep"));
}
@Test
public void testFailure() throws Exception {
tested = new EventTrackingStep() {
@Override
protected void doExecute(StepExecution context) throws Exception {
super.doExecute(context);
throw new RuntimeException("crash!");
}
};
tested.setJobRepository(repository);
tested.setStepExecutionListeners(new StepExecutionListener[] { listener1, listener2 });
tested.execute(execution);
assertEquals(BatchStatus.FAILED, execution.getStatus());
Throwable expected = execution.getFailureExceptions().get(0);
assertEquals("crash!", expected.getMessage());
int i = 0;
assertEquals("listener1#beforeStep", events.get(i++));
assertEquals("listener2#beforeStep", events.get(i++));
assertEquals("open", events.get(i++));
assertEquals("doExecute", events.get(i++));
assertEquals("listener2#afterStep(FAILED)", events.get(i++));
assertEquals("listener1#afterStep(FAILED)", events.get(i++));
assertEquals("close", events.get(i++));
assertEquals(7, events.size());
assertEquals(ExitStatus.FAILED.getExitCode(), execution.getExitStatus().getExitCode());
String exitDescription = execution.getExitStatus().getExitDescription();
assertTrue("Wrong message: " + exitDescription, exitDescription.contains("crash"));
assertTrue("Execution context modifications made by listener should be persisted",
repository.saved.containsKey("afterStep"));
}
/**
* Exception during business processing.
*/
@Test
public void testStoppedStep() throws Exception {
tested = new EventTrackingStep() {
@Override
protected void doExecute(StepExecution context) throws Exception {
context.setTerminateOnly();
super.doExecute(context);
}
};
tested.setJobRepository(repository);
tested.setStepExecutionListeners(new StepExecutionListener[] { listener1, listener2 });
tested.execute(execution);
assertEquals(BatchStatus.STOPPED, execution.getStatus());
Throwable expected = execution.getFailureExceptions().get(0);
assertEquals("JobExecution interrupted.", expected.getMessage());
int i = 0;
assertEquals("listener1#beforeStep", events.get(i++));
assertEquals("listener2#beforeStep", events.get(i++));
assertEquals("open", events.get(i++));
assertEquals("doExecute", events.get(i++));
assertEquals("listener2#afterStep(STOPPED)", events.get(i++));
assertEquals("listener1#afterStep(STOPPED)", events.get(i++));
assertEquals("close", events.get(i++));
assertEquals(7, events.size());
assertEquals("STOPPED", execution.getExitStatus().getExitCode());
assertTrue("Execution context modifications made by listener should be persisted",
repository.saved.containsKey("afterStep"));
}
@Test
public void testStoppedStepWithCustomStatus() throws Exception {
tested = new EventTrackingStep() {
@Override
protected void doExecute(StepExecution context) throws Exception {
super.doExecute(context);
context.setTerminateOnly();
context.setExitStatus(new ExitStatus("FUNNY"));
}
};
tested.setJobRepository(repository);
tested.setStepExecutionListeners(new StepExecutionListener[] { listener1, listener2 });
tested.execute(execution);
assertEquals(BatchStatus.STOPPED, execution.getStatus());
Throwable expected = execution.getFailureExceptions().get(0);
assertEquals("JobExecution interrupted.", expected.getMessage());
assertEquals("FUNNY", execution.getExitStatus().getExitCode());
assertTrue("Execution context modifications made by listener should be persisted",
repository.saved.containsKey("afterStep"));
}
/**
* Exception during business processing.
*/
@Test
public void testFailureInSavingExecutionContext() throws Exception {
tested = new EventTrackingStep() {
@Override
protected void doExecute(StepExecution context) throws Exception {
super.doExecute(context);
}
};
repository = new JobRepositoryStub() {
@Override
public void updateExecutionContext(StepExecution stepExecution) {
throw new RuntimeException("Bad context!");
}
};
tested.setJobRepository(repository);
tested.execute(execution);
assertEquals(BatchStatus.UNKNOWN, execution.getStatus());
Throwable expected = execution.getFailureExceptions().get(0);
assertEquals("Bad context!", expected.getMessage());
int i = 0;
assertEquals("open", events.get(i++));
assertEquals("doExecute", events.get(i++));
assertEquals("close", events.get(i++));
assertEquals(3, events.size());
assertEquals(ExitStatus.UNKNOWN, execution.getExitStatus());
}
/**
* JobRepository is a required property.
*/
@Test(expected = IllegalStateException.class)
public void testAfterPropertiesSet() throws Exception {
tested.setJobRepository(null);
tested.afterPropertiesSet();
}
}