OPEN - issue BATCH-1407: Integration tests for core (including multi-threaded long running tests)

This commit is contained in:
dsyer
2009-12-21 14:23:44 +00:00
parent 159a29216e
commit 644efc30a0
21 changed files with 648 additions and 546 deletions

View File

@@ -0,0 +1,240 @@
/*
* Copyright 2006-2008 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.test.repository;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.simple.SimpleJdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/simple-job-launcher-context.xml" })
public class JdbcJobRepositoryTests {
private JobRepository repository;
private JobSupport job;
private Set<Long> jobExecutionIds = new HashSet<Long>();
private Set<Long> jobIds = new HashSet<Long>();
private List<Serializable> list = new ArrayList<Serializable>();
private SimpleJdbcTemplate simpleJdbcTemplate;
private PlatformTransactionManager transactionManager;
/** Logger */
private final Log logger = LogFactory.getLog(getClass());
@Autowired
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
@Autowired
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Autowired
public void setRepository(JobRepository repository) {
this.repository = repository;
}
@BeforeTransaction
public void onSetUpInTransaction() throws Exception {
job = new JobSupport("test-job");
job.setRestartable(true);
simpleJdbcTemplate.update("DELETE FROM BATCH_STEP_EXECUTION_CONTEXT");
simpleJdbcTemplate.update("DELETE FROM BATCH_JOB_EXECUTION_CONTEXT");
simpleJdbcTemplate.update("DELETE FROM BATCH_STEP_EXECUTION");
simpleJdbcTemplate.update("DELETE FROM BATCH_JOB_EXECUTION");
simpleJdbcTemplate.update("DELETE FROM BATCH_JOB_PARAMS");
simpleJdbcTemplate.update("DELETE FROM BATCH_JOB_INSTANCE");
}
@AfterTransaction
public void onTearDownAfterTransaction() throws Exception {
for (Long id : jobExecutionIds) {
simpleJdbcTemplate.update("DELETE FROM BATCH_JOB_EXECUTION_CONTEXT where JOB_EXECUTION_ID=?", id);
simpleJdbcTemplate.update("DELETE FROM BATCH_JOB_EXECUTION where JOB_EXECUTION_ID=?", id);
}
for (Long id : jobIds) {
simpleJdbcTemplate.update("DELETE FROM BATCH_JOB_INSTANCE where JOB_INSTANCE_ID=?", id);
}
for (Long id : jobIds) {
int count = simpleJdbcTemplate.queryForInt(
"SELECT COUNT(*) FROM BATCH_JOB_INSTANCE where JOB_INSTANCE_ID=?", id);
assertEquals(0, count);
}
}
@Transactional
@Test
public void testFindOrCreateJob() throws Exception {
job.setName("foo");
int before = 0;
JobExecution execution = repository.createJobExecution(job.getName(), new JobParameters());
int after = simpleJdbcTemplate.queryForInt("SELECT COUNT(*) FROM BATCH_JOB_INSTANCE");
assertEquals(before + 1, after);
assertNotNull(execution.getId());
}
@Transactional
@Test
public void testFindOrCreateJobConcurrently() throws Exception {
job.setName("bar");
int before = 0;
assertEquals(0, before);
long t0 = System.currentTimeMillis();
try {
doConcurrentStart();
fail("Expected JobExecutionAlreadyRunningException");
}
catch (JobExecutionAlreadyRunningException e) {
// expected
}
long t1 = System.currentTimeMillis();
JobExecution execution = (JobExecution) list.get(0);
assertNotNull(execution);
int after = simpleJdbcTemplate.queryForInt("SELECT COUNT(*) FROM BATCH_JOB_INSTANCE");
assertNotNull(execution.getId());
assertEquals(before + 1, after);
logger.info("Duration: " + (t1 - t0)
+ " - the second transaction did not block if this number is less than about 1000.");
}
@Test
public void testFindOrCreateJobConcurrentlyWhenJobAlreadyExists() throws Exception {
job = new JobSupport("test-job");
job.setRestartable(true);
job.setName("spam");
JobExecution execution = repository.createJobExecution(job.getName(), new JobParameters());
cacheJobIds(execution);
execution.setEndTime(new Timestamp(System.currentTimeMillis()));
repository.update(execution);
execution.setStatus(BatchStatus.FAILED);
int before = simpleJdbcTemplate.queryForInt("SELECT COUNT(*) FROM BATCH_JOB_INSTANCE");
assertEquals(1, before);
long t0 = System.currentTimeMillis();
try {
doConcurrentStart();
fail("Expected JobExecutionAlreadyRunningException");
}
catch (JobExecutionAlreadyRunningException e) {
// expected
}
long t1 = System.currentTimeMillis();
int after = simpleJdbcTemplate.queryForInt("SELECT COUNT(*) FROM BATCH_JOB_INSTANCE");
assertNotNull(execution.getId());
assertEquals(before, after);
logger.info("Duration: " + (t1 - t0)
+ " - the second transaction did not block if this number is less than about 1000.");
}
private void cacheJobIds(JobExecution execution) {
if (execution == null)
return;
jobExecutionIds.add(execution.getId());
jobIds.add(execution.getJobId());
}
private JobExecution doConcurrentStart() throws Exception {
new Thread(new Runnable() {
public void run() {
try {
new TransactionTemplate(transactionManager).execute(new TransactionCallback() {
public Object doInTransaction(org.springframework.transaction.TransactionStatus status) {
try {
JobExecution execution = repository.createJobExecution(job.getName(),
new JobParameters());
cacheJobIds(execution);
list.add(execution);
Thread.sleep(1000);
}
catch (Exception e) {
list.add(e);
}
return null;
}
});
}
catch (RuntimeException e) {
list.add(e);
}
}
}).start();
Thread.sleep(400);
JobExecution execution = repository.createJobExecution(job.getName(), new JobParameters());
cacheJobIds(execution);
int count = 0;
while (list.size() == 0 && count++ < 100) {
Thread.sleep(200);
}
assertEquals("Timed out waiting for JobExecution to be created", 1, list.size());
assertTrue("JobExecution not created in thread", list.get(0) instanceof JobExecution);
return (JobExecution) list.get(0);
}
}

