From bbc34c13e776fb5dee1b05424ff71fbc1ed60113 Mon Sep 17 00:00:00 2001 From: lucasward Date: Tue, 16 Sep 2008 22:15:46 +0000 Subject: [PATCH] IN PROGRESS - issue BATCH-825: Modify JobLauncher contract to not throw exception on job failure. http://jira.springframework.org/browse/BATCH-825 Job, Step, and JobLauncher will not throw an exception if the job fails during processing, but rather will return the execution with an appropriate status and list of exceptions encountered. --- .../org/springframework/batch/core/Job.java | 7 +- .../batch/core/JobExecution.java | 55 ++++ .../batch/core/StepExecution.java | 16 ++ .../support/ApplicationContextJobFactory.java | 3 +- .../batch/core/job/SimpleJob.java | 40 ++- .../batch/core/launch/JobLauncher.java | 8 +- .../launch/support/SimpleJobLauncher.java | 4 +- .../batch/core/step/AbstractStep.java | 32 +-- .../batch/core/JobExecutionTests.java | 42 ++- .../batch/core/StepExecutionTests.java | 48 +++- .../batch/core/job/AbstractJobTests.java | 9 +- .../batch/core/job/SimpleJobFailureTests.java | 19 ++ .../batch/core/job/SimpleJobTests.java | 150 ++++------ .../step/item/SimpleStepFactoryBeanTests.java | 22 +- .../item/SkipLimitStepFactoryBeanTests.java | 60 +--- .../StatefulRetryStepFactoryBeanTests.java | 46 +-- .../step/item/TaskletStepExceptionTests.java | 265 ++++++++++++++++++ .../ChunkOrientedStepIntegrationTests.java | 33 +-- .../StepExecutorInterruptionTests.java | 11 +- .../core/step/tasklet/TaskletStepTests.java | 185 +++++------- 20 files changed, 644 insertions(+), 411 deletions(-) create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobFailureTests.java create mode 100644 spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java b/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java index fdd0b7b70..4a93b954b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/Job.java @@ -32,12 +32,13 @@ public interface Job { /** * Run the {@link JobExecution} and update the meta information like status - * and statistics as necessary. + * and statistics as necessary. This method should not throw any exceptions + * for failed execution. Clients should be careful to inspect the {@link JobExecution} + * status to determine success or failure. * * @param execution a {@link JobExecution} - * @throws JobExecutionException */ - void execute(JobExecution execution) throws JobExecutionException; + void execute(JobExecution execution); /** * @return in incrementer to be used for creating new parameters diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java index 91281a58b..7207d344b 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/JobExecution.java @@ -16,9 +16,11 @@ package org.springframework.batch.core; +import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashSet; +import java.util.List; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.repeat.ExitStatus; @@ -48,6 +50,8 @@ public class JobExecution extends Entity { private volatile ExitStatus exitStatus = ExitStatus.UNKNOWN; private volatile ExecutionContext executionContext = new ExecutionContext(); + + private volatile List failureExceptions = new ArrayList(); /** * Because a JobExecution isn't valid unless the job is set, this @@ -234,12 +238,63 @@ public class JobExecution extends Entity { stepExecutions.add(stepExecution); } + /** + * Get the date representing the last time this JobExecution was updated in the + * JobRepository. + * + * @return Date representing the last time this JobExecution was updated. + */ public Date getLastUpdated() { return lastUpdated; } + /** + * Set the last time this JobExecution was updated. + * + * @param lastUpdated + */ public void setLastUpdated(Date lastUpdated) { this.lastUpdated = lastUpdated; } + public List getFailureExceptions() { + return failureExceptions; + } + + /** + * Set the list of failure causing exceptions for this JobExecution. It + * should be noted that the exceptions should only be for the job execution + * not step executions. + * + * @param failureExceptions + */ + public void setFailureExceptions(List failureExceptions) { + this.failureExceptions = failureExceptions; + } + + /** + * Add the provided throwable to the failure exception list. + * + * @param t + */ + public void addFailureException(Throwable t){ + this.failureExceptions.add(t); + } + + /** + * Return all failure causing exceptions for this JobExecution, including + * step executions. + * + * @return List containing all exceptions causing failure for this + * JobExecution. + */ + public List getAllFailureExceptions(){ + + List allExceptions = new ArrayList(failureExceptions); + for(StepExecution stepExecution: stepExecutions){ + allExceptions.addAll(stepExecution.getFailureExceptions()); + } + + return allExceptions; + } } \ No newline at end of file diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java b/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java index d63019180..da8d4a6e5 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/StepExecution.java @@ -16,7 +16,9 @@ package org.springframework.batch.core; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.repeat.ExitStatus; @@ -68,6 +70,8 @@ public class StepExecution extends Entity { private volatile boolean terminateOnly; private volatile int filterCount; + + private volatile List failureExceptions = new ArrayList(); /** * Constructor with mandatory properties. @@ -436,6 +440,18 @@ public class StepExecution extends Entity { public void setLastUpdated(Date lastUpdated) { this.lastUpdated = lastUpdated; } + + public List getFailureExceptions() { + return failureExceptions; + } + + public void setFailureExceptions(List failureExceptions) { + this.failureExceptions = failureExceptions; + } + + public void addFailureException(Throwable throwable){ + this.failureExceptions.add(throwable); + } /* * (non-Javadoc) diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java index bbad0185a..59201f4eb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/support/ApplicationContextJobFactory.java @@ -90,10 +90,9 @@ public class ApplicationContextJobFactory implements JobFactory { /** * @param execution - * @throws JobExecutionException * @see org.springframework.batch.core.Job#execute(org.springframework.batch.core.JobExecution) */ - public void execute(JobExecution execution) throws JobExecutionException { + public void execute(JobExecution execution) { try { delegate.execute(execution); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java index 7f9e54f03..c62b35aeb 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/job/SimpleJob.java @@ -28,7 +28,6 @@ import org.springframework.batch.core.JobInterruptedException; import org.springframework.batch.core.StartLimitExceededException; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.UnexpectedJobExecutionException; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.repeat.ExitStatus; @@ -50,7 +49,7 @@ public class SimpleJob extends AbstractJob { * @throws StartLimitExceededException if start limit of one of the steps * was exceeded */ - public void execute(JobExecution execution) throws JobExecutionException { + public void execute(JobExecution execution){ JobInstance jobInstance = execution.getJobInstance(); @@ -97,6 +96,10 @@ public class SimpleJob extends AbstractJob { step.execute(currentStepExecution); + if(currentStepExecution.getStatus() == BatchStatus.FAILED || + currentStepExecution.getStatus() == BatchStatus.STOPPED ){ + break; + } } } @@ -105,20 +108,30 @@ public class SimpleJob extends AbstractJob { throw new JobInterruptedException("JobExecution interrupted."); } - updateStatus(execution, BatchStatus.COMPLETED); + updateStatus(execution, currentStepExecution.getStatus()); - getCompositeListener().afterJob(execution); + //This is temporary, given the changes to how exceptions are handled, there really should only be an afterJob method + //but it's being left as is until the listener contract can be discussed. + if(execution.getStatus() == BatchStatus.COMPLETED){ + getCompositeListener().afterJob(execution); + } + else if(execution.getStatus() == BatchStatus.FAILED){ + getCompositeListener().onError(execution, currentStepExecution.getFailureExceptions().get(0)); + } + else if(execution.getStatus() == BatchStatus.STOPPED){ + getCompositeListener().onInterrupt(execution); + } } catch (JobInterruptedException e) { execution.setStatus(BatchStatus.STOPPED); + execution.addFailureException(e); getCompositeListener().onInterrupt(execution); - rethrow(e); } catch (Throwable t) { execution.setStatus(BatchStatus.FAILED); + execution.addFailureException(t); getCompositeListener().onError(execution, t); - rethrow(t); } finally { ExitStatus status = ExitStatus.FAILED; @@ -185,19 +198,4 @@ public class SimpleJob extends AbstractJob { + "StartMax: " + step.getStartLimit()); } } - - /** - * @param t - */ - private static void rethrow(Throwable t) throws RuntimeException { - if (t instanceof RuntimeException) { - throw (RuntimeException) t; - } - else if (t instanceof Error) { - throw (Error) t; - } - else { - throw new UnexpectedJobExecutionException("Unexpected checked exception in job execution", t); - } - } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java index 81709f533..557f02461 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/JobLauncher.java @@ -37,10 +37,12 @@ import org.springframework.batch.core.repository.JobExecutionAlreadyRunningExcep public interface JobLauncher { /** - * Start a job execution for the given {@link Job} and {@link JobParameters}. + * Start a job execution for the given {@link Job} and {@link JobParameters}. If + * a JobExecution was able to be created successfully, it will always be returned + * by this method, regardless of whether or not the execution was successful. * - * @return the exit code from the job if it returns synchronously. If the - * implementation is asynchronous, the status might well be unknown. + * @return the {@link JobExecution} if it returns synchronously. If the + * implementation is asynchronous, the status might well be unknown. * * @throws JobExecutionAlreadyRunningException if the JobInstance identified * by the properties already has an execution running. diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java index 2e16c82dd..c8f4646c4 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/launch/support/SimpleJobLauncher.java @@ -99,8 +99,8 @@ public class SimpleJobLauncher implements JobLauncher, InitializingBean { try { logger.info("Job: [" + job + "] launched with the following parameters: [" + jobParameters + "]"); job.execute(jobExecution); - logger.info("Job: [" + job + "] completed successfully with the following parameters: [" - + jobParameters + "]"); + logger.info("Job: [" + job + "] completed with the following parameters: [" + + jobParameters + "] and the following status: [" + jobExecution.getStatus() + "]"); } catch (Throwable t) { logger.info("Job: [" + job + "] failed with the following parameters: [" + jobParameters + "]", t); diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java index 530afa2ca..25be8c920 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/AbstractStep.java @@ -188,12 +188,8 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw try { getCompositeListener().beforeStep(stepExecution); - try { - open(stepExecution.getExecutionContext()); - } - catch (Exception e) { - throw new UnexpectedJobExecutionException("Failed to initialize the step", e); - } + open(stepExecution.getExecutionContext()); + exitStatus = doExecute(stepExecution); // Check if someone is trying to stop us @@ -220,6 +216,7 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw logger.error("Encountered an error executing the step: " + e.getClass() + ": " + e.getMessage()); stepExecution.setStatus(determineBatchStatus(e)); exitStatus = getDefaultExitStatusForFailure(e); + stepExecution.addFailureException(e); try { exitStatus = exitStatus.and(getCompositeListener().onErrorInStep(stepExecution, e)); @@ -227,8 +224,8 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw } catch (Exception ex) { logger.error("Encountered an error on listener error callback.", ex); + stepExecution.addFailureException(ex); } - rethrow(e); } finally { @@ -252,35 +249,18 @@ public abstract class AbstractStep implements Step, InitializingBean, BeanNameAw } catch (Exception e) { logger.error("Exception while closing step execution resources", e); - throw new UnexpectedJobExecutionException("Exception while closing step resources", e); + stepExecution.addFailureException(e); } if (commitException != null) { stepExecution.setStatus(BatchStatus.UNKNOWN); logger.error("Encountered an error saving batch meta data." + "This job is now in an unknown state and should not be restarted.", commitException); - throw new UnexpectedJobExecutionException("Encountered an error saving batch meta data.", - commitException); + stepExecution.addFailureException(commitException); } } } - private static void rethrow(Throwable e) throws JobInterruptedException { - if (e instanceof Error) { - throw (Error) e; - } - if (e instanceof JobInterruptedException) { - throw (JobInterruptedException) e; - } - else if (e.getCause() instanceof JobInterruptedException) { - throw (JobInterruptedException) e.getCause(); - } - else if (e instanceof RuntimeException) { - throw (RuntimeException) e; - } - throw new UnexpectedJobExecutionException("Unexpected checked exception in step execution", e); - } - /** * Determine the step status based on the exception. */ diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java index 47ba2b425..f396edc4d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/JobExecutionTests.java @@ -15,21 +15,24 @@ */ package org.springframework.batch.core; -import java.util.Date; +import static org.junit.Assert.*; -import junit.framework.TestCase; +import java.util.Date; +import java.util.List; import org.apache.commons.lang.SerializationUtils; +import org.junit.Test; import org.springframework.batch.repeat.ExitStatus; /** * @author Dave Syer * */ -public class JobExecutionTests extends TestCase { +public class JobExecutionTests { private JobExecution execution = new JobExecution(new JobInstance(new Long(11), new JobParameters(), "foo"), new Long(12)); + @Test public void testJobExecution() { assertNull(new JobExecution(new JobInstance(null,null,"foo")).getId()); } @@ -38,6 +41,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getEndTime()}. */ + @Test public void testGetEndTime() { assertNull(execution.getEndTime()); execution.setEndTime(new Date(100L)); @@ -48,6 +52,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getEndTime()}. */ + @Test public void testIsRunning() { assertTrue(execution.isRunning()); execution.setEndTime(new Date(100L)); @@ -58,6 +63,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getEndTime()}. */ + @Test public void testIsRunningWithStoppedExecution() { assertTrue(execution.isRunning()); execution.stop(); @@ -69,6 +75,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getStartTime()}. */ + @Test public void testGetStartTime() { execution.setStartTime(new Date(0L)); assertEquals(0L, execution.getStartTime().getTime()); @@ -78,6 +85,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getStatus()}. */ + @Test public void testGetStatus() { assertEquals(BatchStatus.STARTING, execution.getStatus()); execution.setStatus(BatchStatus.COMPLETED); @@ -88,6 +96,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getJobId()}. */ + @Test public void testGetJobId() { assertEquals(11, execution.getJobId().longValue()); execution = new JobExecution(new JobInstance(new Long(23), new JobParameters(), "testJob"), null); @@ -98,6 +107,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getJobId()}. */ + @Test public void testGetJobIdForNullJob() { execution = new JobExecution(null, null); assertEquals(null, execution.getJobId()); @@ -107,6 +117,7 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getJobId()}. */ + @Test public void testGetJob() { assertNotNull(execution.getJobInstance()); } @@ -115,22 +126,26 @@ public class JobExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getExitStatus()}. */ + @Test public void testGetExitCode() { assertEquals(ExitStatus.UNKNOWN, execution.getExitStatus()); execution.setExitStatus(new ExitStatus(true, "23")); assertEquals("23", execution.getExitStatus().getExitCode()); } + @Test public void testContextContainsInfo() throws Exception { assertEquals("foo", execution.getJobInstance().getJobName()); } + @Test public void testAddAndRemoveStepExecution() throws Exception { assertEquals(0, execution.getStepExecutions().size()); execution.createStepExecution("step"); assertEquals(1, execution.getStepExecutions().size()); } + @Test public void testStop() throws Exception { StepExecution stepExecution = execution.createStepExecution("step"); assertFalse(stepExecution.isTerminateOnly()); @@ -138,21 +153,42 @@ public class JobExecutionTests extends TestCase { assertTrue(stepExecution.isTerminateOnly()); } + @Test public void testToString() throws Exception { assertTrue("JobExecution string does not contain id", execution.toString().indexOf("id=") >= 0); assertTrue("JobExecution string does not contain name: " + execution, execution.toString().indexOf("foo") >= 0); } + @Test public void testToStringWithNullJob() throws Exception { execution = new JobExecution(new JobInstance(null,null,"foo")); assertTrue("JobExecution string does not contain id", execution.toString().indexOf("id=") >= 0); assertTrue("JobExecution string does not contain job: " + execution, execution.toString().indexOf("job=") >= 0); } + @Test public void testSerialization() { byte[] serialized = SerializationUtils.serialize(execution); JobExecution deserialize = (JobExecution) SerializationUtils.deserialize(serialized); assertEquals(execution, deserialize); assertNotNull(deserialize.createStepExecution("foo")); } + + public void testFailureExceptions(){ + + RuntimeException exception = new RuntimeException(); + assertEquals(0, execution.getFailureExceptions().size()); + execution.addFailureException(exception); + assertEquals(1, execution.getFailureExceptions().size()); + assertEquals(exception, execution.getFailureExceptions().get(0)); + StepExecution stepExecution1 = execution.createStepExecution("execution1"); + RuntimeException stepException1 = new RuntimeException(); + stepExecution1.addFailureException(stepException1); + execution.createStepExecution("execution2"); + List allExceptions = execution.getAllFailureExceptions(); + assertEquals(2, allExceptions.size()); + assertEquals(1, execution.getFailureExceptions().size()); + assertTrue(allExceptions.contains(exception)); + assertTrue(allExceptions.contains(stepException1)); + } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java index 55ac6f2e0..d41c1daab 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/StepExecutionTests.java @@ -15,13 +15,15 @@ */ package org.springframework.batch.core; +import static org.junit.Assert.*; + import java.util.Date; import java.util.HashSet; import java.util.Set; -import junit.framework.TestCase; - import org.apache.commons.lang.SerializationUtils; +import org.junit.Before; +import org.junit.Test; import org.springframework.batch.core.step.StepSupport; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.repeat.ExitStatus; @@ -30,7 +32,7 @@ import org.springframework.batch.repeat.ExitStatus; * @author Dave Syer * */ -public class StepExecutionTests extends TestCase { +public class StepExecutionTests { private StepExecution execution = newStepExecution(new StepSupport("stepName"), new Long(23)); @@ -40,15 +42,17 @@ public class StepExecutionTests extends TestCase { - @Override - protected void setUp() throws Exception { + @Before + public void setUp() throws Exception { foobarEc.put("foo", "bar"); } + @Test public void testStepExecution() { assertNull(new StepExecution("step", null).getId()); } + @Test public void testStepExecutionWithNullId() { assertNull(new StepExecution("stepName", new JobExecution(new JobInstance(null,null,"foo"))).getId()); } @@ -57,6 +61,7 @@ public class StepExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getEndTime()}. */ + @Test public void testGetEndTime() { assertNull(execution.getEndTime()); execution.setEndTime(new Date(0L)); @@ -67,6 +72,7 @@ public class StepExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getStartTime()}. */ + @Test public void testGetStartTime() { assertNotNull(execution.getStartTime()); execution.setStartTime(new Date(10L)); @@ -77,6 +83,7 @@ public class StepExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getStatus()}. */ + @Test public void testGetStatus() { assertEquals(BatchStatus.STARTING, execution.getStatus()); execution.setStatus(BatchStatus.COMPLETED); @@ -87,6 +94,7 @@ public class StepExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getJobId()}. */ + @Test public void testGetJobId() { assertEquals(23, execution.getJobExecutionId().longValue()); } @@ -95,6 +103,7 @@ public class StepExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.JobExecution#getExitStatus()}. */ + @Test public void testGetExitCode() { assertEquals(ExitStatus.CONTINUABLE, execution.getExitStatus()); execution.setExitStatus(ExitStatus.FINISHED); @@ -105,20 +114,24 @@ public class StepExecutionTests extends TestCase { * Test method for * {@link org.springframework.batch.core.StepExecution#getCommitCount()}. */ + @Test public void testGetCommitCount() { execution.setCommitCount(123); assertEquals(123, execution.getCommitCount()); } + @Test public void testGetFilterCount() { execution.setFilterCount(123); assertEquals(123, execution.getFilterCount()); } + @Test public void testGetJobExecution() throws Exception { assertNotNull(execution.getJobExecution()); } + @Test public void testApplyContribution() throws Exception { StepContribution contribution = execution.createStepContribution(); contribution.incrementReadSkipCount(); @@ -134,12 +147,14 @@ public class StepExecutionTests extends TestCase { assertEquals(1, execution.getFilterCount()); } + @Test public void testTerminateOnly() throws Exception { assertFalse(execution.isTerminateOnly()); execution.setTerminateOnly(); assertTrue(execution.isTerminateOnly()); } + @Test public void testNullNameIsIllegal() throws Exception { try { new StepExecution(null, new JobExecution(new JobInstance(null, null, "job"))); @@ -150,6 +165,7 @@ public class StepExecutionTests extends TestCase { } } + @Test public void testToString() throws Exception { assertTrue("Should contain read count: " + execution.toString(), execution.toString().indexOf("read") >= 0); assertTrue("Should contain write count: " + execution.toString(), execution.toString().indexOf("write") >= 0); @@ -159,6 +175,7 @@ public class StepExecutionTests extends TestCase { execution.toString().indexOf("rollback") >= 0); } + @Test public void testExecutionContext() throws Exception { assertNotNull(execution.getExecutionContext()); ExecutionContext context = new ExecutionContext(); @@ -167,6 +184,7 @@ public class StepExecutionTests extends TestCase { assertEquals("bar", execution.getExecutionContext().getString("foo")); } + @Test public void testEqualsWithSameIdentifier() throws Exception { Step step = new StepSupport("stepName"); Entity stepExecution1 = newStepExecution(step, new Long(11)); @@ -174,30 +192,36 @@ public class StepExecutionTests extends TestCase { assertEquals(stepExecution1, stepExecution2); } + @Test public void testEqualsWithNull() throws Exception { Entity stepExecution = newStepExecution(new StepSupport("stepName"), new Long(11)); assertFalse(stepExecution.equals(null)); } + @Test public void testEqualsWithNullIdentifiers() throws Exception { Entity stepExecution = newStepExecution(new StepSupport("stepName"), new Long(11)); assertFalse(stepExecution.equals(blankExecution)); } + @Test public void testEqualsWithNullJob() throws Exception { Entity stepExecution = newStepExecution(new StepSupport("stepName"), new Long(11)); assertFalse(stepExecution.equals(blankExecution)); } + @Test public void testEqualsWithSelf() throws Exception { assertTrue(execution.equals(execution)); } + @Test public void testEqualsWithDifferent() throws Exception { Entity stepExecution = newStepExecution(new StepSupport("foo"), new Long(13)); assertFalse(execution.equals(stepExecution)); } + @Test public void testEqualsWithNullStepId() throws Exception { Step step = new StepSupport("name"); execution = newStepExecution(step, new Long(31)); @@ -207,15 +231,18 @@ public class StepExecutionTests extends TestCase { assertTrue(execution.equals(stepExecution)); } + @Test public void testHashCode() throws Exception { assertTrue("Hash code same as parent", new Entity(execution.getId()).hashCode() != execution.hashCode()); } + @Test public void testHashCodeWithNullIds() throws Exception { assertTrue("Hash code not same as parent", new Entity(execution.getId()).hashCode() != blankExecution .hashCode()); } + @Test public void testHashCodeViaHashSet() throws Exception { Set set = new HashSet(); set.add(execution); @@ -224,6 +251,7 @@ public class StepExecutionTests extends TestCase { assertTrue(set.contains(execution)); } + @Test public void testSerialization() throws Exception { ExitStatus status = ExitStatus.NOOP; @@ -236,6 +264,16 @@ public class StepExecutionTests extends TestCase { assertEquals(execution, deserialized); assertEquals(status, deserialized.getExitStatus()); } + + @Test + public void testAddException() throws Exception{ + + RuntimeException exception = new RuntimeException(); + assertEquals(0, execution.getFailureExceptions().size()); + execution.addFailureException(exception); + assertEquals(1, execution.getFailureExceptions().size()); + assertEquals(exception, execution.getFailureExceptions().get(0)); + } private StepExecution newStepExecution(Step step, Long long2) { JobInstance job = new JobInstance(new Long(3), new JobParameters(), "testJob"); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/AbstractJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/AbstractJobTests.java index 99174322d..00a6856fe 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/AbstractJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/AbstractJobTests.java @@ -20,7 +20,6 @@ import java.util.Collections; import junit.framework.TestCase; import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionException; import org.springframework.batch.core.Step; import org.springframework.batch.core.step.StepSupport; @@ -31,7 +30,7 @@ import org.springframework.batch.core.step.StepSupport; public class AbstractJobTests extends TestCase { AbstractJob job = new AbstractJob("job") { - public void execute(JobExecution execution) throws JobExecutionException { + public void execute(JobExecution execution) { throw new UnsupportedOperationException(); } }; @@ -41,7 +40,7 @@ public class AbstractJobTests extends TestCase { */ public void testGetName() { job = new AbstractJob(){ - public void execute(JobExecution execution) throws JobExecutionException { + public void execute(JobExecution execution) { // No-op } }; @@ -61,7 +60,7 @@ public class AbstractJobTests extends TestCase { */ public void testSetBeanNameWithNullName() { job = new AbstractJob(null) { - public void execute(JobExecution execution) throws JobExecutionException { + public void execute(JobExecution execution) { // NO-OP } }; @@ -103,7 +102,7 @@ public class AbstractJobTests extends TestCase { public void testAfterPropertiesSet() throws Exception { AbstractJob job = new AbstractJob() { - public void execute(JobExecution execution) throws JobExecutionException { + public void execute(JobExecution execution) { } }; job.setJobRepository(null); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobFailureTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobFailureTests.java new file mode 100644 index 000000000..6d24492f7 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobFailureTests.java @@ -0,0 +1,19 @@ +package org.springframework.batch.core.job; + +import org.junit.Test; + +/** + * Test suite for various failure scenarios during job processing. + * + * @author Lucas Ward + * + */ +public class SimpleJobFailureTests { + + + @Test + public void testStepFailure(){ + + + } +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java index f7f7255a6..63af89891 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/job/SimpleJobTests.java @@ -16,7 +16,9 @@ package org.springframework.batch.core.job; -import static org.easymock.EasyMock.*; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; import java.io.Serializable; import java.util.ArrayList; @@ -32,7 +34,6 @@ import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobInterruptedException; import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.StartLimitExceededException; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.UnexpectedJobExecutionException; @@ -217,13 +218,9 @@ public class SimpleJobTests extends TestCase { step2.setStartLimit(5); final JobInterruptedException exception = new JobInterruptedException("Interrupt!"); step1.setProcessException(exception); - try { - job.execute(jobExecution); - fail(); - } - catch (UnexpectedJobExecutionException e) { - assertEquals(exception, e.getCause()); - } + job.execute(jobExecution); + assertEquals(1, jobExecution.getAllFailureExceptions().size()); + assertEquals(exception, jobExecution.getAllFailureExceptions().get(0)); assertEquals(0, list.size()); checkRepository(BatchStatus.STOPPED, ExitStatus.FAILED); } @@ -233,13 +230,10 @@ public class SimpleJobTests extends TestCase { step2.setStartLimit(5); final RuntimeException exception = new RuntimeException("Foo!"); step1.setProcessException(exception); - try { - job.execute(jobExecution); - fail(); - } - catch (RuntimeException e) { - assertEquals(exception, e); - } + + job.execute(jobExecution); + assertEquals(1, jobExecution.getAllFailureExceptions().size()); + assertEquals(exception, jobExecution.getAllFailureExceptions().get(0)); assertEquals(0, list.size()); checkRepository(BatchStatus.FAILED, ExitStatus.FAILED); } @@ -253,15 +247,10 @@ public class SimpleJobTests extends TestCase { final RuntimeException exception = new RuntimeException("Foo!"); step1.setProcessException(exception); - try { - job.execute(jobExecution); - fail(); - } - catch (RuntimeException e) { - assertEquals(exception, e); - } + job.execute(jobExecution); + assertEquals(1, jobExecution.getAllFailureExceptions().size()); + assertEquals(exception, jobExecution.getAllFailureExceptions().get(0)); assertEquals(1, list.size()); - assertSame(exception, list.get(0)); checkRepository(BatchStatus.FAILED, ExitStatus.FAILED); } @@ -270,13 +259,10 @@ public class SimpleJobTests extends TestCase { step2.setStartLimit(5); final Error exception = new Error("Foo!"); step1.setProcessException(exception); - try { - job.execute(jobExecution); - fail(); - } - catch (Error e) { - assertEquals(exception, e); - } + + job.execute(jobExecution); + assertEquals(1, jobExecution.getAllFailureExceptions().size()); + assertEquals(exception, jobExecution.getAllFailureExceptions().get(0)); assertEquals(0, list.size()); checkRepository(BatchStatus.FAILED, ExitStatus.FAILED); } @@ -285,15 +271,12 @@ public class SimpleJobTests extends TestCase { // Start policy will return false, keeping the step from being started. step1.setStartLimit(0); - try { - job.execute(jobExecution); - fail("Expected BatchCriticalException"); - } - catch (StartLimitExceededException ex) { - // expected - assertTrue("Wrong message in exception: " + ex.getMessage(), ex.getMessage() + job.execute(jobExecution); + + assertEquals(1, jobExecution.getFailureExceptions().size()); + Throwable ex = jobExecution.getFailureExceptions().get(0); + assertTrue("Wrong message in exception: " + ex.getMessage(), ex.getMessage() .indexOf("start limit exceeded") >= 0); - } } public void testNoSteps() throws Exception { @@ -320,13 +303,10 @@ public class SimpleJobTests extends TestCase { public void testNotExecutedIfAlreadyStopped() throws Exception { jobExecution.stop(); - try { - job.execute(jobExecution); - fail(); - } - catch (UnexpectedJobExecutionException e) { - assertTrue(e.getCause() instanceof JobInterruptedException); - } + job.execute(jobExecution); + + assertEquals(1, jobExecution.getFailureExceptions().size()); + assertTrue(jobExecution.getFailureExceptions().get(0) instanceof JobInterruptedException); assertEquals(0, list.size()); checkRepository(BatchStatus.STOPPED, ExitStatus.NOOP); ExitStatus exitStatus = jobExecution.getExitStatus(); @@ -338,24 +318,15 @@ public class SimpleJobTests extends TestCase { final RuntimeException exception = new RuntimeException("Foo!"); step2.setProcessException(exception); - try { - job.execute(jobExecution); - fail(); - } - catch (RuntimeException e) { - assertSame(exception, e); - } + job.execute(jobExecution); + Throwable e = jobExecution.getAllFailureExceptions().get(0); + assertSame(exception, e); - try { - job.execute(jobExecution); - fail(); - } - catch (RuntimeException e) { - assertSame(exception, e); - } + job.execute(jobExecution); + e = jobExecution.getAllFailureExceptions().get(0); + assertSame(exception, e); assertTrue(step1.passedInStepContext.isEmpty()); assertFalse(step2.passedInStepContext.isEmpty()); - } public void testInterruptWithListener() throws Exception { @@ -368,13 +339,8 @@ public class SimpleJobTests extends TestCase { job.setJobExecutionListeners(new JobExecutionListener[] { listener }); - try { - job.execute(jobExecution); - fail(); - } - catch (UnexpectedJobExecutionException e) { - // expected - } + job.execute(jobExecution); + assertEquals(BatchStatus.STOPPED, jobExecution.getStatus()); verify(listener); } @@ -390,13 +356,10 @@ public class SimpleJobTests extends TestCase { final RuntimeException exception = new RuntimeException("Foo!"); step2.setProcessException(exception); - try { - job.execute(jobExecution); - fail(); - } - catch (RuntimeException e) { - assertSame(exception, e); - } + job.execute(jobExecution); + assertEquals(1, jobExecution.getAllFailureExceptions().size()); + Throwable e = jobExecution.getAllFailureExceptions().get(0); + assertSame(exception, e); assertTrue(step1.passedInJobContext.isEmpty()); assertFalse(step2.passedInJobContext.isEmpty()); @@ -405,13 +368,10 @@ public class SimpleJobTests extends TestCase { jobExecution = jobRepository.createJobExecution(job.getName(), jobParameters); - try { - job.execute(jobExecution); - fail(); - } - catch (RuntimeException e) { - assertSame(exception, e); - } + job.execute(jobExecution); + assertEquals(1, jobExecution.getAllFailureExceptions().size()); + e = jobExecution.getAllFailureExceptions().get(0); + assertSame(exception, e); assertFalse(step1.passedInJobContext.isEmpty()); assertFalse(step2.passedInJobContext.isEmpty()); } @@ -428,16 +388,11 @@ public class SimpleJobTests extends TestCase { }; job.setSteps(Arrays.asList(new Step[] { step1, step2 })); - - try { - job.execute(jobExecution); - fail(); - } - catch (UnexpectedJobExecutionException expected) { - assertTrue(expected.getCause() instanceof JobInterruptedException); - assertEquals("JobExecution interrupted.", expected.getCause().getMessage()); - } - + job.execute(jobExecution); + Throwable expected = jobExecution.getAllFailureExceptions().get(0); + assertTrue(expected instanceof JobInterruptedException); + assertEquals("JobExecution interrupted.", expected.getMessage()); + assertNull("Second step was not executed", step2.passedInStepContext); } @@ -509,17 +464,20 @@ public class SimpleJobTests extends TestCase { if (exception instanceof RuntimeException) { stepExecution.setExitStatus(ExitStatus.FAILED); stepExecution.setStatus(BatchStatus.FAILED); - throw (RuntimeException) exception; + stepExecution.addFailureException(exception); + return; } if (exception instanceof Error) { stepExecution.setExitStatus(ExitStatus.FAILED); stepExecution.setStatus(BatchStatus.FAILED); - throw (Error) exception; + stepExecution.addFailureException(exception); + return; } if (exception instanceof JobInterruptedException) { stepExecution.setExitStatus(ExitStatus.FAILED); - stepExecution.setStatus(BatchStatus.FAILED); - throw (JobInterruptedException) exception; + stepExecution.setStatus(BatchStatus.STOPPED); + stepExecution.addFailureException(exception); + return; } if (runnable != null) { runnable.run(); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java index 3da6e237a..f7b090401 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java @@ -151,14 +151,9 @@ public class SimpleStepFactoryBeanTests { job.setSteps(Collections.singletonList(step)); JobExecution jobExecution = repository.createJobExecution(job.getName(), new JobParameters()); - try { - job.execute(jobExecution); - fail("Expected RuntimeException"); - } - catch (RuntimeException e) { - // expected - assertEquals("Error!", e.getMessage()); - } + + job.execute(jobExecution); + assertEquals("Error!", jobExecution.getAllFailureExceptions().get(0).getMessage()); assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); assertEquals(0, written.size()); @@ -180,14 +175,9 @@ public class SimpleStepFactoryBeanTests { job.setSteps(Collections.singletonList((Step) step)); JobExecution jobExecution = repository.createJobExecution(job.getName(), new JobParameters()); - try { - job.execute(jobExecution); - fail("Expected RuntimeException"); - } - catch (RuntimeException e) { - assertEquals("Foo", e.getMessage()); - // expected - } + + job.execute(jobExecution); + assertEquals("Foo", jobExecution.getAllFailureExceptions().get(0).getMessage()); assertEquals(BatchStatus.FAILED, jobExecution.getStatus()); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SkipLimitStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SkipLimitStepFactoryBeanTests.java index 145ad4c66..12e50864c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SkipLimitStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SkipLimitStepFactoryBeanTests.java @@ -3,7 +3,6 @@ package org.springframework.batch.core.step.item; 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.util.ArrayList; import java.util.Arrays; @@ -16,6 +15,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Before; import org.junit.Test; +import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobInstance; import org.springframework.batch.core.JobParameters; @@ -24,8 +24,6 @@ import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.StepListener; import org.springframework.batch.core.listener.SkipListenerSupport; import org.springframework.batch.core.step.JobRepositorySupport; -import org.springframework.batch.core.step.skip.SkipLimitExceededException; -import org.springframework.batch.core.step.skip.SkipListenerFailedException; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemWriter; @@ -148,13 +146,8 @@ public class SkipLimitStepFactoryBeanTests { Step step = (Step) factory.getObject(); StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail(); - } - catch (FatalRuntimeException expected) { - assertTrue(expected.getMessage().equals("Ouch!")); - } + step.execute(stepExecution); + assertTrue(stepExecution.getFailureExceptions().get(0).getMessage().equals("Ouch!")); } /** @@ -169,12 +162,7 @@ public class SkipLimitStepFactoryBeanTests { StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected SkipLimitExceededException."); - } - catch (SkipLimitExceededException e) { - } + step.execute(stepExecution); assertEquals(1, stepExecution.getSkipCount()); @@ -205,13 +193,9 @@ public class SkipLimitStepFactoryBeanTests { StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected SkipLimitExceededException."); - } - catch (SkipLimitExceededException e) { - } - + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + assertEquals(3, stepExecution.getSkipCount()); assertEquals(2, stepExecution.getReadSkipCount()); assertEquals(1, stepExecution.getWriteSkipCount()); @@ -249,13 +233,9 @@ public class SkipLimitStepFactoryBeanTests { StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected SkipListenerFailedException."); - } - catch (SkipListenerFailedException e) { - assertEquals("oops", e.getCause().getMessage()); - } + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + assertEquals("oops", stepExecution.getFailureExceptions().get(0).getCause().getMessage()); assertEquals(1, stepExecution.getSkipCount()); assertEquals(1, stepExecution.getReadSkipCount()); @@ -286,14 +266,9 @@ public class SkipLimitStepFactoryBeanTests { StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected SkipListenerFailedException."); - } - catch (SkipListenerFailedException e) { - assertEquals("oops", e.getCause().getMessage()); - } - + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + assertEquals("oops", stepExecution.getFailureExceptions().get(0).getCause().getMessage()); assertEquals(3, stepExecution.getSkipCount()); assertEquals(2, stepExecution.getReadSkipCount()); assertEquals(1, stepExecution.getWriteSkipCount()); @@ -406,13 +381,8 @@ public class SkipLimitStepFactoryBeanTests { StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected SkipLimitExceededException."); - } - catch (SkipLimitExceededException e) { - } - + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); assertEquals("bad skip count", 3, stepExecution.getSkipCount()); assertEquals("bad read skip count", 2, stepExecution.getReadSkipCount()); assertEquals("bad write skip count", 1, stepExecution.getWriteSkipCount()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/StatefulRetryStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/StatefulRetryStepFactoryBeanTests.java index 089a423ff..0cad5196b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/StatefulRetryStepFactoryBeanTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/StatefulRetryStepFactoryBeanTests.java @@ -29,6 +29,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Before; import org.junit.Test; +import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.JobExecution; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.JobParametersBuilder; @@ -361,14 +362,9 @@ public class StatefulRetryStepFactoryBeanTests { Step step = (Step) factory.getObject(); StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected SkipLimitExceededException"); - } - catch (SkipLimitExceededException e) { - // expected - } - + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + List expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("")); assertEquals(expectedOutput, written); @@ -418,15 +414,9 @@ public class StatefulRetryStepFactoryBeanTests { Step step = (Step) factory.getObject(); StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected RuntimeException"); - } - catch (RuntimeException e) { - // expected - String message = e.getMessage(); - assertTrue("Wrong message: " + message, message.contains("Write error - planned but not skippable.")); - } + step.execute(stepExecution); + String message = stepExecution.getFailureExceptions().get(0).getMessage(); + assertTrue("Wrong message: " + message, message.contains("Write error - planned but not skippable.")); List expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("")); assertEquals(expectedOutput, written); @@ -466,14 +456,9 @@ public class StatefulRetryStepFactoryBeanTests { AbstractStep step = (AbstractStep) factory.getObject(); StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected SkipLimitExceededException"); - } - catch (SkipLimitExceededException e) { - // expected - } - + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + List expectedOutput = Arrays.asList(StringUtils.commaDelimitedListToStringArray("")); assertEquals(expectedOutput, written); @@ -519,14 +504,9 @@ public class StatefulRetryStepFactoryBeanTests { AbstractStep step = (AbstractStep) factory.getObject(); StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected RetryCacheCapacityExceededException"); - } - catch (RetryCacheCapacityExceededException e) { - // expected - } - + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + // We added a bogus cache so no items are actually skipped // because they aren't recognised as eligible assertEquals(0, stepExecution.getSkipCount()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java new file mode 100644 index 000000000..88678e050 --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/TaskletStepExceptionTests.java @@ -0,0 +1,265 @@ +/** + * + */ +package org.springframework.batch.core.step.item; + +import static org.junit.Assert.*; +import static org.springframework.batch.core.BatchStatus.*; + +import org.junit.Before; +import org.junit.Test; +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.listener.StepExecutionListenerSupport; +import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException; +import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException; +import org.springframework.batch.core.repository.JobRepository; +import org.springframework.batch.core.repository.JobRestartException; +import org.springframework.batch.core.step.tasklet.Tasklet; +import org.springframework.batch.core.step.tasklet.TaskletStep; +import org.springframework.batch.item.ExecutionContext; +import org.springframework.batch.item.ItemStream; +import org.springframework.batch.item.ItemStreamException; +import org.springframework.batch.item.ItemStreamSupport; +import org.springframework.batch.repeat.ExitStatus; +import org.springframework.batch.support.transaction.ResourcelessTransactionManager; +import org.springframework.core.AttributeAccessor; +import org.springframework.transaction.TransactionException; +import org.springframework.transaction.support.DefaultTransactionStatus; + +/** + * Tests for the behavior of TaskletStep in a failure scenario. + * + * @author Lucas Ward + * + */ +public class TaskletStepExceptionTests { + + TaskletStep taskletStep; + StepExecution stepExecution; + UpdateCountingJobRepository jobRepository; + static RuntimeException taskletException = new RuntimeException(); + static JobInterruptedException interruptedException = new JobInterruptedException(""); + + @Before + public void init(){ + taskletStep = new TaskletStep(); + taskletStep.setTasklet(new ExceptionTasklet()); + jobRepository = new UpdateCountingJobRepository(); + taskletStep.setJobRepository(jobRepository); + taskletStep.setTransactionManager(new ResourcelessTransactionManager()); + + JobInstance jobInstance = new JobInstance(1L, new JobParameters(), "testJob" ); + JobExecution jobExecution = new JobExecution(jobInstance); + stepExecution = new StepExecution("testStep", jobExecution); + } + + @Test + public void testApplicationException() throws Exception{ + + taskletStep.execute(stepExecution); + assertEquals(FAILED, stepExecution.getStatus()); + } + + @Test + public void testInterrupted() throws Exception{ + taskletStep.setStepExecutionListeners(new StepExecutionListener[]{new InterruptionListener()}); + taskletStep.execute(stepExecution); + assertEquals(STOPPED, stepExecution.getStatus()); + } + + @Test + public void testOpenFailure() throws Exception{ + final RuntimeException exception = new RuntimeException(); + taskletStep.setStreams(new ItemStream[]{new ItemStreamSupport(){ + @Override + public void open(ExecutionContext executionContext) + throws ItemStreamException { + throw exception; + } + }}); + + taskletStep.execute(stepExecution); + assertEquals(FAILED, stepExecution.getStatus()); + assertTrue(stepExecution.getFailureExceptions().contains(exception)); + assertEquals(1,jobRepository.getUpdateCount()); + } + + @Test + public void testBeforeStepFailure() throws Exception{ + + final RuntimeException exception = new RuntimeException(); + taskletStep.setStepExecutionListeners(new StepExecutionListenerSupport[]{new StepExecutionListenerSupport(){ + @Override + public void beforeStep(StepExecution stepExecution) { + throw exception; + } + }}); + taskletStep.execute(stepExecution); + assertEquals(FAILED, stepExecution.getStatus()); + assertTrue(stepExecution.getFailureExceptions().contains(exception)); + assertEquals(1,jobRepository.getUpdateCount()); + } + + @Test + public void testAfterStepFailure() throws Exception{ + + final RuntimeException exception = new RuntimeException(); + taskletStep.setStepExecutionListeners(new StepExecutionListenerSupport[]{new StepExecutionListenerSupport(){ + @Override + public ExitStatus afterStep(StepExecution stepExecution) { + throw exception; + } + }}); + taskletStep.setTasklet(new Tasklet(){ + + public ExitStatus execute(StepContribution contribution, + AttributeAccessor attributes) throws Exception { + return ExitStatus.FINISHED; + } + + }); + taskletStep.execute(stepExecution); + assertEquals(FAILED, stepExecution.getStatus()); + assertTrue(stepExecution.getFailureExceptions().contains(exception)); + assertEquals(2,jobRepository.getUpdateCount()); + } + + @Test + public void testOnErrorInStepFAilure() throws Exception{ + + final RuntimeException exception = new RuntimeException(); + taskletStep.setStepExecutionListeners(new StepExecutionListenerSupport[]{new StepExecutionListenerSupport(){ + @Override + public ExitStatus onErrorInStep(StepExecution stepExecution, + Throwable e) { + throw exception; + } + }}); + taskletStep.execute(stepExecution); + assertEquals(FAILED, stepExecution.getStatus()); + assertTrue(stepExecution.getFailureExceptions().contains(taskletException)); + assertTrue(stepExecution.getFailureExceptions().contains(exception)); + assertEquals(1,jobRepository.getUpdateCount()); + } + + @Test + public void testCloseError() throws Exception{ + + final RuntimeException exception = new RuntimeException(); + taskletStep.setStreams(new ItemStream[]{new ItemStreamSupport(){ + @Override + public void close(ExecutionContext executionContext) + throws ItemStreamException { + throw exception; + } + }}); + + taskletStep.execute(stepExecution); + assertEquals(FAILED, stepExecution.getStatus()); + assertTrue(stepExecution.getFailureExceptions().contains(taskletException)); + assertTrue(stepExecution.getFailureExceptions().contains(exception)); + assertEquals(1,jobRepository.getUpdateCount()); + } + + @Test + public void testCommitError() throws Exception{ + + final RuntimeException exception = new RuntimeException(); + taskletStep.setTransactionManager(new ResourcelessTransactionManager(){ + @Override + protected void doCommit(DefaultTransactionStatus status) + throws TransactionException { + throw exception; + } + }); + + taskletStep.setTasklet(new Tasklet(){ + + public ExitStatus execute(StepContribution contribution, + AttributeAccessor attributes) throws Exception { + return ExitStatus.FINISHED; + } + + }); + + taskletStep.execute(stepExecution); + assertEquals(UNKNOWN, stepExecution.getStatus()); + Throwable e = stepExecution.getFailureExceptions().get(0); + assertEquals(exception, e.getCause()); + } + + @Test + public void testUpdateError() throws Exception{ + + final RuntimeException exception = new RuntimeException(); + taskletStep.setJobRepository(new UpdateCountingJobRepository(){ + @Override + public void update(StepExecution arg0) { + throw exception; + } + }); + + taskletStep.execute(stepExecution); + assertEquals(UNKNOWN, stepExecution.getStatus()); + assertTrue(stepExecution.getFailureExceptions().contains(taskletException)); + assertTrue(stepExecution.getFailureExceptions().contains(exception)); + } + + private static class ExceptionTasklet implements Tasklet{ + + public ExitStatus execute(StepContribution contribution, + AttributeAccessor attributes) throws Exception { + + throw taskletException; + } + } + + private static class InterruptionListener extends StepExecutionListenerSupport{ + + @Override + public void beforeStep(StepExecution stepExecution) { + stepExecution.setTerminateOnly(); + } + } + + private static class UpdateCountingJobRepository implements JobRepository{ + + private int updateCount = 0; + + public void add(StepExecution stepExecution) { } + + public JobExecution createJobExecution(String jobName, + JobParameters jobParameters) + throws JobExecutionAlreadyRunningException, + JobRestartException, JobInstanceAlreadyCompleteException { + return null;} + + public StepExecution getLastStepExecution(JobInstance jobInstance, + String stepName) {return null;} + + public int getStepExecutionCount(JobInstance jobInstance, + String stepName) {return 0; } + + public boolean isJobInstanceExists(String jobName, + JobParameters jobParameters) {return false;} + + public void update(JobExecution jobExecution) { } + + public void update(StepExecution stepExecution) { + updateCount++; + } + + public void updateExecutionContext(StepExecution stepExecution) { } + + public int getUpdateCount() { + return updateCount; + } + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ChunkOrientedStepIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ChunkOrientedStepIntegrationTests.java index b4047a9b6..b24e75f8a 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ChunkOrientedStepIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/ChunkOrientedStepIntegrationTests.java @@ -18,7 +18,6 @@ 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.util.Arrays; import java.util.List; @@ -39,7 +38,6 @@ 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.JobRepositoryFactoryBean; -import org.springframework.batch.core.step.tasklet.TaskletStep; import org.springframework.batch.item.ExecutionContext; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemWriter; @@ -134,27 +132,20 @@ public class ChunkOrientedStepIntegrationTests { }); // step.setLastExecution(stepExecution); - try { - step.execute(stepExecution); - fail("Expected BatchCriticalException"); - } - catch (RuntimeException e) { + step.execute(stepExecution); + assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); + StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobExecution.getJobInstance(), step.getName()); + assertEquals(lastStepExecution, stepExecution); + assertFalse(lastStepExecution == stepExecution); - assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); - StepExecution lastStepExecution = jobRepository.getLastStepExecution(jobExecution.getJobInstance(), step.getName()); - assertEquals(lastStepExecution, stepExecution); - assertFalse(lastStepExecution == stepExecution); + // If the StepExecution is not saved after the failure it will be + // STARTED instead of UNKNOWN + assertEquals(BatchStatus.UNKNOWN, lastStepExecution.getStatus()); + String msg = stepExecution.getExitStatus().getExitDescription(); + assertTrue(msg.contains("Fatal error detected during commit")); + // The original rollback was caused by this one: + assertEquals("Simulate commit failure", stepExecution.getFailureExceptions().get(0).getCause().getMessage()); - // If the StepExecution is not saved after the failure it will be - // STARTED instead of UNKNOWN - assertEquals(BatchStatus.UNKNOWN, lastStepExecution.getStatus()); - - String msg = stepExecution.getExitStatus().getExitDescription(); - assertTrue(msg.contains("Fatal error detected during commit")); - // The original rollback was caused by this one: - assertEquals("Simulate commit failure", e.getCause().getMessage()); - - } } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepExecutorInterruptionTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepExecutorInterruptionTests.java index a00227ad5..a7f8c2f65 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepExecutorInterruptionTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/StepExecutorInterruptionTests.java @@ -169,15 +169,10 @@ public class StepExecutorInterruptionTests extends TestCase { } }); - try { - step.execute(stepExecution); - fail("Expected planned RuntimeException"); - } catch (RuntimeException e) { - assertEquals("Planned!", e.getMessage()); - } - + step.execute(stepExecution); + + assertEquals("Planned!", stepExecution.getFailureExceptions().get(0).getMessage()); assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); - } /** diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java index b42a7f6ba..d7c37c42e 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/tasklet/TaskletStepTests.java @@ -36,7 +36,6 @@ import org.springframework.batch.core.JobInterruptedException; import org.springframework.batch.core.JobParameters; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.StepExecutionListener; -import org.springframework.batch.core.UnexpectedJobExecutionException; import org.springframework.batch.core.job.JobSupport; import org.springframework.batch.core.listener.StepExecutionListenerSupport; import org.springframework.batch.core.repository.JobRepository; @@ -172,16 +171,8 @@ public class TaskletStepTests { step.setJobRepository(repository); step.afterPropertiesSet(); - try { - step.execute(stepExecution); - fail(); - } - catch (Exception e) { - assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); - assertEquals("Fatal error detected during update of step execution", e.getMessage()); - assertEquals("stub exception", e.getCause().getMessage()); - } - + step.execute(stepExecution); + assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); } @Test @@ -328,14 +319,11 @@ public class TaskletStepTests { counter++; } }); - try { - step.execute(stepExecution); - fail(); - } - catch (RuntimeException e) { - assertEquals("Fatal error detected during save of step execution context", e.getMessage()); - assertEquals("foo", e.getCause().getMessage()); - } + + step.execute(stepExecution); + Throwable e = stepExecution.getFailureExceptions().get(0); + assertEquals("Fatal error detected during save of step execution context", e.getMessage()); + assertEquals("foo", e.getCause().getMessage()); assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); } @@ -497,13 +485,8 @@ public class TaskletStepTests { }, itemWriter)); JobExecution jobExecution = new JobExecution(jobInstance); StepExecution stepExecution = new StepExecution(step.getName(), jobExecution); - try { - step.execute(stepExecution); - fail("Expected RuntimeException"); - } - catch (RuntimeException e) { - assertEquals("FOO", e.getMessage()); - } + step.execute(stepExecution); + assertEquals("FOO", stepExecution.getFailureExceptions().get(0).getMessage()); assertEquals(1, list.size()); } @@ -533,7 +516,7 @@ public class TaskletStepTests { } @Test - public void testStatusForInterruptedException() { + public void testStatusForInterruptedException() throws Exception{ StepInterruptionPolicy interruptionPolicy = new StepInterruptionPolicy() { @@ -560,16 +543,11 @@ public class TaskletStepTests { stepExecution.setExecutionContext(foobarEc); - try { - step.execute(stepExecution); - fail("Expected JobInterruptedException"); - } - catch (JobInterruptedException ex) { - assertEquals(BatchStatus.STOPPED, stepExecution.getStatus()); - String msg = stepExecution.getExitStatus().getExitDescription(); - assertTrue("Message does not contain 'JobInterruptedException': " + msg, contains(msg, + step.execute(stepExecution); + assertEquals(BatchStatus.STOPPED, stepExecution.getStatus()); + String msg = stepExecution.getExitStatus().getExitDescription(); + assertTrue("Message does not contain 'JobInterruptedException': " + msg, contains(msg, "JobInterruptedException")); - } } @Test @@ -589,15 +567,10 @@ public class TaskletStepTests { stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); - try { - step.execute(stepExecution); - fail("Expected RuntimeException"); - } - catch (RuntimeException ex) { - assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); - // The original rollback was caused by this one: - assertEquals("Foo", ex.getMessage()); - } + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + // The original rollback was caused by this one: + assertEquals("Foo", stepExecution.getFailureExceptions().get(0).getMessage()); } @Test @@ -617,15 +590,10 @@ public class TaskletStepTests { stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); - try { - step.execute(stepExecution); - fail("Expected Error"); - } - catch (Error ex) { - assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); - // The original rollback was caused by this one: - assertEquals("Foo", ex.getMessage()); - } + step.execute(stepExecution); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + // The original rollback was caused by this one: + assertEquals("Foo", stepExecution.getFailureExceptions().get(0).getMessage()); } @Test @@ -651,17 +619,12 @@ public class TaskletStepTests { stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); - try { - step.execute(stepExecution); - fail("Expected UnexpectedJobExecutionException"); - } - catch (RuntimeException ex) { - assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); - String msg = stepExecution.getExitStatus().getExitDescription(); - assertTrue("Message does not contain ResetFailedException: " + msg, contains(msg, "ResetFailedException")); - // The original rollback was caused by this one: - assertEquals("Bar", ex.getCause().getMessage()); - } + step.execute(stepExecution); + assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); + String msg = stepExecution.getExitStatus().getExitDescription(); + assertTrue("Message does not contain ResetFailedException: " + msg, contains(msg, "ResetFailedException")); + // The original rollback was caused by this one: + assertEquals("Bar", stepExecution.getFailureExceptions().get(0).getCause().getMessage()); } @Test @@ -680,19 +643,15 @@ public class TaskletStepTests { stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); - try { - step.execute(stepExecution); - fail("Expected BatchCriticalException"); - } - catch (RuntimeException ex) { - assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); - String msg = stepExecution.getExitStatus().getExitDescription(); - assertTrue(msg.contains("Fatal error detected during commit")); - msg = ex.getMessage(); - assertTrue(msg.contains("Fatal error detected during commit")); - // The original rollback was caused by this one: - assertEquals("Bar", ex.getCause().getMessage()); - } + step.execute(stepExecution); + assertEquals(BatchStatus.UNKNOWN, stepExecution.getStatus()); + String msg = stepExecution.getExitStatus().getExitDescription(); + assertTrue(msg.contains("Fatal error detected during commit")); + Throwable ex = stepExecution.getFailureExceptions().get(0); + msg = ex.getMessage(); + assertTrue(msg.contains("Fatal error detected during commit")); + // The original rollback was caused by this one: + assertEquals("Bar", ex.getCause().getMessage()); } @Test @@ -708,20 +667,15 @@ public class TaskletStepTests { JobExecution jobExecutionContext = new JobExecution(jobInstance); StepExecution stepExecution = new StepExecution(step.getName(), jobExecutionContext); - try { - step.execute(stepExecution); - fail("Expected RuntimeException"); - } - catch (RuntimeException ex) { - // The job actually completed, but the streams couldn't be closed. - assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); - String msg = stepExecution.getExitStatus().getExitDescription(); - assertEquals("", msg); - msg = ex.getMessage(); - assertTrue("Message does not contain 'closing step': " + msg, contains(msg, "closing step")); - // The original rollback was caused by this one: - assertEquals("Bar", ex.getCause().getMessage()); - } + 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); + msg = ex.getMessage(); + // The original rollback was caused by this one: + assertEquals("Bar", ex.getMessage()); } @Test @@ -743,20 +697,15 @@ public class TaskletStepTests { stepExecution.setExecutionContext(foobarEc); // step.setLastExecution(stepExecution); - try { - step.execute(stepExecution); - fail("Expected InfrastructureException"); - } - catch (UnexpectedJobExecutionException ex) { + 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); - msg = ex.getMessage(); - assertTrue("Message does not contain 'closing': " + msg, contains(msg, "closing")); - // The original rollback was caused by this one: - assertEquals("Bar", ex.getCause().getMessage()); - } + assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); + String msg = stepExecution.getExitStatus().getExitDescription(); + assertEquals("", msg); + Throwable ex = stepExecution.getFailureExceptions().get(0); + msg = ex.getMessage(); + // The original rollback was caused by this one: + assertEquals("Bar", ex.getMessage()); } /** @@ -777,16 +726,12 @@ public class TaskletStepTests { StepExecution stepExecution = new StepExecution(step.getName(), new JobExecution(jobInstance)); - try { - step.execute(stepExecution); - fail("Expected InfrastructureException"); - } - catch (RuntimeException expected) { - assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); - assertEquals("CRASH!", expected.getMessage()); - assertFalse(stepExecution.getExecutionContext().isEmpty()); - assertTrue(stepExecution.getExecutionContext().getString("spam").equals("bucket")); - } + 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 @@ -820,14 +765,10 @@ public class TaskletStepTests { }; step.setStepExecutionListeners(new StepExecutionListener[] { listener }); StepExecution stepExecution = new StepExecution(step.getName(), new JobExecution(jobInstance)); - try { - step.execute(stepExecution); - fail(); - } - catch (RuntimeException expected) { - assertEquals("exception thrown in afterStep to signal failure", expected.getMessage()); - } + step.execute(stepExecution); + Throwable expected = stepExecution.getFailureExceptions().get(0); + assertEquals("exception thrown in afterStep to signal failure", expected.getMessage()); assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); }