diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java index 015a90e03..31ea7439d 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBIntegrationTestConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 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. @@ -15,8 +15,6 @@ */ package org.springframework.batch.core.repository.support; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoClients; import org.springframework.batch.core.Job; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.explore.JobExplorer; @@ -63,8 +61,7 @@ class MongoDBIntegrationTestConfiguration { @Bean public MongoDatabaseFactory mongoDatabaseFactory(@Value("${mongo.connectionString}") String connectionString) { - MongoClient mongoClient = MongoClients.create(connectionString); - return new SimpleMongoClientDatabaseFactory(mongoClient, "test"); + return new SimpleMongoClientDatabaseFactory(connectionString + "/test"); } @Bean diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java index a6ed1c9bb..f47c73199 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobExplorerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 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. @@ -30,6 +30,7 @@ import org.springframework.batch.core.explore.JobExplorer; import org.springframework.batch.core.launch.JobLauncher; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -45,6 +46,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; /** * @author Henning Pöttker */ +@DirtiesContext @Testcontainers(disabledWithoutDocker = true) @SpringJUnitConfig(MongoDBIntegrationTestConfiguration.class) public class MongoDBJobExplorerIntegrationTests { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java index b45aa7bd1..6b70f0b3c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoDBJobRepositoryIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 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. @@ -23,6 +23,7 @@ import org.bson.Document; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -43,6 +44,7 @@ import org.springframework.data.mongodb.core.MongoTemplate; /** * @author Mahmoud Ben Hassine */ +@DirtiesContext @Testcontainers(disabledWithoutDocker = true) @SpringJUnitConfig(MongoDBIntegrationTestConfiguration.class) public class MongoDBJobRepositoryIntegrationTests { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java index 7b71ca850..a04795928 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/repository/support/MongoExecutionContextDaoIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 the original author or authors. + * Copyright 2024-2025 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. @@ -36,6 +36,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -51,6 +52,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; /** * @author Henning Pöttker */ +@DirtiesContext @Testcontainers(disabledWithoutDocker = true) @SpringJUnitConfig({ MongoDBIntegrationTestConfiguration.class, ExecutionContextDaoConfiguration.class }) public class MongoExecutionContextDaoIntegrationTests { diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java index 67d24fcef..2cf59d33c 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2024 the original author or authors. + * Copyright 2010-2025 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. @@ -16,17 +16,14 @@ package org.springframework.batch.core.test.step; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; -import javax.sql.DataSource; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.JobExecution; @@ -39,8 +36,8 @@ import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemWriter; -import org.springframework.batch.item.ParseException; -import org.springframework.batch.item.UnexpectedInputException; +import org.springframework.batch.item.support.ListItemReader; +import org.springframework.batch.item.support.SynchronizedItemReader; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.lang.Nullable; @@ -48,15 +45,14 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.jdbc.JdbcTestUtils; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.util.Assert; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; /** * Tests for {@link FaultTolerantStepFactoryBean}. */ @SpringJUnitConfig(locations = "/simple-job-launcher-context.xml") -@Disabled("Randomly failing/hanging") // FIXME This test is randomly failing/hanging class FaultTolerantStepFactoryBeanIntegrationTests { private static final int MAX_COUNT = 1000; @@ -69,12 +65,8 @@ class FaultTolerantStepFactoryBeanIntegrationTests { private SkipWriterStub writer; - private JobExecution jobExecution; - - private StepExecution stepExecution; - @Autowired - private DataSource dataSource; + private JdbcTemplate jdbcTemplate; @Autowired private JobRepository repository; @@ -85,8 +77,8 @@ class FaultTolerantStepFactoryBeanIntegrationTests { @BeforeEach void setUp() { - writer = new SkipWriterStub(dataSource); - processor = new SkipProcessorStub(dataSource); + writer = new SkipWriterStub(jdbcTemplate); + processor = new SkipProcessorStub(jdbcTemplate); factory = new FaultTolerantStepFactoryBean<>(); @@ -101,14 +93,12 @@ class FaultTolerantStepFactoryBeanIntegrationTests { taskExecutor.afterPropertiesSet(); factory.setTaskExecutor(taskExecutor); - JdbcTestUtils.deleteFromTables(new JdbcTemplate(dataSource), "ERROR_LOG"); + JdbcTestUtils.deleteFromTables(jdbcTemplate, "ERROR_LOG"); } @Test - void testUpdatesNoRollback() throws Exception { - - JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + void testUpdatesNoRollback() { writer.write(Chunk.of("foo", "bar")); processor.process("spam"); @@ -121,17 +111,15 @@ class FaultTolerantStepFactoryBeanIntegrationTests { } @Test + @Timeout(value = 30, threadMode = SEPARATE_THREAD) void testMultithreadedSunnyDay() throws Throwable { - jobExecution = repository.createJobExecution("vanillaJob", new JobParameters()); + JobExecution jobExecution = repository.createJobExecution("vanillaJob", new JobParameters()); for (int i = 0; i < MAX_COUNT; i++) { - JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); - - SkipReaderStub reader = new SkipReaderStub(); - reader.clear(); - reader.setItems("1", "2", "3", "4", "5"); + ItemReader reader = new SynchronizedItemReader<>( + new ListItemReader<>(List.of("1", "2", "3", "4", "5"))); factory.setItemReader(reader); writer.clear(); factory.setItemWriter(writer); @@ -144,7 +132,7 @@ class FaultTolerantStepFactoryBeanIntegrationTests { Step step = factory.getObject(); - stepExecution = jobExecution.createStepExecution(factory.getName()); + StepExecution stepExecution = jobExecution.createStepExecution(factory.getName()); repository.add(stepExecution); step.execute(stepExecution); assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); @@ -167,48 +155,12 @@ class FaultTolerantStepFactoryBeanIntegrationTests { } - private static class SkipReaderStub implements ItemReader { - - private String[] items; - - private int counter = -1; - - public SkipReaderStub() throws Exception { - super(); - } - - public void setItems(String... items) { - Assert.isTrue(counter < 0, "Items cannot be set once reading has started"); - this.items = items; - } - - public void clear() { - counter = -1; - } - - @Nullable - @Override - public synchronized String read() throws Exception, UnexpectedInputException, ParseException { - counter++; - if (counter >= items.length) { - return null; - } - String item = items[counter]; - return item; - } - - } - private static class SkipWriterStub implements ItemWriter { - private final List written = new ArrayList<>(); - - private final Collection failures = Collections.emptySet(); - private final JdbcTemplate jdbcTemplate; - public SkipWriterStub(DataSource dataSource) { - jdbcTemplate = new JdbcTemplate(dataSource); + public SkipWriterStub(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; } public List getCommitted() { @@ -217,22 +169,13 @@ class FaultTolerantStepFactoryBeanIntegrationTests { } public void clear() { - written.clear(); JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, "ERROR_LOG", "STEP_NAME='written'"); } @Override - public void write(Chunk items) throws Exception { + public void write(Chunk items) { for (String item : items) { - written.add(item); jdbcTemplate.update("INSERT INTO ERROR_LOG (MESSAGE, STEP_NAME) VALUES (?, ?)", item, "written"); - checkFailure(item); - } - } - - private void checkFailure(String item) { - if (failures.contains(item)) { - throw new RuntimeException("Planned failure"); } } @@ -242,12 +185,10 @@ class FaultTolerantStepFactoryBeanIntegrationTests { private final Log logger = LogFactory.getLog(getClass()); - private final List processed = new ArrayList<>(); - private final JdbcTemplate jdbcTemplate; - public SkipProcessorStub(DataSource dataSource) { - jdbcTemplate = new JdbcTemplate(dataSource); + public SkipProcessorStub(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; } public List getCommitted() { @@ -256,14 +197,12 @@ class FaultTolerantStepFactoryBeanIntegrationTests { } public void clear() { - processed.clear(); JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, "ERROR_LOG", "STEP_NAME='processed'"); } @Nullable @Override - public String process(String item) throws Exception { - processed.add(item); + public String process(String item) { logger.debug("Processed item: " + item); jdbcTemplate.update("INSERT INTO ERROR_LOG (MESSAGE, STEP_NAME) VALUES (?, ?)", item, "processed"); return item; diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java index 6eb416fa0..eeaeba236 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/test/step/FaultTolerantStepFactoryBeanRollbackIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2023 the original author or authors. + * Copyright 2010-2025 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. @@ -19,16 +19,15 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; -import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; import org.springframework.batch.core.BatchStatus; import org.springframework.batch.core.JobExecution; @@ -41,8 +40,8 @@ import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemReader; import org.springframework.batch.item.ItemWriter; -import org.springframework.batch.item.ParseException; -import org.springframework.batch.item.UnexpectedInputException; +import org.springframework.batch.item.support.ListItemReader; +import org.springframework.batch.item.support.SynchronizedItemReader; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.lang.Nullable; @@ -50,9 +49,9 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.jdbc.JdbcTestUtils; import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.util.Assert; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Timeout.ThreadMode.SEPARATE_THREAD; /** * Tests for {@link FaultTolerantStepFactoryBean}. @@ -70,12 +69,8 @@ class FaultTolerantStepFactoryBeanRollbackIntegrationTests { private SkipWriterStub writer; - private JobExecution jobExecution; - - private StepExecution stepExecution; - @Autowired - private DataSource dataSource; + private JdbcTemplate jdbcTemplate; @Autowired private JobRepository repository; @@ -86,8 +81,8 @@ class FaultTolerantStepFactoryBeanRollbackIntegrationTests { @BeforeEach void setUp() { - writer = new SkipWriterStub(dataSource); - processor = new SkipProcessorStub(dataSource); + writer = new SkipWriterStub(jdbcTemplate, "1", "2", "3", "4", "5"); + processor = new SkipProcessorStub(jdbcTemplate); factory = new FaultTolerantStepFactoryBean<>(); @@ -97,14 +92,12 @@ class FaultTolerantStepFactoryBeanRollbackIntegrationTests { factory.setCommitInterval(3); factory.setSkipLimit(10); - JdbcTestUtils.deleteFromTables(new JdbcTemplate(dataSource), "ERROR_LOG"); + JdbcTestUtils.deleteFromTables(jdbcTemplate, "ERROR_LOG"); } @Test - void testUpdatesNoRollback() throws Exception { - - JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + void testUpdatesNoRollback() { writer.write(Chunk.of("foo", "bar")); processor.process("spam"); @@ -117,6 +110,7 @@ class FaultTolerantStepFactoryBeanRollbackIntegrationTests { } @Test + @Timeout(value = 30, threadMode = SEPARATE_THREAD) void testMultithreadedSkipInWriter() throws Throwable { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); @@ -126,11 +120,9 @@ class FaultTolerantStepFactoryBeanRollbackIntegrationTests { taskExecutor.afterPropertiesSet(); factory.setTaskExecutor(taskExecutor); - @SuppressWarnings("unchecked") - Map, Boolean> skippable = getExceptionMap(Exception.class); - factory.setSkippableExceptionClasses(skippable); + factory.setSkippableExceptionClasses(Map.of(Exception.class, true)); - jobExecution = repository.createJobExecution("skipJob", new JobParameters()); + JobExecution jobExecution = repository.createJobExecution("skipJob", new JobParameters()); for (int i = 0; i < MAX_COUNT; i++) { @@ -138,25 +130,21 @@ class FaultTolerantStepFactoryBeanRollbackIntegrationTests { logger.info("Starting step: " + i); } - JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); assertEquals(0, JdbcTestUtils.countRowsInTable(jdbcTemplate, "ERROR_LOG")); try { - SkipReaderStub reader = new SkipReaderStub(); - reader.clear(); - reader.setItems("1", "2", "3", "4", "5"); + ItemReader reader = new SynchronizedItemReader<>( + new ListItemReader<>(List.of("1", "2", "3", "4", "5"))); factory.setItemReader(reader); writer.clear(); factory.setItemWriter(writer); processor.clear(); factory.setItemProcessor(processor); - writer.setFailures("1", "2", "3", "4", "5"); - Step step = factory.getObject(); - stepExecution = jobExecution.createStepExecution(factory.getName()); + StepExecution stepExecution = jobExecution.createStepExecution(factory.getName()); repository.add(stepExecution); step.execute(stepExecution); assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); @@ -178,61 +166,15 @@ class FaultTolerantStepFactoryBeanRollbackIntegrationTests { } - @SuppressWarnings("unchecked") - private Map, Boolean> getExceptionMap(Class... args) { - Map, Boolean> map = new HashMap<>(); - for (Class arg : args) { - map.put(arg, true); - } - return map; - } - - private static class SkipReaderStub implements ItemReader { - - private String[] items; - - private int counter = -1; - - public SkipReaderStub() throws Exception { - super(); - } - - public void setItems(String... items) { - Assert.isTrue(counter < 0, "Items cannot be set once reading has started"); - this.items = items; - } - - public void clear() { - counter = -1; - } - - @Nullable - @Override - public synchronized String read() throws Exception, UnexpectedInputException, ParseException { - counter++; - if (counter >= items.length) { - return null; - } - String item = items[counter]; - return item; - } - - } - private static class SkipWriterStub implements ItemWriter { - private final List written = new CopyOnWriteArrayList<>(); - - private Collection failures = Collections.emptySet(); + private final Collection failures; private final JdbcTemplate jdbcTemplate; - public SkipWriterStub(DataSource dataSource) { - jdbcTemplate = new JdbcTemplate(dataSource); - } - - public void setFailures(String... failures) { + public SkipWriterStub(JdbcTemplate jdbcTemplate, String... failures) { this.failures = Arrays.asList(failures); + this.jdbcTemplate = jdbcTemplate; } public List getCommitted() { @@ -241,14 +183,12 @@ class FaultTolerantStepFactoryBeanRollbackIntegrationTests { } public void clear() { - written.clear(); JdbcTestUtils.deleteFromTableWhere(jdbcTemplate, "ERROR_LOG", "STEP_NAME='written'"); } @Override - public void write(Chunk items) throws Exception { + public void write(Chunk items) { for (String item : items) { - written.add(item); jdbcTemplate.update("INSERT INTO ERROR_LOG (MESSAGE, STEP_NAME) VALUES (?, ?)", item, "written"); checkFailure(item); } @@ -270,8 +210,8 @@ class FaultTolerantStepFactoryBeanRollbackIntegrationTests { private final JdbcTemplate jdbcTemplate; - public SkipProcessorStub(DataSource dataSource) { - jdbcTemplate = new JdbcTemplate(dataSource); + public SkipProcessorStub(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; } /** @@ -293,7 +233,7 @@ class FaultTolerantStepFactoryBeanRollbackIntegrationTests { @Nullable @Override - public String process(String item) throws Exception { + public String process(String item) { processed.add(item); logger.debug("Processed item: " + item); jdbcTemplate.update("INSERT INTO ERROR_LOG (MESSAGE, STEP_NAME) VALUES (?, ?)", item, "processed");