View File

@@ -0,0 +1,152 @@
/*
* Copyright 2006-2007 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.test.repository;
import java.util.ArrayList;
import java.util.List;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParametersIncrementer;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.util.ClassUtils;
/**
* Batch domain object representing a job. Job is an explicit abstraction
* representing the configuration of a job specified by a developer. It should
* be noted that restart policy is applied to the job as a whole and not to a
* step.
*
* @author Lucas Ward
* @author Dave Syer
*/
public class JobSupport implements BeanNameAware, Job {
private List<Step> steps = new ArrayList<Step>();
private String name;
private boolean restartable = false;
private int startLimit = Integer.MAX_VALUE;
/**
* Default constructor.
*/
public JobSupport() {
super();
}
/**
* Convenience constructor to immediately add name (which is mandatory but
* not final).
*
* @param name the name
*/
public JobSupport(String name) {
super();
this.name = name;
}
/**
* Set the name property if it is not already set. Because of the order of
* the callbacks in a Spring container the name property will be set first
* if it is present. Care is needed with bean definition inheritance - if a
* parent bean has a name, then its children need an explicit name as well,
* otherwise they will not be unique.
*
* @see org.springframework.beans.factory.BeanNameAware#setBeanName(java.lang.String)
*/
public void setBeanName(String name) {
if (this.name == null) {
this.name = name;
}
}
/**
* Set the name property. Always overrides the default value if this object
* is a Spring bean.
*
* @see #setBeanName(java.lang.String)
* @param name the name
*/
public void setName(String name) {
this.name = name;
}
/* (non-Javadoc)
* @see org.springframework.batch.core.domain.IJob#getName()
*/
public String getName() {
return name;
}
public void setSteps(List<Step> steps) {
this.steps.clear();
this.steps.addAll(steps);
}
public void addStep(Step step) {
this.steps.add(step);
}
public List<Step> getSteps() {
return steps;
}
/* (non-Javadoc)
* @see org.springframework.batch.core.domain.IJob#getStartLimit()
*/
public int getStartLimit() {
return startLimit;
}
public void setStartLimit(int startLimit) {
this.startLimit = startLimit;
}
public void setRestartable(boolean restartable) {
this.restartable = restartable;
}
/* (non-Javadoc)
* @see org.springframework.batch.core.domain.IJob#isRestartable()
*/
public boolean isRestartable() {
return restartable;
}
/* (non-Javadoc)
* @see org.springframework.batch.core.Job#getJobParametersIncrementer()
*/
public JobParametersIncrementer getJobParametersIncrementer() {
return null;
}
/* (non-Javadoc)
* @see org.springframework.batch.core.domain.Job#run(org.springframework.batch.core.domain.JobExecution)
*/
public void execute(JobExecution execution) throws UnexpectedJobExecutionException {
throw new UnsupportedOperationException("JobSupport does not provide an implementation of run(). Use a smarter subclass.");
}
public String toString() {
return ClassUtils.getShortName(getClass()) + ": [name=" + name + "]";
}
}

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<import resource="simple-job-launcher-context.xml" />
<bean class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="spring:service=batch,bean=jobOperator">
<bean class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="jobOperator" />
<property name="interceptorNames" value="exceptionTranslator" />
</bean>
</entry>
<entry key="spring:service=batch,bean=jobLoader" value-ref="loader" />
</map>
</property>
<property name="assembler">
<bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler">
<property name="interfaceMappings">
<map>
<entry key="spring:service=batch,bean=jobOperator" value="org.springframework.batch.core.launch.JobOperator" />
<entry key="spring:service=batch,bean=jobLoader" value="org.springframework.batch.core.test.launch.JobLoader" />
</map>
</property>
</bean>
</property>
</bean>
<bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry" />
<bean id="jobOperator" class="org.springframework.batch.core.launch.support.SimpleJobOperator">
<property name="jobExplorer">
<bean class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
</property>
<property name="jobRepository" ref="jobRepository" />
<property name="jobRegistry" ref="jobRegistry" />
<property name="jobLauncher">
<bean parent="jobLauncher">
<property name="taskExecutor">
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor" />
</property>
</bean>
</property>
</bean>
<bean id="exceptionTranslator" class="org.springframework.batch.core.launch.support.RuntimeExceptionTranslator" />
<bean id="loader" class="org.springframework.batch.core.test.launch.DefaultJobLoader">
<property name="registry" ref="jobRegistry" />
</bean>
</beans>

