/*
* Copyright 2006-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.tasklet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
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.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.job.JobSupport;
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.dao.MapExecutionContextDao;
import org.springframework.batch.core.repository.dao.MapJobExecutionDao;
import org.springframework.batch.core.repository.dao.MapJobInstanceDao;
import org.springframework.batch.core.repository.dao.MapStepExecutionDao;
import org.springframework.batch.core.repository.support.SimpleJobRepository;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.JobRepositorySupport;
import org.springframework.batch.core.step.StepInterruptionPolicy;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemStreamException;
import org.springframework.batch.item.ItemStreamSupport;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.support.AbstractItemStreamItemReader;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.batch.repeat.policy.DefaultResultCompletionPolicy;
import org.springframework.batch.repeat.policy.SimpleCompletionPolicy;
import org.springframework.batch.repeat.support.RepeatTemplate;
import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.support.DefaultTransactionStatus;
public class TaskletStepTests {
List<String> processed = new ArrayList<String>();
private List<Serializable> list = new ArrayList<Serializable>();
ItemWriter<String> itemWriter = new ItemWriter<String>() {
@Override
public void write(List<? extends String> data) throws Exception {
processed.addAll(data);
}
};
private TaskletStep step;
private Job job;
private JobInstance jobInstance;
private JobParameters jobParameters;
private ResourcelessTransactionManager transactionManager;
@SuppressWarnings("serial")
private ExecutionContext foobarEc = new ExecutionContext() {
{
put("foo", "bar");
}
};
private ItemReader<String> getReader(String[] args) {
return new ListItemReader<String>(Arrays.asList(args));
}
private TaskletStep getStep(String[] strings) throws Exception {
return getStep(strings, 1);
}
private TaskletStep getStep(String[] strings, int commitInterval) throws Exception {
TaskletStep step = new TaskletStep("stepName");
// Only process one item:
RepeatTemplate template = new RepeatTemplate();
template.setCompletionPolicy(new SimpleCompletionPolicy(commitInterval));
step.setTasklet(new TestingChunkOrientedTasklet<String>(getReader(strings), itemWriter, template));
step.setJobRepository(new JobRepositorySupport());
step.setTransactionManager(transactionManager);
return step;
}
@Before
public void setUp() throws Exception {
transactionManager = new ResourcelessTransactionManager();
RepeatTemplate template = new RepeatTemplate();
template.setCompletionPolicy(new SimpleCompletionPolicy(1));
step = getStep(new String[] { "foo", "bar", "spam" });
step.setStepOperations(template);
job = new JobSupport("FOO");
jobInstance = new JobInstance(0L, job.getName());
jobParameters = new JobParameters();
step.setTransactionManager(transactionManager);
}
@Test
public void testStepExecutor() throws Exception {
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
step.execute(stepExecution);
assertEquals(1, processed.size());
assertEquals(1, stepExecution.getReadCount());
assertEquals(1, stepExecution.getCommitCount());
}
@Test
public void testCommitCount_Even() throws Exception {
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
step = getStep(new String[] { "foo", "bar", "spam", "eggs" }, 2);
step.setTransactionManager(transactionManager);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
step.execute(stepExecution);
assertEquals(4, processed.size());
assertEquals(4, stepExecution.getReadCount());
assertEquals(4, stepExecution.getWriteCount());
assertEquals(3, stepExecution.getCommitCount()); //the empty chunk is the 3rd commit
}
@Test
public void testCommitCount_Uneven() throws Exception {
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
step = getStep(new String[] { "foo", "bar", "spam" }, 2);
step.setTransactionManager(transactionManager);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
step.execute(stepExecution);
assertEquals(3, processed.size());
assertEquals(3, stepExecution.getReadCount());
assertEquals(3, stepExecution.getWriteCount());
assertEquals(2, stepExecution.getCommitCount());
}
@Test
public void testEmptyReader() throws Exception {
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
step = getStep(new String[0]);
step.setTasklet(new TestingChunkOrientedTasklet<String>(getReader(new String[0]), itemWriter,
new RepeatTemplate()));
step.setStepOperations(new RepeatTemplate());
step.execute(stepExecution);
assertEquals(0, processed.size());
assertEquals(0, stepExecution.getReadCount());
// Commit after end of data detected (this leads to the commit count
// being one greater than people expect if the commit interval is
// commensurate with the total number of items).h
assertEquals(1, stepExecution.getCommitCount());
}
/**
* StepExecution should be updated after every chunk commit.
*/
@Test
public void testStepExecutionUpdates() throws Exception {
JobExecution jobExecution = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
step.setStepOperations(new RepeatTemplate());
JobRepositoryStub jobRepository = new JobRepositoryStub();
step.setJobRepository(jobRepository);
step.execute(stepExecution);
assertEquals(3, processed.size());
assertEquals(3, stepExecution.getReadCount());
assertTrue(3 <= jobRepository.updateCount);
}
/**
* Failure to update StepExecution after chunk commit is fatal.
*/
@Test
public void testStepExecutionUpdateFailure() throws Exception {
JobExecution jobExecution = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
JobRepository repository = new JobRepositoryFailedUpdateStub();
step.setJobRepository(repository);
step.afterPropertiesSet();
step.execute(stepExecution);
assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus());
}
@Test
public void testRepository() throws Exception {
SimpleJobRepository repository = new SimpleJobRepository(new MapJobInstanceDao(), new MapJobExecutionDao(),
new MapStepExecutionDao(), new MapExecutionContextDao());
step.setJobRepository(repository);
JobExecution jobExecution = repository.createJobExecution(job.getName(), jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
repository.add(stepExecution);
step.execute(stepExecution);
assertEquals(1, processed.size());
}
@Test
public void testIncrementRollbackCount() {
ItemReader<String> itemReader = new ItemReader<String>() {
@Override
public String read() throws Exception {
throw new RuntimeException();
}
};
step.setTasklet(new TestingChunkOrientedTasklet<String>(itemReader, itemWriter));
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
try {
step.execute(stepExecution);
}
catch (Exception ex) {
assertEquals(1, stepExecution.getRollbackCount());
}
}
@Test
public void testExitCodeDefaultClassification() throws Exception {
ItemReader<String> itemReader = new ItemReader<String>() {
@Override
public String read() throws Exception {
throw new RuntimeException();
}
};
step.setTasklet(new TestingChunkOrientedTasklet<String>(itemReader, itemWriter));
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
try {
step.execute(stepExecution);
}
catch (Exception ex) {
ExitStatus status = stepExecution.getExitStatus();
assertEquals(ExitStatus.COMPLETED, status);
}
}
@Test
public void testExitCodeCustomClassification() throws Exception {
ItemReader<String> itemReader = new ItemReader<String>() {
@Override
public String read() throws Exception {
throw new RuntimeException();
}
};
step.setTasklet(new TestingChunkOrientedTasklet<String>(itemReader, itemWriter));
step.registerStepExecutionListener(new StepExecutionListenerSupport() {
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
return ExitStatus.FAILED.addExitDescription("FOO");
}
});
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
try {
step.execute(stepExecution);
}
catch (Exception ex) {
ExitStatus status = stepExecution.getExitStatus();
assertEquals(ExitStatus.FAILED.getExitCode(), status.getExitCode());
String description = status.getExitDescription();
assertTrue("Description does not include 'FOO': " + description, description.indexOf("FOO") >= 0);
}
}
/*
* make sure a job that has never been executed before, but does have
* saveExecutionAttributes = true, doesn't have restoreFrom called on it.
*/
@Test
public void testNonRestartedJob() throws Exception {
MockRestartableItemReader tasklet = new MockRestartableItemReader();
step.setTasklet(new TestingChunkOrientedTasklet<String>(tasklet, itemWriter));
step.registerStream(tasklet);
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
step.execute(stepExecution);
assertFalse(tasklet.isRestoreFromCalled());
assertTrue(tasklet.isGetExecutionAttributesCalled());
}
@Test
public void testSuccessfulExecutionWithExecutionContext() throws Exception {
final JobExecution jobExecution = new JobExecution(jobInstance, jobParameters);
final StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
step.setJobRepository(new JobRepositorySupport() {
@Override
public void updateExecutionContext(StepExecution stepExecution) {
list.add(stepExecution);
}
});
step.execute(stepExecution);
// context saved before looping and updated once for every processing
// loop (once in this case)
assertEquals(3, list.size());
}
@Test
public void testSuccessfulExecutionWithFailureOnSaveOfExecutionContext() throws Exception {
final JobExecution jobExecution = new JobExecution(jobInstance, jobParameters);
final StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
step.setJobRepository(new JobRepositorySupport() {
private int counter = 0;
// initial save before item processing succeeds, later calls fail
@Override
public void updateExecutionContext(StepExecution stepExecution) {
if (counter > 0) {
throw new RuntimeException("foo");
}
counter++;
}
});
step.execute(stepExecution);
Throwable e = stepExecution.getFailureExceptions().get(0);
assertEquals("foo", e.getCause().getMessage());
assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus());
}
/*
* Test that a job that is being restarted, but has saveExecutionAttributes
* set to false, doesn't have restore or getExecutionAttributes called on
* it.
*/
@Test
public void testNoSaveExecutionAttributesRestartableJob() {
MockRestartableItemReader tasklet = new MockRestartableItemReader();
step.setTasklet(new TestingChunkOrientedTasklet<String>(tasklet, itemWriter));
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
try {
step.execute(stepExecution);
}
catch (Throwable t) {
fail();
}
assertFalse(tasklet.isRestoreFromCalled());
}
/*
* Even though the job is restarted, and saveExecutionAttributes is true,
* nothing will be restored because the Tasklet does not implement
* Restartable.
*/
@Test
public void testRestartJobOnNonRestartableTasklet() throws Exception {
step.setTasklet(new TestingChunkOrientedTasklet<String>(new ItemReader<String>() {
@Override
public String read() throws Exception {
return "foo";
}
}, itemWriter));
JobExecution jobExecution = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
step.execute(stepExecution);
}
@Test
public void testStreamManager() throws Exception {
MockRestartableItemReader reader = new MockRestartableItemReader() {
@Override
public String read() {
return "foo";
}
@Override
public void update(ExecutionContext executionContext) {
super.update(executionContext);
executionContext.putString("foo", "bar");
}
};
step.setTasklet(new TestingChunkOrientedTasklet<String>(reader, itemWriter));
step.registerStream(reader);
JobExecution jobExecution = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
assertEquals(false, stepExecution.getExecutionContext().containsKey("foo"));
step.execute(stepExecution);
// At least once in that process the statistics service was asked for
// statistics...
assertEquals("bar", stepExecution.getExecutionContext().getString("foo"));
}
@Test
public void testDirectlyInjectedItemStream() throws Exception {
step.setStreams(new ItemStream[] { new ItemStreamSupport() {
@Override
public void update(ExecutionContext executionContext) {
super.update(executionContext);
executionContext.putString("foo", "bar");
}
} });
JobExecution jobExecution = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
assertEquals(false, stepExecution.getExecutionContext().containsKey("foo"));
step.execute(stepExecution);
assertEquals("bar", stepExecution.getExecutionContext().getString("foo"));
}
@Test
public void testDirectlyInjectedListener() throws Exception {
step.registerStepExecutionListener(new StepExecutionListenerSupport() {
@Override
public void beforeStep(StepExecution stepExecution) {
list.add("foo");
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
list.add("bar");
return null;
}
});
JobExecution jobExecution = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
step.execute(stepExecution);
assertEquals(2, list.size());
}
@Test
public void testListenerCalledBeforeStreamOpened() throws Exception {
MockRestartableItemReader reader = new MockRestartableItemReader() {
@Override
public void beforeStep(StepExecution stepExecution) {
list.add("foo");
}
@Override
public void open(ExecutionContext executionContext) throws ItemStreamException {
super.open(executionContext);
assertEquals(1, list.size());
}
};
step.setStreams(new ItemStream[] { reader });
step.registerStepExecutionListener(reader);
StepExecution stepExecution = new StepExecution(step.getName(), new JobExecution(jobInstance, jobParameters));
step.execute(stepExecution);
assertEquals(1, list.size());
}
@Test
public void testAfterStep() throws Exception {
final ExitStatus customStatus = new ExitStatus("COMPLETED_CUSTOM");
step.setStepExecutionListeners(new StepExecutionListener[] { new StepExecutionListenerSupport() {
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
list.add("afterStepCalled");
return customStatus;
}
} });
RepeatTemplate stepTemplate = new RepeatTemplate();
stepTemplate.setCompletionPolicy(new SimpleCompletionPolicy(5));
step.setStepOperations(stepTemplate);
JobExecution jobExecution = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
step.execute(stepExecution);
assertEquals(1, list.size());
ExitStatus returnedStatus = stepExecution.getExitStatus();
assertEquals(customStatus.getExitCode(), returnedStatus.getExitCode());
assertEquals(customStatus.getExitDescription(), returnedStatus.getExitDescription());
}
@Test
public void testDirectlyInjectedListenerOnError() throws Exception {
step.registerStepExecutionListener(new StepExecutionListenerSupport() {
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
list.add("exception");
return null;
}
});
step.setTasklet(new TestingChunkOrientedTasklet<String>(new MockRestartableItemReader() {
@Override
public String read() throws RuntimeException {
throw new RuntimeException("FOO");
}
}, itemWriter));
JobExecution jobExecution = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
step.execute(stepExecution);
assertEquals("FOO", stepExecution.getFailureExceptions().get(0).getMessage());
assertEquals(1, list.size());
}
@Test
public void testDirectlyInjectedStreamWhichIsAlsoReader() throws Exception {
MockRestartableItemReader reader = new MockRestartableItemReader() {
@Override
public String read() {
return "foo";
}
@Override
public void update(ExecutionContext executionContext) {
super.update(executionContext);
executionContext.putString("foo", "bar");
}
};
step.setTasklet(new TestingChunkOrientedTasklet<String>(reader, itemWriter));
step.setStreams(new ItemStream[] { reader });
JobExecution jobExecution = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecution);
assertEquals(false, stepExecution.getExecutionContext().containsKey("foo"));
step.execute(stepExecution);
// At least once in that process the statistics service was asked for
// statistics...
assertEquals("bar", stepExecution.getExecutionContext().getString("foo"));
}
@Test
public void testStatusForInterruptedException() throws Exception {
StepInterruptionPolicy interruptionPolicy = new StepInterruptionPolicy() {
@Override
public void checkInterrupted(StepExecution stepExecution) throws JobInterruptedException {
throw new JobInterruptedException("interrupted");
}
};
step.setInterruptionPolicy(interruptionPolicy);
ItemReader<String> itemReader = new ItemReader<String>() {
@Override
public String read() throws Exception {
throw new RuntimeException();
}
};
step.setTasklet(new TestingChunkOrientedTasklet<String>(itemReader, itemWriter));
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
stepExecution.setExecutionContext(foobarEc);
step.execute(stepExecution);
assertEquals(BatchStatus.STOPPED, stepExecution.getStatus());
String msg = stepExecution.getExitStatus().getExitDescription();
assertTrue("Message does not contain 'JobInterruptedException': " + msg, msg
.contains("JobInterruptedException"));
}
@Test
public void testStatusForNormalFailure() throws Exception {
ItemReader<String> itemReader = new ItemReader<String>() {
@Override
public String read() throws Exception {
// Trigger a rollback
throw new RuntimeException("Foo");
}
};
step.setTasklet(new TestingChunkOrientedTasklet<String>(itemReader, itemWriter));
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
stepExecution.setExecutionContext(foobarEc);
// step.setLastExecution(stepExecution);
step.execute(stepExecution);
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
// The original rollback was caused by this one:
assertEquals("Foo", stepExecution.getFailureExceptions().get(0).getMessage());
}
@Test
public void testStatusForErrorFailure() throws Exception {
ItemReader<String> itemReader = new ItemReader<String>() {
@Override
public String read() throws Exception {
// Trigger a rollback
throw new Error("Foo");
}
};
step.setTasklet(new TestingChunkOrientedTasklet<String>(itemReader, itemWriter));
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
stepExecution.setExecutionContext(foobarEc);
// step.setLastExecution(stepExecution);
step.execute(stepExecution);
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
// The original rollback was caused by this one:
assertEquals("Foo", stepExecution.getFailureExceptions().get(0).getMessage());
}
@SuppressWarnings("serial")
@Test
public void testStatusForResetFailedException() throws Exception {
ItemReader<String> itemReader = new ItemReader<String>() {
@Override
public String read() throws Exception {
// Trigger a rollback
throw new RuntimeException("Foo");
}
};
step.setTasklet(new TestingChunkOrientedTasklet<String>(itemReader, itemWriter));
step.setTransactionManager(new ResourcelessTransactionManager() {
@Override
protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
// Simulate failure on rollback when stream resets
throw new RuntimeException("Bar");
}
});
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
stepExecution.setExecutionContext(foobarEc);
// step.setLastExecution(stepExecution);
step.execute(stepExecution);
assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus());
String msg = stepExecution.getExitStatus().getExitDescription();
assertTrue("Message does not contain ResetFailedException: " + msg, msg.contains("ResetFailedException"));
// The original rollback was caused by this one:
assertEquals("Bar", stepExecution.getFailureExceptions().get(0).getMessage());
}
@SuppressWarnings("serial")
@Test
public void testStatusForCommitFailedException() throws Exception {
step.setTransactionManager(new ResourcelessTransactionManager() {
@Override
protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
// Simulate failure on commit
throw new RuntimeException("Foo");
}
@Override
protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
throw new RuntimeException("Bar");
}
});
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
stepExecution.setExecutionContext(foobarEc);
// step.setLastExecution(stepExecution);
step.execute(stepExecution);
assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus());
Throwable ex = stepExecution.getFailureExceptions().get(0);
// The original rollback failed because of this one:
assertEquals("Bar", ex.getMessage());
}
@Test
public void testStatusForFinalUpdateFailedException() throws Exception {
step.setJobRepository(new JobRepositorySupport());
step.setStreams(new ItemStream[] { new ItemStreamSupport() {
@Override
public void close() throws ItemStreamException {
super.close();
throw new RuntimeException("Bar");
}
} });
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
step.execute(stepExecution);
// The job actually completed, but the streams couldn't be closed.
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
String msg = stepExecution.getExitStatus().getExitDescription();
assertEquals("", msg);
Throwable ex = stepExecution.getFailureExceptions().get(0);
// The original rollback was caused by this one:
assertEquals("Bar", ex.getMessage());
}
@Test
public void testStatusForCloseFailedException() throws Exception {
MockRestartableItemReader itemReader = new MockRestartableItemReader() {
@Override
public void close() throws ItemStreamException {
super.close();
// Simulate failure on rollback when stream resets
throw new RuntimeException("Bar");
}
};
step.setTasklet(new TestingChunkOrientedTasklet<String>(itemReader, itemWriter));
step.registerStream(itemReader);
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
stepExecution.setExecutionContext(foobarEc);
// step.setLastExecution(stepExecution);
step.execute(stepExecution);
// The job actually completed, but the streams couldn't be closed.
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
String msg = stepExecution.getExitStatus().getExitDescription();
assertEquals("", msg);
Throwable ex = stepExecution.getFailureExceptions().get(0);
// The original rollback was caused by this one:
assertEquals("Bar", ex.getMessage());
}
/**
* Execution context must not be left empty even if job failed before
* committing first chunk - otherwise ItemStreams won't recognize it is
* restart scenario on next run.
*/
@Test
public void testRestartAfterFailureInFirstChunk() throws Exception {
MockRestartableItemReader reader = new MockRestartableItemReader() {
@Override
public String read() throws RuntimeException {
// fail on the very first item
throw new RuntimeException("CRASH!");
}
};
step.setTasklet(new TestingChunkOrientedTasklet<String>(reader, itemWriter));
step.registerStream(reader);
StepExecution stepExecution = new StepExecution(step.getName(), new JobExecution(jobInstance, jobParameters));
step.execute(stepExecution);
assertEquals(BatchStatus.FAILED, stepExecution.getStatus());
Throwable expected = stepExecution.getFailureExceptions().get(0);
assertEquals("CRASH!", expected.getMessage());
assertFalse(stepExecution.getExecutionContext().isEmpty());
assertTrue(stepExecution.getExecutionContext().getString("spam").equals("bucket"));
}
@Test
public void testStepToCompletion() throws Exception {
RepeatTemplate template = new RepeatTemplate();
// process all items:
template.setCompletionPolicy(new DefaultResultCompletionPolicy());
step.setStepOperations(template);
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
step.execute(stepExecution);
assertEquals(3, processed.size());
assertEquals(3, stepExecution.getReadCount());
}
/**
* Exception in {@link StepExecutionListener#afterStep(StepExecution)}
* doesn't cause step failure.
* @throws JobInterruptedException
*/
@Test
public void testStepFailureInAfterStepCallback() throws JobInterruptedException {
StepExecutionListener listener = new StepExecutionListenerSupport() {
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
throw new RuntimeException("exception thrown in afterStep to signal failure");
}
};
step.setStepExecutionListeners(new StepExecutionListener[] { listener });
StepExecution stepExecution = new StepExecution(step.getName(), new JobExecution(jobInstance, jobParameters));
step.execute(stepExecution);
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
}
@Test
public void testNoRollbackFor() throws Exception {
step.setTasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
throw new RuntimeException("Bar");
}
});
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
@SuppressWarnings("serial")
DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute() {
@Override
public boolean rollbackOn(Throwable ex) {
return false;
}
};
step.setTransactionAttribute(transactionAttribute);
step.execute(stepExecution);
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
}
@Test
public void testTaskletExecuteReturnNull() throws Exception {
step.setTasklet(new Tasklet() {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
return null;
}
});
JobExecution jobExecutionContext = new JobExecution(jobInstance, jobParameters);
StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext);
step.execute(stepExecution);
assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus());
}
private static class JobRepositoryStub extends JobRepositorySupport {
private int updateCount = 0;
@Override
public void update(StepExecution stepExecution) {
updateCount++;
if (updateCount <= 3) {
assertEquals(updateCount, stepExecution.getReadCount() + 1);
}
}
}
private static class JobRepositoryFailedUpdateStub extends JobRepositorySupport {
private int called = 0;
@Override
public void update(StepExecution stepExecution) {
called++;
if (called == 3) {
throw new DataAccessResourceFailureException("stub exception");
}
}
}
private class MockRestartableItemReader extends AbstractItemStreamItemReader<String> implements StepExecutionListener {
private boolean getExecutionAttributesCalled = false;
private boolean restoreFromCalled = false;
@Override
public String read() {
return "item";
}
@Override
public void update(ExecutionContext executionContext) {
super.update(executionContext);
getExecutionAttributesCalled = true;
executionContext.putString("spam", "bucket");
}
public boolean isGetExecutionAttributesCalled() {
return getExecutionAttributesCalled;
}
public boolean isRestoreFromCalled() {
return restoreFromCalled;
}
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
return null;
}
@Override
public void beforeStep(StepExecution stepExecution) {
}
}
}