This is an automated email from the ASF dual-hosted git repository.
apupier pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 71633593ebdc CAMEL-25688: Stabilize flaky FTP consumer integration
tests
71633593ebdc is described below
commit 71633593ebdcc3b3e658f858283bd6a03bf9556c
Author: Aurélien Pupier <[email protected]>
AuthorDate: Fri Oct 16 09:21:20 2026 +0200
CAMEL-25688: Stabilize flaky FTP consumer integration tests
Seven FTP consumer IT tests were susceptible to receiving more messages
than expected because:
1. The Camel context and routes are started *before* doPostSetup() runs,
so the FTP consumer scheduler can fire its first poll against an
empty or partially-populated directory.
2. Without idempotency protection (no noop/delete/idempotent), files are
re-consumed on every subsequent poll interval within the
MockEndpoint.assertIsSatisfied() 10-second wait window, causing
"expected <N> but was <M>" assertion failures non-deterministically.
Fix: add &noop=true to all affected consumer URLs. noop=true
automatically enables idempotent=true, so each file path is tracked in
a MemoryIdempotentRepository and consumed exactly once regardless of how
many poll cycles occur during the test.
A separate getFtpProducerUrl() helper is introduced in each test where
sendFile() previously reused getFtpUrl(). This prevents consumer-only
options (noop, recursive, include, filter, sorter, sortBy) from leaking
into producer URLs, which would cause endpoint resolution failures.
FtpConsumerDirectoriesNotMatchedIT now also verifies the exact set of
matched file names via mock.expectedHeaderValuesReceivedInAnyOrder(),
which reports the unexpected file names when the assertion fails.
For FtpConsumerRelativeFileNameIT and FromFtpRemoteFileSorterIT, which
check strict delivery order (sortBy=file:name and a custom alphabetical
sorter), the route is started explicitly after doPostSetup() completes
the upload (autoStartup=false + startRoute()) to guarantee all files
are present before the first poll fires.
Affected tests:
- FtpConsumerDirectoriesNotMatchedIT (original flaky test, CAMEL-25688)
- FtpConsumerRelativeFileNameIT
- FtpConsumerMultipleDirectoriesIT
- FtpConsumerSkipDotFilesIT
- FromFtpRemoteFileFilterIT
- FromFtpRemoteFileSorterIT
- FromFtpRegexPatternIT
Co-authored-by: IBM Bob 2.2.1
Signed-off-by: Aurélien Pupier <[email protected]>
---
.../remote/integration/FromFtpRegexPatternIT.java | 2 +-
.../integration/FromFtpRemoteFileFilterIT.java | 14 ++++++++-----
.../integration/FromFtpRemoteFileSorterIT.java | 15 +++++++++-----
.../FtpConsumerDirectoriesNotMatchedIT.java | 23 ++++++++++++++--------
.../FtpConsumerMultipleDirectoriesIT.java | 12 +++++++----
.../integration/FtpConsumerRelativeFileNameIT.java | 13 ++++++++----
.../integration/FtpConsumerSkipDotFilesIT.java | 12 +++++++----
7 files changed, 60 insertions(+), 31 deletions(-)
diff --git
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FromFtpRegexPatternIT.java
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FromFtpRegexPatternIT.java
index 686352d669e5..27e511566969 100644
---
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FromFtpRegexPatternIT.java
+++
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FromFtpRegexPatternIT.java
@@ -27,7 +27,7 @@ import org.junit.jupiter.api.Test;
public class FromFtpRegexPatternIT extends FtpServerTestSupport {
private String getFtpUrl() {
- return
"ftp://admin@localhost:{{ftp.server.port}}/regexp?password=admin&include=report.*";
+ return
"ftp://admin@localhost:{{ftp.server.port}}/regexp?password=admin&include=report.*&noop=true";
}
@Override
diff --git
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FromFtpRemoteFileFilterIT.java
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FromFtpRemoteFileFilterIT.java
index 84d29004202d..88e998caa713 100644
---
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FromFtpRemoteFileFilterIT.java
+++
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FromFtpRemoteFileFilterIT.java
@@ -34,7 +34,11 @@ public class FromFtpRemoteFileFilterIT extends
FtpServerTestSupport {
private final MyFileFilter<Object> filter = new MyFileFilter<>();
private String getFtpUrl() {
- return
"ftp://admin@localhost:{{ftp.server.port}}/filefilter?password=admin&filter=#myFilter";
+ return
"ftp://admin@localhost:{{ftp.server.port}}/filefilter?password=admin&filter=#myFilter&noop=true";
+ }
+
+ private String getFtpProducerUrl() {
+ return
"ftp://admin@localhost:{{ftp.server.port}}/filefilter?password=admin";
}
@Override
@@ -59,10 +63,10 @@ public class FromFtpRemoteFileFilterIT extends
FtpServerTestSupport {
// prepares the FTP Server by creating files on the server that we want
// to unit
// test that we can pool
- sendFile(getFtpUrl(), "Hello World", "hello.txt");
- sendFile(getFtpUrl(), "Report 1", "report1.txt");
- sendFile(getFtpUrl(), "Bye World", "bye.txt");
- sendFile(getFtpUrl(), "Report 2", "report2.txt");
+ sendFile(getFtpProducerUrl(), "Hello World", "hello.txt");
+ sendFile(getFtpProducerUrl(), "Report 1", "report1.txt");
+ sendFile(getFtpProducerUrl(), "Bye World", "bye.txt");
+ sendFile(getFtpProducerUrl(), "Report 2", "report2.txt");
}
@Override
diff --git
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FromFtpRemoteFileSorterIT.java
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FromFtpRemoteFileSorterIT.java
index f9a70e628c99..78186aba447c 100644
---
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FromFtpRemoteFileSorterIT.java
+++
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FromFtpRemoteFileSorterIT.java
@@ -33,12 +33,17 @@ public class FromFtpRemoteFileSorterIT extends
FtpServerTestSupport {
private final MyRemoteFileSorter sorter = new MyRemoteFileSorter();
private String getFtpUrl() {
- return
"ftp://admin@localhost:{{ftp.server.port}}/sorter?password=admin&sorter=#mySorter";
+ return
"ftp://admin@localhost:{{ftp.server.port}}/sorter?password=admin&sorter=#mySorter&noop=true";
+ }
+
+ private String getFtpProducerUrl() {
+ return
"ftp://admin@localhost:{{ftp.server.port}}/sorter?password=admin";
}
@Override
public void doPostSetup() throws Exception {
prepareFtpServer();
+ context.getRouteController().startRoute("ftp-sorter");
}
@Test
@@ -53,16 +58,16 @@ public class FromFtpRemoteFileSorterIT extends
FtpServerTestSupport {
// prepares the FTP Server by creating files on the server that we want
// to unit
// test that we can pool
- sendFile(getFtpUrl(), "Hello Paris", "paris.txt");
- sendFile(getFtpUrl(), "Hello London", "london.txt");
- sendFile(getFtpUrl(), "Hello Copenhagen", "copenhagen.txt");
+ sendFile(getFtpProducerUrl(), "Hello Paris", "paris.txt");
+ sendFile(getFtpProducerUrl(), "Hello London", "london.txt");
+ sendFile(getFtpProducerUrl(), "Hello Copenhagen", "copenhagen.txt");
}
@Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
public void configure() {
- from(getFtpUrl()).to("mock:result");
+
from(getFtpUrl()).routeId("ftp-sorter").autoStartup(false).to("mock:result");
}
};
}
diff --git
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerDirectoriesNotMatchedIT.java
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerDirectoriesNotMatchedIT.java
index 12027ed1d190..5fa96c93991a 100644
---
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerDirectoriesNotMatchedIT.java
+++
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerDirectoriesNotMatchedIT.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.component.file.remote.integration;
+import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.junit.jupiter.api.Test;
@@ -26,7 +27,12 @@ import org.junit.jupiter.api.Test;
public class FtpConsumerDirectoriesNotMatchedIT extends FtpServerTestSupport {
private String getFtpUrl() {
- return
"ftp://admin@localhost:{{ftp.server.port}}/dirnotmatched/?password=admin" +
"&recursive=true&include=^.*txt$";
+ return
"ftp://admin@localhost:{{ftp.server.port}}/dirnotmatched/?password=admin"
+ + "&recursive=true&include=^.*txt$&noop=true";
+ }
+
+ private String getFtpProducerUrl(String subDir) {
+ return "ftp://admin@localhost:{{ftp.server.port}}/dirnotmatched/" +
subDir + "?password=admin";
}
@Override
@@ -38,19 +44,20 @@ public class FtpConsumerDirectoriesNotMatchedIT extends
FtpServerTestSupport {
public void testSkipDirectories() throws Exception {
MockEndpoint mock = getMockEndpoint("mock:result");
mock.expectedMessageCount(3);
+ mock.expectedHeaderValuesReceivedInAnyOrder(Exchange.FILE_NAME,
+ "readme.txt", "2007/report2007.txt", "2008/report2008.txt");
mock.assertIsSatisfied();
}
private void prepareFtpServer() {
// prepares the FTP Server by creating files on the server that we want
- // to unit
- // test that we can pool and store as a local file
+ // to unit test that we can poll and skip non-matching files and
directories
- sendFile(getFtpUrl() + "/?password=admin", "This is a dot file",
".skipme");
- sendFile(getFtpUrl() + "/?password=admin", "This is a web file",
"index.html");
- sendFile(getFtpUrl() + "/?password=admin", "This is a readme file",
"readme.txt");
- sendFile(getFtpUrl() + "/2007/?password=admin", "2007 report",
"report2007.txt");
- sendFile(getFtpUrl() + "/2008/?password=admin", "2008 report",
"report2008.txt");
+ sendFile(getFtpProducerUrl(""), "This is a dot file", ".skipme");
+ sendFile(getFtpProducerUrl(""), "This is a web file", "index.html");
+ sendFile(getFtpProducerUrl(""), "This is a readme file", "readme.txt");
+ sendFile(getFtpProducerUrl("2007"), "2007 report", "report2007.txt");
+ sendFile(getFtpProducerUrl("2008"), "2008 report", "report2008.txt");
}
@Override
diff --git
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerMultipleDirectoriesIT.java
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerMultipleDirectoriesIT.java
index 8a548f4a95ee..e06543c3719a 100644
---
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerMultipleDirectoriesIT.java
+++
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerMultipleDirectoriesIT.java
@@ -30,7 +30,11 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
public class FtpConsumerMultipleDirectoriesIT extends FtpServerTestSupport {
private String getFtpUrl() {
- return
"ftp://admin@localhost:{{ftp.server.port}}/multidir/?password=admin&recursive=true&delay=5000&sortBy=file:path";
+ return
"ftp://admin@localhost:{{ftp.server.port}}/multidir/?password=admin&recursive=true&delay=5000&sortBy=file:path&noop=true";
+ }
+
+ private String getFtpProducerUrl() {
+ return
"ftp://admin@localhost:{{ftp.server.port}}/multidir/?password=admin";
}
@Override
@@ -70,9 +74,9 @@ public class FtpConsumerMultipleDirectoriesIT extends
FtpServerTestSupport {
}
private void prepareFtpServer() {
- sendFile(getFtpUrl(), "Bye World", "bye.txt");
- sendFile(getFtpUrl(), "Hello World", "sub/hello.txt");
- sendFile(getFtpUrl(), "Goodday World", "sub/sub2/godday.txt");
+ sendFile(getFtpProducerUrl(), "Bye World", "bye.txt");
+ sendFile(getFtpProducerUrl(), "Hello World", "sub/hello.txt");
+ sendFile(getFtpProducerUrl(), "Goodday World", "sub/sub2/godday.txt");
}
@Override
diff --git
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerRelativeFileNameIT.java
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerRelativeFileNameIT.java
index e125e10e7808..442f7a4e10fb 100644
---
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerRelativeFileNameIT.java
+++
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerRelativeFileNameIT.java
@@ -30,13 +30,18 @@ public class FtpConsumerRelativeFileNameIT extends
FtpServerTestSupport {
private String getFtpUrl() {
return "ftp://admin@localhost:{{ftp.server.port}}"
- +
"/out/filename-consumer?password=admin&recursive=true&sortBy=file:name";
+ +
"/out/filename-consumer?password=admin&recursive=true&sortBy=file:name&noop=true";
+ }
+
+ private String getFtpProducerUrl() {
+ return
"ftp://admin@localhost:{{ftp.server.port}}/out/filename-consumer?password=admin";
}
@Override
public void doPostSetup() throws Exception {
- sendFile(getFtpUrl(), "Hello World",
"out/filename-consumer-hello.txt");
- sendFile(getFtpUrl(), "Bye World", "out/filename-consumer-bye.txt");
+ sendFile(getFtpProducerUrl(), "Hello World",
"out/filename-consumer-hello.txt");
+ sendFile(getFtpProducerUrl(), "Bye World",
"out/filename-consumer-bye.txt");
+ context.getRouteController().startRoute("ftp-filename-consumer");
}
@Override
@@ -44,7 +49,7 @@ public class FtpConsumerRelativeFileNameIT extends
FtpServerTestSupport {
return new RouteBuilder() {
@Override
public void configure() {
- from(getFtpUrl()).to("mock:result");
+
from(getFtpUrl()).routeId("ftp-filename-consumer").autoStartup(false).to("mock:result");
}
};
}
diff --git
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerSkipDotFilesIT.java
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerSkipDotFilesIT.java
index 7998958ccb50..0b5ab3b96f4b 100644
---
a/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerSkipDotFilesIT.java
+++
b/components/camel-ftp/src/test/java/org/apache/camel/component/file/remote/integration/FtpConsumerSkipDotFilesIT.java
@@ -26,6 +26,10 @@ import org.junit.jupiter.api.Test;
public class FtpConsumerSkipDotFilesIT extends FtpServerTestSupport {
private String getFtpUrl() {
+ return
"ftp://admin@localhost:{{ftp.server.port}}/dotfiles?password=admin&noop=true";
+ }
+
+ private String getFtpProducerUrl() {
return
"ftp://admin@localhost:{{ftp.server.port}}/dotfiles?password=admin";
}
@@ -46,10 +50,10 @@ public class FtpConsumerSkipDotFilesIT extends
FtpServerTestSupport {
// prepares the FTP Server by creating files on the server that we want
// to unit
// test that we can pool and store as a local file
- sendFile(getFtpUrl(), "Hello World", ".skipme");
- sendFile(getFtpUrl(), "Reports", "report1.txt");
- sendFile(getFtpUrl(), "Bye World", ".camel");
- sendFile(getFtpUrl(), "Reports", "report2.txt");
+ sendFile(getFtpProducerUrl(), "Hello World", ".skipme");
+ sendFile(getFtpProducerUrl(), "Reports", "report1.txt");
+ sendFile(getFtpProducerUrl(), "Bye World", ".camel");
+ sendFile(getFtpProducerUrl(), "Reports", "report2.txt");
}
@Override