View File

@@ -0,0 +1,18 @@
# Placeholders batch.*
# for Derby:
batch.jdbc.driver=org.apache.derby.jdbc.EmbeddedDriver
batch.jdbc.url=jdbc:derby:derby-home/test;create=true
batch.jdbc.user=sa
batch.jdbc.password=
batch.schema=
batch.jndi.name=
batch.naming.factory.initial=
batch.naming.provider.url=
batch.schema.script=schema-derby.sql
batch.business.schema.script=business-schema-derby.sql
batch.database.incrementer.class=org.springframework.jdbc.support.incrementer.DerbyMaxValueIncrementer
batch.lob.handler.class=org.springframework.jdbc.support.lob.DefaultLobHandler
# Bean Properties for override
# when not using sequences:
incrementerParent.columnName=ID

View File

@@ -0,0 +1,17 @@
# Placeholders batch.*
# for HSQLDB:
batch.jdbc.driver=org.hsqldb.jdbcDriver
batch.jdbc.url=jdbc:hsqldb:mem:testdb;sql.enforce_strict_size=true
# use this one for a separate server process so you can inspect the results
# (or add it to system properties with -D to override at run time).
# batch.jdbc.url=jdbc:hsqldb:hsql://localhost:9005/samples
batch.jdbc.user=sa
batch.jdbc.password=
batch.jdbc.testWhileIdle=false
batch.jdbc.validationQuery=
batch.schema.script=classpath:/org/springframework/batch/core/schema-hsqldb.sql
batch.business.schema.script=classpath:/business-schema-hsqldb.sql
batch.data.source.init=true
batch.database.incrementer.class=org.springframework.jdbc.support.incrementer.HsqlMaxValueIncrementer
batch.database.incrementer.parent=columnIncrementerParent
batch.grid.size=2

View File

