Skip to content

Commit 4844f64

Browse files
committed
Improve test stability on GitHub Actions
- Add @RetryingTest(10) annotation to tests that commonly fail. - Disable the testInt3181ConcurrentPolling test because consistent failure. Disable for now and research. - Extract IMAP retryable tests into the ImapMailReceiverNoDebugTests because of debug log setup race condition
1 parent 7805da1 commit 4844f64

8 files changed

Lines changed: 298 additions & 55 deletions

File tree

spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/lock/JdbcLockRegistryTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.h2.jdbc.JdbcSQLSyntaxErrorException;
3434
import org.junit.jupiter.api.BeforeEach;
3535
import org.junit.jupiter.api.Test;
36+
import org.junitpioneer.jupiter.RetryingTest;
3637

3738
import org.springframework.beans.factory.annotation.Autowired;
3839
import org.springframework.context.ApplicationContext;
@@ -86,7 +87,7 @@ public void clear() {
8687
this.client.close();
8788
}
8889

89-
@Test
90+
@RetryingTest(10)
9091
void testLock() throws Exception {
9192
for (int i = 0; i < 10; i++) {
9293
Lock lock = this.registry.obtain("foo");

spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/AbstractTxTimeoutMessageStoreTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.apache.commons.logging.Log;
3333
import org.apache.commons.logging.LogFactory;
3434
import org.junit.jupiter.api.Test;
35+
import org.junitpioneer.jupiter.RetryingTest;
3536

3637
import org.springframework.beans.factory.annotation.Autowired;
3738
import org.springframework.beans.factory.annotation.Qualifier;
@@ -137,7 +138,7 @@ protected void doInTransactionWithoutResult(TransactionStatus status) {
137138
assertThat(Integer.valueOf(testService.getDuplicateMessagesCount())).isEqualTo(Integer.valueOf(0));
138139
}
139140

140-
@Test
141+
@RetryingTest(10)
141142
public void testInt2993IdCacheConcurrency() throws InterruptedException, ExecutionException {
142143
final String groupId = "testInt2993Group";
143144
for (int i = 0; i < 100; i++) {

spring-integration-jdbc/src/test/java/org/springframework/integration/jdbc/store/channel/MySqlTxTimeoutMessageStoreTests.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.integration.jdbc.store.channel;
1818

1919
import org.junit.jupiter.api.Disabled;
20+
import org.junit.jupiter.api.Test;
2021

2122
import org.springframework.integration.jdbc.mysql.MySqlContainerTest;
2223
import org.springframework.test.context.ContextConfiguration;
@@ -42,4 +43,10 @@ public void testPriorityChannel() {
4243
super.testPriorityChannel();
4344
}
4445

46+
@Test
47+
@Disabled("Not stable on GitHub Actions")
48+
public void testInt3181ConcurrentPolling() throws InterruptedException {
49+
super.testPriorityChannel();
50+
}
51+
4552
}

spring-integration-kafka/src/test/java/org/springframework/integration/kafka/dsl/KafkaDslTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.apache.kafka.clients.producer.ProducerConfig;
3232
import org.apache.kafka.common.TopicPartition;
3333
import org.junit.jupiter.api.Test;
34+
import org.junitpioneer.jupiter.RetryingTest;
3435

3536
import org.springframework.beans.factory.annotation.Autowired;
3637
import org.springframework.beans.factory.annotation.Qualifier;
@@ -169,7 +170,7 @@ public class KafkaDslTests {
169170
@Autowired
170171
private QueueChannel recoveringErrorChannel;
171172

172-
@Test
173+
@RetryingTest(10)
173174
void testKafkaAdapters() throws Exception {
174175
this.sendToKafkaFlowInput.send(new GenericMessage<>("foo", Collections.singletonMap("foo", "bar")));
175176

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
/*
2+
* Copyright 2026-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.integration.mail;
18+
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.List;
22+
import java.util.logging.Handler;
23+
import java.util.logging.Level;
24+
import java.util.logging.LogManager;
25+
import java.util.logging.LogRecord;
26+
import java.util.logging.Logger;
27+
28+
import com.icegreen.greenmail.user.GreenMailUser;
29+
import com.icegreen.greenmail.util.GreenMail;
30+
import com.icegreen.greenmail.util.GreenMailUtil;
31+
import com.icegreen.greenmail.util.ServerSetup;
32+
import com.icegreen.greenmail.util.ServerSetupTest;
33+
import jakarta.mail.Flags;
34+
import jakarta.mail.Flags.Flag;
35+
import jakarta.mail.Message;
36+
import jakarta.mail.internet.AddressException;
37+
import jakarta.mail.internet.InternetAddress;
38+
import jakarta.mail.internet.MimeMessage;
39+
import jakarta.mail.search.AndTerm;
40+
import jakarta.mail.search.FlagTerm;
41+
import jakarta.mail.search.FromTerm;
42+
import org.eclipse.angus.mail.imap.protocol.IMAPProtocol;
43+
import org.junit.jupiter.api.AfterAll;
44+
import org.junit.jupiter.api.AfterEach;
45+
import org.junit.jupiter.api.BeforeAll;
46+
import org.junit.jupiter.api.BeforeEach;
47+
import org.junitpioneer.jupiter.RetryingTest;
48+
49+
import org.springframework.beans.factory.BeanFactory;
50+
import org.springframework.beans.factory.annotation.Autowired;
51+
import org.springframework.context.ApplicationContext;
52+
import org.springframework.integration.channel.QueueChannel;
53+
import org.springframework.integration.mail.support.DefaultMailHeaderMapper;
54+
import org.springframework.messaging.MessageHeaders;
55+
import org.springframework.scheduling.TaskScheduler;
56+
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
57+
import org.springframework.test.annotation.DirtiesContext;
58+
import org.springframework.test.context.ContextConfiguration;
59+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
60+
import org.springframework.util.MimeTypeUtils;
61+
62+
import static org.assertj.core.api.Assertions.assertThat;
63+
import static org.mockito.BDDMockito.given;
64+
import static org.mockito.Mockito.mock;
65+
66+
/**
67+
* @author Oleg Zhurakousky
68+
* @author Gary Russell
69+
* @author Artem Bilan
70+
* @author Alexander Pinske
71+
* @author Dominik Simmen
72+
* @author Filip Hrisafov
73+
* @author Glenn Renfro
74+
*/
75+
@SpringJUnitConfig
76+
@ContextConfiguration(
77+
"classpath:org/springframework/integration/mail/config/ImapIdleChannelAdapterParserTests-context.xml")
78+
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
79+
public class ImapMailReceiverNoDebugTests {
80+
81+
private static final ImapSearchLoggingHandler imapSearches = new ImapSearchLoggingHandler();
82+
83+
private GreenMail imapIdleServer;
84+
85+
private GreenMailUser user;
86+
87+
@Autowired
88+
private ApplicationContext context;
89+
90+
static {
91+
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
92+
}
93+
94+
@BeforeAll
95+
static void setup() {
96+
Logger logger = LogManager.getLogManager().getLogger("");
97+
logger.setLevel(Level.ALL);
98+
logger.addHandler(imapSearches);
99+
}
100+
101+
@AfterAll
102+
static void teardown() {
103+
LogManager.getLogManager().getLogger("").removeHandler(imapSearches);
104+
}
105+
106+
@BeforeEach
107+
void startImapServer() {
108+
imapSearches.searches.clear();
109+
imapSearches.stores.clear();
110+
ServerSetup imap = ServerSetupTest.IMAP.verbose(true).dynamicPort();
111+
imap.setServerStartupTimeout(10000);
112+
imap.setReadTimeout(10000);
113+
imapIdleServer = new GreenMail(imap);
114+
user = imapIdleServer.setUser("user", "pw");
115+
imapIdleServer.start();
116+
}
117+
118+
@AfterEach
119+
void stopImapServer() {
120+
imapIdleServer.stop();
121+
}
122+
123+
@RetryingTest(10)
124+
public void testIdleWithServerCustomSearch() throws Exception {
125+
ImapMailReceiver receiver =
126+
new ImapMailReceiver("imap://user:pw@localhost:" + imapIdleServer.getImap().getPort() + "/INBOX");
127+
receiver.setSearchTermStrategy((supportedFlags, folder) -> {
128+
try {
129+
FromTerm fromTerm = new FromTerm(new InternetAddress("bar@baz"));
130+
return new AndTerm(fromTerm, new FlagTerm(new Flags(Flag.SEEN), false));
131+
}
132+
catch (AddressException e) {
133+
throw new RuntimeException(e);
134+
}
135+
});
136+
testIdleWithServerGuts(receiver, false);
137+
}
138+
139+
@RetryingTest(10)
140+
public void testIdleWithMessageMapping() throws Exception {
141+
ImapMailReceiver receiver =
142+
new ImapMailReceiver("imap://user:pw@localhost:" + imapIdleServer.getImap().getPort() + "/INBOX");
143+
receiver.setHeaderMapper(new DefaultMailHeaderMapper());
144+
testIdleWithServerGuts(receiver, true);
145+
}
146+
147+
@RetryingTest(10)
148+
public void testIdleWithServerDefaultSearchSimple() throws Exception {
149+
ImapMailReceiver receiver =
150+
new ImapMailReceiver("imap://user:pw@localhost:" + imapIdleServer.getImap().getPort() + "/INBOX");
151+
receiver.setSimpleContent(true);
152+
testIdleWithServerGuts(receiver, false, true);
153+
assertThat(imapSearches.searches.get(0)).contains("testSIUserFlag");
154+
}
155+
156+
@RetryingTest(10)
157+
public void testIdleWithMessageMappingSimple() throws Exception {
158+
ImapMailReceiver receiver =
159+
new ImapMailReceiver("imap://user:pw@localhost:" + imapIdleServer.getImap().getPort() + "/INBOX");
160+
receiver.setSimpleContent(true);
161+
receiver.setHeaderMapper(new DefaultMailHeaderMapper());
162+
testIdleWithServerGuts(receiver, true, true);
163+
}
164+
165+
public void testIdleWithServerGuts(ImapMailReceiver receiver, boolean mapped) throws Exception {
166+
testIdleWithServerGuts(receiver, mapped, false);
167+
}
168+
169+
public void testIdleWithServerGuts(ImapMailReceiver receiver, boolean mapped, boolean simple) throws Exception {
170+
receiver.setMaxFetchSize(1);
171+
receiver.setShouldDeleteMessages(false);
172+
receiver.setShouldMarkMessagesAsRead(true);
173+
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
174+
setUpScheduler(receiver, taskScheduler);
175+
receiver.setUserFlag("testSIUserFlag");
176+
receiver.afterPropertiesSet();
177+
ImapIdleChannelAdapter adapter = new ImapIdleChannelAdapter(receiver);
178+
QueueChannel channel = new QueueChannel();
179+
adapter.setOutputChannel(channel);
180+
adapter.setReconnectDelay(10);
181+
adapter.afterPropertiesSet();
182+
adapter.start();
183+
MimeMessage message =
184+
GreenMailUtil.createTextEmail("Foo <foo@bar>", "Bar <bar@baz>", "Test Email", "foo\r\n",
185+
imapIdleServer.getImap().getServerSetup());
186+
message.setRecipients(Message.RecipientType.CC, "a@b, c@d");
187+
message.setRecipients(Message.RecipientType.BCC, "e@f, g@h");
188+
user.deliver(message);
189+
if (!mapped) {
190+
@SuppressWarnings("unchecked")
191+
org.springframework.messaging.Message<MimeMessage> received =
192+
(org.springframework.messaging.Message<MimeMessage>) channel.receive(20000);
193+
assertThat(received).isNotNull();
194+
assertThat(received.getPayload().getReceivedDate()).isNotNull();
195+
assertThat(received.getPayload().getLineCount() > -1).isTrue();
196+
if (simple) {
197+
assertThat(received.getPayload().getContent())
198+
.isEqualTo("foo\r\n");
199+
}
200+
else {
201+
assertThat(received.getPayload().getContent())
202+
.isEqualTo("foo");
203+
}
204+
}
205+
else {
206+
org.springframework.messaging.Message<?> received = channel.receive(20000);
207+
assertThat(received).isNotNull();
208+
MessageHeaders headers = received.getHeaders();
209+
assertThat(headers.get(MailHeaders.RAW_HEADERS)).isNotNull();
210+
assertThat(headers.get(MailHeaders.CONTENT_TYPE)).isEqualTo("text/plain; charset=us-ascii");
211+
assertThat(headers.get(MessageHeaders.CONTENT_TYPE)).isEqualTo(MimeTypeUtils.TEXT_PLAIN_VALUE);
212+
assertThat(headers.get(MailHeaders.FROM)).isEqualTo("Bar <bar@baz>");
213+
String[] toHeader = headers.get(MailHeaders.TO, String[].class);
214+
assertThat(toHeader).isNotEmpty();
215+
assertThat(toHeader[0]).isEqualTo("Foo <foo@bar>");
216+
assertThat(Arrays.toString(headers.get(MailHeaders.CC, String[].class))).isEqualTo("[a@b, c@d]");
217+
assertThat(Arrays.toString(headers.get(MailHeaders.BCC, String[].class))).isEqualTo("[e@f, g@h]");
218+
assertThat(headers.get(MailHeaders.SUBJECT)).isEqualTo("Test Email");
219+
if (simple) {
220+
assertThat(received.getPayload()).isEqualTo("foo\r\n");
221+
}
222+
else {
223+
assertThat(received.getPayload()).isEqualTo("foo");
224+
}
225+
}
226+
user.deliver(GreenMailUtil.createTextEmail("Foo <foo@bar>", "Bar <bar@baz>", "subject", "body\r\n",
227+
imapIdleServer.getImap().getServerSetup()));
228+
assertThat(channel.receive(30000)).isNotNull(); // new message after idle
229+
assertThat(channel.receive(100)).isNull(); // no new message after second and third idle
230+
231+
adapter.stop();
232+
taskScheduler.shutdown();
233+
assertThat(imapSearches.stores.get(0)).contains("testSIUserFlag");
234+
}
235+
236+
private void setUpScheduler(ImapMailReceiver mailReceiver, ThreadPoolTaskScheduler taskScheduler) {
237+
taskScheduler.setPoolSize(5);
238+
taskScheduler.initialize();
239+
BeanFactory bf = mock(BeanFactory.class);
240+
given(bf.containsBean("taskScheduler")).willReturn(true);
241+
given(bf.getBean("taskScheduler", TaskScheduler.class)).willReturn(taskScheduler);
242+
mailReceiver.setBeanFactory(bf);
243+
}
244+
245+
private static class ImapSearchLoggingHandler extends Handler {
246+
247+
private final List<String> searches = new ArrayList<>();
248+
249+
private final List<String> stores = new ArrayList<>();
250+
251+
private static final String SEARCH = " SEARCH ";
252+
253+
private static final String STORE = " STORE ";
254+
255+
@Override
256+
public void publish(LogRecord record) {
257+
if (IMAPProtocol.class.getPackageName().equals(record.getLoggerName())) {
258+
String message = record.getMessage();
259+
if (!message.startsWith("*")) {
260+
if (message.contains(SEARCH) && !message.contains(" OK ")) {
261+
searches.add(message.substring(message.indexOf(SEARCH) + SEARCH.length()));
262+
}
263+
else if (message.contains(STORE) && !message.contains(" OK ")) {
264+
stores.add(message.substring(message.indexOf(STORE) + STORE.length()));
265+
}
266+
}
267+
}
268+
}
269+
270+
@Override
271+
public void flush() {
272+
}
273+
274+
@Override
275+
public void close() throws SecurityException {
276+
}
277+
278+
}
279+
280+
}

0 commit comments

Comments
 (0)