This is an automated email from the ASF dual-hosted git repository.

ppkarwasz pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/logging-flume.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 373de83e De-flake `TestExecSource` on Windows (#491)
373de83e is described below

commit 373de83efb1d6a2b4feb335377435fc97464ba03
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Tue Aug 25 13:46:29 2026 +0200

    De-flake `TestExecSource` on Windows (#491)
    
    Fixed sleeps lose to PowerShell start-up times on loaded CI runners:
    await the source counter instead.
    `Get-Content -Wait` keeps the process alive like `tail -f`,
    so the batch timeout, not the EOF flush, delivers the events.
    Writing the input file before the source starts removes the race
    with the one-shot read of the old command.
    
    Assisted-By: Claude Fable 5 <[email protected]>
    Claude-Session: https://claude.ai/code/session_016zsFn1f2B32vcNoCeZSfn7
---
 flume-ng-core/pom.xml                              |  6 +++
 .../java/org/apache/flume/source/ExecSource.java   |  6 +++
 .../org/apache/flume/source/TestExecSource.java    | 60 ++++++++++++++--------
 3 files changed, 50 insertions(+), 22 deletions(-)

diff --git a/flume-ng-core/pom.xml b/flume-ng-core/pom.xml
index e8b6a0d2..fa9857c8 100644
--- a/flume-ng-core/pom.xml
+++ b/flume-ng-core/pom.xml
@@ -98,6 +98,12 @@
       <scope>test</scope>
     </dependency>
 
+    <dependency>
+      <groupId>org.awaitility</groupId>
+      <artifactId>awaitility</artifactId>
+      <scope>test</scope>
+    </dependency>
+
     <dependency>
       <groupId>org.easytesting</groupId>
       <artifactId>fest-reflect</artifactId>
diff --git 
a/flume-ng-core/src/main/java/org/apache/flume/source/ExecSource.java 
b/flume-ng-core/src/main/java/org/apache/flume/source/ExecSource.java
index c99e8dd5..17b03c58 100644
--- a/flume-ng-core/src/main/java/org/apache/flume/source/ExecSource.java
+++ b/flume-ng-core/src/main/java/org/apache/flume/source/ExecSource.java
@@ -16,6 +16,7 @@
  */
 package org.apache.flume.source;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import java.io.BufferedReader;
@@ -260,6 +261,11 @@ public class ExecSource extends AbstractSource implements 
EventDrivenSource, Con
         return bufferCount;
     }
 
+    @VisibleForTesting
+    SourceCounter getSourceCounter() {
+        return sourceCounter;
+    }
+
     private static class ExecRunnable implements Runnable {
 
         public ExecRunnable(
diff --git 
a/flume-ng-core/src/test/java/org/apache/flume/source/TestExecSource.java 
b/flume-ng-core/src/test/java/org/apache/flume/source/TestExecSource.java
index d8d369f6..e007ff73 100644
--- a/flume-ng-core/src/test/java/org/apache/flume/source/TestExecSource.java
+++ b/flume-ng-core/src/test/java/org/apache/flume/source/TestExecSource.java
@@ -16,6 +16,7 @@
  */
 package org.apache.flume.source;
 
+import static org.awaitility.Awaitility.await;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -31,6 +32,7 @@ import java.io.InputStreamReader;
 import java.lang.management.ManagementFactory;
 import java.nio.charset.Charset;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 import javax.management.Attribute;
 import javax.management.AttributeList;
@@ -58,9 +60,9 @@ import org.junit.Test;
 public class TestExecSource {
 
     private AbstractSource source;
-    private Channel channel = new MemoryChannel();
-    private Context context = new Context();
-    private ChannelSelector rcs = new ReplicatingChannelSelector();
+    private final Channel channel = new MemoryChannel();
+    private final Context context = new Context();
+    private final ChannelSelector rcs = new ReplicatingChannelSelector();
 
     @Before
     public void setUp() {
@@ -101,7 +103,8 @@ public class TestExecSource {
         // Generates input file with a random data set (10 lines, 200 
characters each)
         FileOutputStream outputStream1 = new FileOutputStream(inputFile);
         for (int i = 0; i < 10; i++) {
-            
outputStream1.write(RandomStringUtils.randomAlphanumeric(200).getBytes());
+            outputStream1.write(
+                    
RandomStringUtils.insecure().nextAlphanumeric(200).getBytes());
             outputStream1.write('\n');
         }
         outputStream1.close();
@@ -116,7 +119,7 @@ public class TestExecSource {
         Configurables.configure(source, context);
 
         source.start();
-        Thread.sleep(2000);
+        awaitEventCount(10);
         Transaction transaction = channel.getTransaction();
 
         transaction.begin();
@@ -286,36 +289,41 @@ public class TestExecSource {
     public void testBatchTimeout()
             throws InterruptedException, LifecycleException, 
EventDeliveryException, IOException {
 
-        String filePath = "/tmp/flume-execsource." + 
Thread.currentThread().getId();
+        File file = File.createTempFile("flume-execsource", null);
+        FileUtils.forceDeleteOnExit(file);
         String eventBody = "TestMessage";
-        FileOutputStream outputStream = new FileOutputStream(filePath);
+
+        // Write the file up front, so the command output does not depend on
+        // when the process starts reading; both commands below print the last
+        // lines of an existing file and then follow it.
+        FileOutputStream outputStream = new FileOutputStream(file);
+        for (int lineNumber = 0; lineNumber < 3; lineNumber++) {
+            outputStream.write((eventBody).getBytes());
+            outputStream.write(String.valueOf(lineNumber).getBytes());
+            outputStream.write('\n');
+        }
+        outputStream.close();
 
         context.put(ExecSourceConfigurationConstants.CONFIG_BATCH_SIZE, 
"50000");
         context.put(ExecSourceConfigurationConstants.CONFIG_BATCH_TIME_OUT, 
"750");
         context.put(
                 "shell",
                 SystemUtils.IS_OS_WINDOWS ? "powershell -ExecutionPolicy 
Unrestricted -command" : "/bin/bash -c");
+        // The process must outlive the batch timeout, so that only the timed
+        // flush can deliver the events.
         context.put(
                 "command",
                 SystemUtils.IS_OS_WINDOWS
-                        ? "Get-Content " + filePath + " | Select-Object -Last 
10"
-                        : ("tail -f " + filePath));
+                        ? "Get-Content -Tail 10 -Wait '" + 
file.getAbsolutePath() + "'"
+                        : ("tail -f " + file.getAbsolutePath()));
 
         Configurables.configure(source, context);
         source.start();
+        awaitEventCount(3);
 
         Transaction transaction = channel.getTransaction();
         transaction.begin();
 
-        for (int lineNumber = 0; lineNumber < 3; lineNumber++) {
-            outputStream.write((eventBody).getBytes());
-            outputStream.write(String.valueOf(lineNumber).getBytes());
-            outputStream.write('\n');
-            outputStream.flush();
-        }
-        outputStream.close();
-        Thread.sleep(1500);
-
         for (int i = 0; i < 3; i++) {
             Event event = channel.take();
             assertNotNull(event);
@@ -326,19 +334,27 @@ public class TestExecSource {
         transaction.commit();
         transaction.close();
         source.stop();
-        File file = new File(filePath);
         FileUtils.forceDelete(file);
     }
 
+    /**
+     * Waits until the source accepted the given number of events.
+     *
+     * <p>A fixed sleep is not enough on slow environments, where starting
+     * the child process alone can take several seconds.
+     */
+    private void awaitEventCount(int expected) {
+        await().atMost(30, TimeUnit.SECONDS)
+                .until(() -> ((ExecSource) 
source).getSourceCounter().getEventAcceptedCount() >= expected);
+    }
+
     private void runTestShellCmdHelper(String shell, String command, String[] 
expectedOutput)
             throws InterruptedException, LifecycleException, 
EventDeliveryException, IOException {
         context.put("shell", shell);
         context.put("command", command);
         Configurables.configure(source, context);
         source.start();
-        // Some commands might take longer to complete, specially on Windows
-        // or on slow environments (e.g. Travis CI).
-        Thread.sleep(2500);
+        awaitEventCount(expectedOutput.length);
         Transaction transaction = channel.getTransaction();
         transaction.begin();
         try {

Reply via email to