@@ -0,0 +1,19 @@
# Placeholders batch.*
# for MySQL:
batch.jdbc.driver=com.mysql.jdbc.Driver
batch.jdbc.url=jdbc:mysql://localhost/test
batch.jdbc.user=root
batch.jdbc.password=root
batch.schema=
batch.jndi.name=
batch.naming.factory.initial=
batch.naming.provider.url=
batch.schema.script=schema-mysql.sql
batch.drop.script=schema-drop-mysql.sql
batch.business.schema.script=business-schema-mysql.sql
batch.database.incrementer.class=org.springframework.jdbc.support.incrementer.MySQLMaxValueIncrementer
batch.lob.handler.class=org.springframework.jdbc.support.lob.DefaultLobHandler
# Bean Properties for override
# when not using sequences:
incrementerParent.columnName=ID

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd">
<!-- Initialise the database before every test case: -->
<bean id="dataSourceInitializer" class="test.jdbc.datasource.DataSourceInitializer">
<property name="dataSource" ref="dataSource"/>
<property name="initialize" value="${batch.data.source.init}"/>
<property name="initScripts">
<list>
<value>${batch.drop.script}</value>
<value>${batch.schema.script}</value>
<value>${batch.business.schema.script}</value>
</list>
</property>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${batch.jdbc.driver}" />
<property name="url" value="${batch.jdbc.url}" />
<property name="username" value="${batch.jdbc.user}" />
<property name="password" value="${batch.jdbc.password}" />
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" lazy-init="true">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- Set up or detect a System property called "org.springframework.batch.support.SystemPropertyInitializer.ENVIRONMENT" used to construct a properties file on the classpath. The default is "hsql". -->
<bean id="environment"
class="org.springframework.batch.support.SystemPropertyInitializer">
<property name="defaultValue" value="hsql"/>
</bean>
<!-- Use this to set additional properties on beans at run time -->
<bean id="overrideProperties" class="org.springframework.beans.factory.config.PropertyOverrideConfigurer"
depends-on="environment">
<property name="location" value="classpath:batch-${org.springframework.batch.support.SystemPropertyInitializer.ENVIRONMENT}.properties" />
<!-- Allow system properties (-D) to override those from file -->
<property name="localOverride" value="true" />
<property name="properties">
<bean class="java.lang.System" factory-method="getProperties" />
</property>
<property name="ignoreInvalidKeys" value="true" />
<property name="order" value="2" />
</bean>
<bean id="placeholderProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
depends-on="environment">
<property name="location" value="classpath:batch-${org.springframework.batch.support.SystemPropertyInitializer.ENVIRONMENT}.properties" />
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="order" value="1" />
</bean>
<bean id="sequenceIncrementerParent" class="${batch.database.incrementer.class}" abstract="true">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="columnIncrementerParent" class="${batch.database.incrementer.class}" abstract="true" parent="sequenceIncrementerParent">
<property name="columnName" value="ID" />
</bean>
<bean id="incrementerParent" parent="${batch.database.incrementer.parent}">
<property name="incrementerName" value="DUMMY" />
</bean>
<!--import resource="alt-data-source-context.xml" /-->
</beans>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch-2.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<!-- The tasklet used in this job will run in an infinite loop. This is useful for testing graceful shutdown from
multiple environments. -->
<job id="loopJob" incrementer="jobParametersIncrementer" xmlns="http://www.springframework.org/schema/batch">
<step id="step1">
<tasklet>
<chunk reader="reader" writer="writer" commit-interval="3"/>
</tasklet>
</step>
</job>
<bean id="reader" class="org.springframework.batch.core.test.launch.InfiniteLoopReader" />
<bean id="writer" class="org.springframework.batch.core.test.launch.InfiniteLoopWriter" />
<bean id="jobParametersIncrementer"
class="org.springframework.batch.core.launch.support.RunIdIncrementer"/>
</beans>

View File

@@ -0,0 +1,12 @@
log4j.rootCategory=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{1}:%L - %m%n
log4j.category.org.apache.activemq=ERROR
# log4j.category.org.springframework=DEBUG
log4j.category.org.springframework.jdbc=DEBUG
log4j.category.org.springframework.jms=DEBUG
log4j.category.org.springframework.batch=DEBUG
log4j.category.org.springframework.retry=DEBUG

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
<import resource="data-source-context.xml" />
<bean id="jobLauncher"
class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
<property name="jobRepository" ref="jobRepository" />
</bean>
<bean class="org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor">
<property name="jobRegistry" ref="jobRegistry"/>
</bean>
<bean id="jobRepository"
class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean"
p:dataSource-ref="dataSource" p:transactionManager-ref="transactionManager" />
<bean id="mapJobRepository"
class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"
lazy-init="true" autowire-candidate="false" />
<bean id="jobOperator"
class="org.springframework.batch.core.launch.support.SimpleJobOperator"
p:jobLauncher-ref="jobLauncher" p:jobExplorer-ref="jobExplorer"
p:jobRepository-ref="jobRepository" p:jobRegistry-ref="jobRegistry" />
<bean id="jobExplorer"
class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean"
p:dataSource-ref="dataSource" />
<bean id="jobRegistry"
class="org.springframework.batch.core.configuration.support.MapJobRegistry" />
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>