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

hellostephen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new fc1b3ecdac4 [feature](regression) add support to run export cases on 
multi bes (#55981)
fc1b3ecdac4 is described below

commit fc1b3ecdac455a361e6b876c1acf07d203f5f182
Author: shuke <[email protected]>
AuthorDate: Tue Sep 16 16:24:05 2025 +0800

    [feature](regression) add support to run export cases on multi bes (#55981)
---
 .../regression/util/RemoteFileOperator.groovy      | 370 +++++++++++++++++++++
 .../agg_state/test_outfile_agg_state.groovy        |  19 +-
 .../agg_state_array/test_outfile_agg_array.groovy  |  17 +-
 .../test_outfile_agg_state_bitmap.groovy           |  17 +-
 .../nereids_p0/outfile/hll/test_outfile_hll.groovy |  19 +-
 .../test_outfile_quantile_state.groovy             |  19 +-
 6 files changed, 430 insertions(+), 31 deletions(-)

diff --git 
a/regression-test/framework/src/main/groovy/org/apache/doris/regression/util/RemoteFileOperator.groovy
 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/util/RemoteFileOperator.groovy
new file mode 100644
index 00000000000..13f404f928d
--- /dev/null
+++ 
b/regression-test/framework/src/main/groovy/org/apache/doris/regression/util/RemoteFileOperator.groovy
@@ -0,0 +1,370 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you 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.apache.doris.regression.util
+
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.util.concurrent.TimeUnit
+import java.security.SecureRandom
+
+/**
+ * A utility class for remote file operations via SSH protocol, supporting 
batch operations on multiple hosts.
+ * Core capabilities include remote directory creation, file downloading (via 
SCP), and directory deletion.
+ * <p>Key Features:
+ * <ul>
+ *   <li>Batch operation support for multiple remote hosts (shared SSH 
credentials and port)</li>
+ *   <li>Built-in timeout control for all operations to prevent long-term 
blocking</li>
+ *   <li>Safety check for deletion: only paths containing {@link 
#SAFETY_PREFIX} can be deleted (avoids accidental data loss)</li>
+ *   <li>Pre-download file count check: skips SCP if remote directory is empty 
(prevents "No such file" errors)</li>
+ * </ul>
+ *
+ * <p>Usage Example:
+ * <pre>
+ * // 1. Initialize operator with 2 remote hosts, SSH user "root", port 22, 
15s timeout
+ * List<String> hosts = Arrays.asList("172.20.56.7", "172.20.56.14");
+ * RemoteFileOperator operator = new RemoteFileOperator(hosts, "root", 22, 
15000);
+ *
+ * // 2. Create directory on all remote hosts
+ * operator.createRemoteDirectories("/tmp/doristest_data");
+ *
+ * // 3. Download files from remote to local (skips if remote is empty)
+ * operator.scpToLocal("/tmp/doristest_data", "/local/data");
+ *
+ * // 4. Delete remote directory (only allowed if path contains "doristest")
+ * operator.deleteRemoteDirectories("/tmp/doristest_data");
+ * </pre>
+ */
+class RemoteFileOperator {
+    private static final Logger logger = 
LoggerFactory.getLogger(RemoteFileOperator.class)
+    public static final String SAFETY_PREFIX = "doristest"
+    
+    private List<String> hosts
+    private String username
+    private int port
+    private int timeout
+
+    /**
+     * Constructor for RemoteFileOperator
+     * 
+     * @param hosts Single host string or list of host strings
+     * @param username SSH username (shared across all hosts)
+     * @param port SSH port (shared across all hosts), default is 22
+     * @param timeout Operation timeout in milliseconds, default is 10000
+     */
+    RemoteFileOperator(def hosts, String username, int port = 22, int timeout 
= 10000) {
+        if (hosts instanceof List) {
+            this.hosts = hosts
+        } else if (hosts instanceof String) {
+            this.hosts = [hosts]
+        } else {
+            throw new IllegalArgumentException("Hosts must be a string or list 
of strings")
+        }
+        
+        this.username = username
+        this.port = port
+        this.timeout = timeout
+    }
+
+    /**
+     * Create directories on all remote hosts
+     * Throws exception if any host fails
+     * 
+     * @param dirPath Path of the directory to create
+     * @throws Exception if directory creation fails on any host
+     */
+    void createRemoteDirectories(String dirPath) throws Exception {
+        hosts.each { host ->
+            logger.info("Processing host: ${host}")
+            def command = "mkdir -p ${escapePath(dirPath)}"
+            def execResult = executeSshCommand(host, command)
+            
+            if (execResult.timedOut) {
+                def errorMsg = "Timeout creating directory on ${host} 
(${timeout}ms)"
+                logger.error(errorMsg)
+                throw new Exception(errorMsg)
+            }
+            
+            if (execResult.exitCode != 0) {
+                def errorMsg = "Failed to create directory on ${host} (exit 
code: ${execResult.exitCode}): ${execResult.error}"
+                logger.error(errorMsg)
+                throw new Exception(errorMsg)
+            }
+        }
+        
+        logger.info("Successfully created directory '${dirPath}' on all hosts")
+    }
+
+    /**
+     * Download files from all remote hosts directly to local base directory
+     * without host-specific subfolders
+     * Throws exception if any host fails
+     * 
+     * @param remoteDir Remote directory path to download
+     * @param localBaseDir Local directory where all files will be copied
+     * @throws Exception if SCP fails on any host
+     */
+    void scpToLocal(String remoteDir, String localBaseDir) throws Exception {
+        logger.info("Starting SCP download process from ${hosts.size()} hosts 
to ${localBaseDir}")
+        remoteDir = remoteDir.trim()
+        localBaseDir = localBaseDir.trim()
+        if (remoteDir == null || remoteDir.trim().isEmpty()) {
+            throw new IllegalArgumentException("Remote directory cannot be 
null or empty")
+        }
+        if (localBaseDir == null || localBaseDir.trim().isEmpty()) {
+            throw new IllegalArgumentException("Local base directory cannot be 
null or empty")
+        }
+        
+        // Create base directory if it doesn't exist
+        def baseDir = new File(localBaseDir)
+        if (!baseDir.exists() && !baseDir.mkdirs()) {
+            def errorMsg = "Failed to create local base directory: 
${localBaseDir}"
+            logger.error(errorMsg)
+            throw new Exception(errorMsg)
+        }
+        
+        hosts.each { host ->
+            // Step 1: Check if remote directory has files (count regular 
files only)
+            // scp user@host:/tmp/doristest/* will fail if doristest has no 
files.
+            String countCommand = """ssh -p ${port} ${username}@${host} "find 
${escapePath(remoteDir)} -maxdepth 1 -type f | wc -l" """
+            def countResult = executeLocalCommand(countCommand)
+            
+            if (countResult.timedOut) {
+                def errorMsg = "Timeout checking file count on ${host} 
(${timeout}ms)"
+                logger.error(errorMsg)
+                throw new Exception(errorMsg)
+            }
+            
+            if (countResult.exitCode != 0) {
+                def errorMsg = "Failed to check file count on ${host} (exit 
code: ${countResult.exitCode}): ${countResult.error}"
+                logger.error(errorMsg)
+                throw new Exception(errorMsg)
+            }
+
+            // Parse file count (handle possible whitespace in output)
+            int fileCount = countResult.stdout.trim().toInteger()  // wc -l 
output is in error stream due to redirect
+            logger.info("Found ${fileCount} files in ${host}:${remoteDir}")
+
+            // Step 2: Only execute SCP if there are files to copy
+            if (fileCount > 0) {
+                logger.info("Downloading ${fileCount} files from 
${host}:${remoteDir} to ${localBaseDir}")
+
+                def normalizedRemoteDir = (remoteDir.endsWith('/') ? remoteDir 
: "${remoteDir}/") + "*"
+                def scpCommand = "scp -P ${port} 
${username}@${host}:${escapePath(normalizedRemoteDir)} 
${escapePath(localBaseDir)}"
+                def execResult = executeLocalCommand(scpCommand)
+
+                if (execResult.timedOut) {
+                    def errorMsg = "Timeout downloading from ${host} 
(${timeout}ms)"
+                    logger.error(errorMsg)
+                    throw new Exception(errorMsg)
+                }
+
+                if (execResult.exitCode != 0) {
+                    def errorMsg = "Failed to download from ${host} (exit 
code: ${execResult.exitCode}): ${execResult.error}"
+                    logger.error(errorMsg)
+                    throw new Exception(errorMsg)
+                }
+                logger.info("Successfully downloaded ${fileCount} files from 
${host}")
+            } else {
+                logger.info("No files found in ${host}:${remoteDir}, skipping 
SCP")
+            }
+        }
+
+        logger.info("SCP download process completed for all hosts")
+    }
+
+    /**
+     * Delete directories on all remote hosts
+     * Throws exception if any host fails
+     * 
+     * @param dirPath Path of the directory to delete
+     * @throws Exception if directory deletion fails on any host
+     */
+    void deleteRemoteDirectories(String dirPath) throws Exception {
+        logger.info("Deleting directory '${dirPath}' on ${hosts.size()} hosts")
+        if (!dirPath.contains(SAFETY_PREFIX)) {
+            def errorMsg = "Deletion forbidden: Path '${dirPath}' does not 
contain safety prefix '${SAFETY_PREFIX}'"
+            logger.error(errorMsg)
+            throw new SecurityException(errorMsg);
+        }
+
+        hosts.each { host ->
+            logger.info("Processing host: ${host}")
+            def command = "rm -rf ${escapePath(dirPath)}"
+            def execResult = executeSshCommand(host, command)
+            
+            if (execResult.timedOut) {
+                def errorMsg = "Timeout deleting directory on ${host} 
(${timeout}ms)"
+                logger.error(errorMsg)
+                throw new Exception(errorMsg)
+            }
+            
+            if (execResult.exitCode != 0) {
+                def errorMsg = "Failed to delete directory on ${host} (exit 
code: ${execResult.exitCode}): ${execResult.error}"
+                logger.error(errorMsg)
+                throw new Exception(errorMsg)
+            }
+        }
+        
+        logger.info("Successfully deleted directory '${dirPath}' on all hosts")
+    }
+
+    private Map executeSshCommand(String host, String command) {
+        def sshCommand = "ssh -p ${port} ${username}@${host} '${command}'"
+        return executeLocalCommand(sshCommand)
+    }
+
+    private Map executeLocalCommand(String command) {
+        def result = [
+            exitCode: -1,
+            stdout: '',
+            stderr: '',
+            timedOut: false
+        ]
+
+        try {
+            logger.debug("Executing command: ${command}")
+            Process process = new ProcessBuilder('/bin/sh', '-c', command)
+                .redirectErrorStream(false)
+                .start()
+
+            boolean completed = process.waitFor(timeout, TimeUnit.MILLISECONDS)
+
+            if (!completed) {
+                process.destroyForcibly()
+                result.timedOut = true
+                logger.warn("Command timed out after ${timeout}ms: ${command}")
+                return result
+            }
+
+            result.exitCode = process.exitValue()
+            result.stdout = process.inputStream.text.trim()
+            result.stderr = process.errorStream.text.trim()
+
+        } catch (Exception e) {
+            result.stderr = e.message
+            logger.error("Error executing command: ${e.message}", e)
+        }
+        
+        return result
+    }
+
+    private String escapePath(String path) {
+        return "'${path.replace("'", "'\"'\"'")}'"
+    }
+}
+
+class UniquePathGenerator {
+    private static final SecureRandom random = new SecureRandom()
+    private static final String RANDOM_CHARS = 
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+    private static final int RANDOM_LENGTH = 6  // Adjust length for more/less 
uniqueness
+
+    /**
+     * Generates a short, unique local path with "doristest_" prefix
+     * Format: doristest_&lt;timestamp&gt;_&lt;random_chars&gt;
+     * 
+     * @param baseDir (Optional) Base directory to prepend, null for current 
working directory
+     * @return Unique path string
+     */
+    static String generateUniqueLocalPath(String baseDir, String prefix) {
+        // Generate random characters
+        String randomStr = (1..RANDOM_LENGTH).collect { 
+            RANDOM_CHARS[random.nextInt(RANDOM_CHARS.length())] 
+        }.join()
+        
+        // Create base filename
+        String fileName = "${prefix}_${randomStr}"
+        
+        // Combine with base directory if provided
+        if (baseDir) {
+            return new File(baseDir, fileName).absolutePath
+        }
+        
+        return new File(fileName).absolutePath
+    }
+}
+
+/**
+ * Test helper class for managing temporary files/directories during export 
regression tests.
+ * Automates the creation, collection, and cleanup of temporary directories 
(both local and remote).
+ * <p>Key Responsibilities:
+ * <ul>
+ *   <li>Generate unique local/remote temporary directories using {@link 
UniquePathGenerator}</li>
+ *   <li>Handle remote directory creation via {@link RemoteFileOperator}</li>
+ *   <li>Facilitate file collection from remote hosts to local directory</li>
+ *   <li>Automatically clean up temporary resources (local/remote) via {@link 
AutoCloseable} interface</li>
+ * </ul>
+ *
+ * <p>Lifecycle:
+ * <ol>
+ *   <li>On initialization: Creates unique local and remote directories, 
initializes SSH operator</li>
+ *   <li>During test: Use {@link #collect()} to download files from remote to 
local directory</li>
+ *   <li>On cleanup (via try-with-resources or explicit close()): Deletes 
remote directory and local directory</li>
+ * </ol>
+ *
+ * <p>Usage Example:
+ * <pre>
+ * // Initialize with target hosts
+ * List<String> testHosts = Arrays.asList("192.168.1.10", "192.168.1.11");
+ *
+ * // Use try-with-resources to ensure automatic cleanup
+ * try (ExportTestHelper testHelper = new ExportTestHelper(testHosts)) {
+ *     // Test logic that generates files in testHelper.remoteDir on remote 
hosts
+ *     runExportTest(testHelper.remoteDir);
+ *
+ *     // Collect generated files to local directory
+ *     testHelper.collect();
+ *
+ *     // Verify exported files in testHelper.localDir
+ *     assertExportedFiles(testHelper.localDir);
+ * }
+ * // Cleanup happens automatically here: remote and local dirs are deleted
+ * </pre>
+ */
+class ExportTestHelper implements AutoCloseable {
+    private static final Logger logger = 
LoggerFactory.getLogger(ExportTestHelper.class)
+    String localDir 
+    String remoteDir
+    RemoteFileOperator operator
+    boolean deleteTmpFile = false
+
+    public ExportTestHelper(List<String> hosts) {
+        localDir = UniquePathGenerator.generateUniqueLocalPath("/tmp", 
RemoteFileOperator.SAFETY_PREFIX+"_l")
+        remoteDir = UniquePathGenerator.generateUniqueLocalPath("/tmp", 
RemoteFileOperator.SAFETY_PREFIX)
+
+        operator = new RemoteFileOperator(hosts, "root")
+        operator.createRemoteDirectories(remoteDir)
+        deleteTmpFile = true
+    }
+
+    public void collect() {
+        operator.scpToLocal(remoteDir, localDir)
+    }
+
+    @Override
+    void close() throws Exception {
+        if (deleteTmpFile && operator) {
+            operator.deleteRemoteDirectories(remoteDir)
+            def localDirFile = new File(localDir);
+            if (localDirFile.exists() && 
localDir.contains(RemoteFileOperator.SAFETY_PREFIX)) {
+                if (localDirFile.deleteDir()) {
+                    logger.info("Successfully deleted local temporary 
directory: ${localDir}");
+                }
+            }
+        }
+    }
+}
diff --git 
a/regression-test/suites/nereids_p0/outfile/agg_state/test_outfile_agg_state.groovy
 
b/regression-test/suites/nereids_p0/outfile/agg_state/test_outfile_agg_state.groovy
index e9c51876843..6ac2f90914a 100644
--- 
a/regression-test/suites/nereids_p0/outfile/agg_state/test_outfile_agg_state.groovy
+++ 
b/regression-test/suites/nereids_p0/outfile/agg_state/test_outfile_agg_state.groovy
@@ -16,16 +16,18 @@
 // under the License.
 
 import org.codehaus.groovy.runtime.IOGroovyMethods
-
+import org.apache.doris.regression.util.ExportTestHelper
 import java.nio.charset.StandardCharsets
 import java.nio.file.Files
 import java.nio.file.Paths
 
 suite("test_outfile_agg_state") {
-    def outFilePath = """./tmp/test_outfile_agg_state"""
-    File path = new File(outFilePath)
-    path.deleteDir()
-    path.mkdirs()
+    def hosts = []
+    List<List<Object>> backends = sql("show backends");
+    for (def b : backends) {
+        hosts.add(b[1])
+    }
+    ExportTestHelper testHelper = new ExportTestHelper(hosts)
 
     sql "set enable_agg_state=true"
     sql "DROP TABLE IF EXISTS a_table"
@@ -45,7 +47,8 @@ suite("test_outfile_agg_state") {
 
     qt_test "select k1,max_by_merge(k2),group_concat_merge(k3) from a_table 
group by k1 order by k1;"
 
-    sql """select * from a_table into outfile 
"file://${path.getAbsolutePath()}/tmp" FORMAT AS PARQUET;"""
+    sql """select * from a_table into outfile 
"file://${testHelper.remoteDir}/e_" FORMAT AS PARQUET;"""
+    testHelper.collect()
 
     sql "DROP TABLE IF EXISTS a_table2"
     sql """
@@ -59,10 +62,12 @@ suite("test_outfile_agg_state") {
     properties("replication_num" = "1");
     """
 
-    def filePath=path.getAbsolutePath()+"/tmp*"
+    def filePath=testHelper.localDir+"/*"
     cmd """
     curl --location-trusted -u 
${context.config.jdbcUser}:${context.config.jdbcPassword} -H "format:PARQUET" 
-H "Expect:100-continue" -T ${filePath} -XPUT 
http://${context.config.feHttpAddress}/api/regression_test_nereids_p0_outfile_agg_state/a_table2/_stream_load
     """
     Thread.sleep(10000)
     qt_test "select k1,max_by_merge(k2),group_concat_merge(k3) from a_table2 
group by k1 order by k1;"
+
+    testHelper.close()
 }
diff --git 
a/regression-test/suites/nereids_p0/outfile/agg_state_array/test_outfile_agg_array.groovy
 
b/regression-test/suites/nereids_p0/outfile/agg_state_array/test_outfile_agg_array.groovy
index a7d0992f47c..e7908dd4df6 100644
--- 
a/regression-test/suites/nereids_p0/outfile/agg_state_array/test_outfile_agg_array.groovy
+++ 
b/regression-test/suites/nereids_p0/outfile/agg_state_array/test_outfile_agg_array.groovy
@@ -16,16 +16,19 @@
 // under the License.
 
 import org.codehaus.groovy.runtime.IOGroovyMethods
+import org.apache.doris.regression.util.ExportTestHelper
 
 import java.nio.charset.StandardCharsets
 import java.nio.file.Files
 import java.nio.file.Paths
 
 suite("test_outfile_agg_state_array") {
-    def outFilePath = """./tmp/test_outfile_agg_state_array"""
-    File path = new File(outFilePath)
-    path.deleteDir()
-    path.mkdirs()
+    def hosts = []
+    List<List<Object>> backends = sql("show backends");
+    for (def b : backends) {
+        hosts.add(b[1])
+    }
+    ExportTestHelper testHelper = new ExportTestHelper(hosts)
 
     sql "set enable_agg_state=true"
     sql "DROP TABLE IF EXISTS a_table"
@@ -44,7 +47,8 @@ suite("test_outfile_agg_state_array") {
 
     qt_test "select k1,array_agg_merge(k2) from a_table group by k1 order by 
k1;"
 
-    sql """select * from a_table into outfile 
"file://${path.getAbsolutePath()}/tmp" FORMAT AS PARQUET;"""
+    sql """select * from a_table into outfile 
"file://${testHelper.remoteDir}/tmp_" FORMAT AS PARQUET;"""
+    testHelper.collect()
 
     sql "DROP TABLE IF EXISTS a_table2"
     sql """
@@ -58,10 +62,11 @@ suite("test_outfile_agg_state_array") {
     properties("replication_num" = "1");
     """
 
-    def filePath=path.getAbsolutePath()+"/tmp*"
+    def filePath=testHelper.localDir+"/tmp_*"
     cmd """
     curl --location-trusted -u 
${context.config.jdbcUser}:${context.config.jdbcPassword} -H "format:PARQUET" 
-H "Expect:100-continue" -T ${filePath} 
http://${context.config.feHttpAddress}/api/regression_test_nereids_p0_outfile_agg_state_array/a_table2/_stream_load
     """
     Thread.sleep(10000)
     qt_test "select k1,max_by_merge(k2),group_concat_merge(k3) from a_table2 
group by k1 order by k1;"
+    testHelper.close()
 }
diff --git 
a/regression-test/suites/nereids_p0/outfile/agg_state_bitmap/test_outfile_agg_state_bitmap.groovy
 
b/regression-test/suites/nereids_p0/outfile/agg_state_bitmap/test_outfile_agg_state_bitmap.groovy
index d46dd7f15ee..fc845f53521 100644
--- 
a/regression-test/suites/nereids_p0/outfile/agg_state_bitmap/test_outfile_agg_state_bitmap.groovy
+++ 
b/regression-test/suites/nereids_p0/outfile/agg_state_bitmap/test_outfile_agg_state_bitmap.groovy
@@ -16,16 +16,19 @@
 // under the License.
 
 import org.codehaus.groovy.runtime.IOGroovyMethods
+import org.apache.doris.regression.util.ExportTestHelper
 
 import java.nio.charset.StandardCharsets
 import java.nio.file.Files
 import java.nio.file.Paths
 
 suite("test_outfile_agg_state_bitmap") {
-    def outFilePath = """./tmp/test_outfile_agg_state_bitmap"""
-    File path = new File(outFilePath)
-    path.deleteDir()
-    path.mkdirs()
+   def hosts = []
+    List<List<Object>> backends = sql("show backends");
+    for (def b : backends) {
+        hosts.add(b[1])
+    }
+    ExportTestHelper testHelper = new ExportTestHelper(hosts)
 
     sql "set enable_agg_state=true"
     sql "DROP TABLE IF EXISTS a_table"
@@ -44,7 +47,8 @@ suite("test_outfile_agg_state_bitmap") {
 
     qt_test "select k1,bitmap_to_string(bitmap_union_merge(k2)) from a_table 
group by k1 order by k1;"
 
-    sql """select * from a_table into outfile 
"file://${path.getAbsolutePath()}/tmp" FORMAT AS PARQUET;"""
+    sql """select * from a_table into outfile 
"file://${testHelper.remoteDir}/tmp_" FORMAT AS PARQUET;"""
+    testHelper.collect()
 
     sql "DROP TABLE IF EXISTS a_table2"
     sql """
@@ -57,10 +61,11 @@ suite("test_outfile_agg_state_bitmap") {
     properties("replication_num" = "1");
     """
 
-    def filePath=path.getAbsolutePath()+"/tmp*"
+    def filePath=testHelper.localDir+"/tmp_*"
     cmd """
     curl --location-trusted -u 
${context.config.jdbcUser}:${context.config.jdbcPassword} -H "format:PARQUET" 
-H "Expect:100-continue" -T ${filePath} 
http://${context.config.feHttpAddress}/api/regression_test_nereids_p0_outfile_agg_state_bitmap/a_table2/_stream_load
     """
     Thread.sleep(10000)
     qt_test "select k1,bitmap_to_string(bitmap_union_merge(k2)) from a_table 
group by k1 order by k1;"
+    testHelper.close()
 }
diff --git 
a/regression-test/suites/nereids_p0/outfile/hll/test_outfile_hll.groovy 
b/regression-test/suites/nereids_p0/outfile/hll/test_outfile_hll.groovy
index 4d8ff939dba..ab7d7616f1b 100644
--- a/regression-test/suites/nereids_p0/outfile/hll/test_outfile_hll.groovy
+++ b/regression-test/suites/nereids_p0/outfile/hll/test_outfile_hll.groovy
@@ -16,6 +16,7 @@
 // under the License.
 
 import org.codehaus.groovy.runtime.IOGroovyMethods
+import org.apache.doris.regression.util.ExportTestHelper
 
 import java.nio.charset.StandardCharsets
 import java.nio.file.Files
@@ -23,10 +24,13 @@ import java.nio.file.Paths
 
 suite("test_outfile_hll") {
     sql "set return_object_data_as_binary=true"
-    def outFilePath = """./tmp/test_outfile_hll"""
-    File path = new File(outFilePath)
-    path.deleteDir()
-    path.mkdirs()
+    
+    def hosts = []
+    List<List<Object>> backends = sql("show backends");
+    for (def b : backends) {
+        hosts.add(b[1])
+    }
+    ExportTestHelper testHelper = new ExportTestHelper(hosts)
 
     sql "DROP TABLE IF EXISTS h_table"
     sql """
@@ -45,7 +49,8 @@ suite("test_outfile_hll") {
 
     qt_test "select k1,hll_union_agg(k2) from h_table group by k1 order by k1;"
 
-    sql """select k1, cast(hll_to_base64(k2) as string) as tmp from h_table 
into outfile "file://${path.getAbsolutePath()}/tmp" FORMAT AS PARQUET;"""
+    sql """select k1, cast(hll_to_base64(k2) as string) as tmp from h_table 
into outfile "file://${testHelper.remoteDir}/tmp_" FORMAT AS PARQUET;"""
+    testHelper.collect()
 
     sql "DROP TABLE IF EXISTS h_table2"
     sql """
@@ -58,10 +63,12 @@ suite("test_outfile_hll") {
     properties("replication_num" = "1");
     """
 
-    def filePath=path.getAbsolutePath()+"/tmp*"
+    def filePath=testHelper.localDir+"/tmp_*"
     cmd """
     curl --location-trusted -u 
${context.config.jdbcUser}:${context.config.jdbcPassword} -H "columns: k1, tmp, 
k2=hll_from_base64(tmp)" -H "format:PARQUET" -H "Expect:100-continue" -T 
${filePath} -XPUT 
http://${context.config.feHttpAddress}/api/regression_test_nereids_p0_outfile_hll/h_table2/_stream_load
     """
     Thread.sleep(10000)
     qt_test "select k1,hll_union_agg(k2) from h_table2 group by k1 order by 
k1;"
+
+    testHelper.close()
 }
diff --git 
a/regression-test/suites/nereids_p0/outfile/quantile_state/test_outfile_quantile_state.groovy
 
b/regression-test/suites/nereids_p0/outfile/quantile_state/test_outfile_quantile_state.groovy
index 5b9aee31c5e..3108a4f7715 100644
--- 
a/regression-test/suites/nereids_p0/outfile/quantile_state/test_outfile_quantile_state.groovy
+++ 
b/regression-test/suites/nereids_p0/outfile/quantile_state/test_outfile_quantile_state.groovy
@@ -16,18 +16,22 @@
 // under the License.
 
 import org.codehaus.groovy.runtime.IOGroovyMethods
+import org.apache.doris.regression.util.ExportTestHelper
 
 import java.nio.charset.StandardCharsets
 import java.nio.file.Files
 import java.nio.file.Paths
 
 suite("test_outfile_quantile_state") {
+    def hosts = []
+    List<List<Object>> backends = sql("show backends");
+    for (def b : backends) {
+        hosts.add(b[1])
+    }
+    ExportTestHelper testHelper = new ExportTestHelper(hosts)
+
     sql "set enable_agg_state=true"
     sql "set return_object_data_as_binary=true"
-    def outFilePath = """./tmp/test_outfile_quantile_state"""
-    File path = new File(outFilePath)
-    path.deleteDir()
-    path.mkdirs()
 
     sql "DROP TABLE IF EXISTS q_table"
     sql """
@@ -46,7 +50,8 @@ suite("test_outfile_quantile_state") {
 
     qt_test "select k1,quantile_percent(quantile_union(k2),0.5) from q_table 
group by k1 order by k1;"
 
-    sql """select k1, quantile_union_state(k2) as k2 from q_table into outfile 
"file://${path.getAbsolutePath()}/tmp" FORMAT AS PARQUET;"""
+    sql """select k1, quantile_union_state(k2) as k2 from q_table into outfile 
"file://${testHelper.remoteDir}/tmp_" FORMAT AS PARQUET;"""
+    testHelper.collect()
 
     sql "DROP TABLE IF EXISTS q_table2"
     sql """
@@ -59,10 +64,12 @@ suite("test_outfile_quantile_state") {
     properties("replication_num" = "1");
     """
 
-    def filePath=path.getAbsolutePath()+"/tmp*"
+    def filePath=testHelper.localDir+"/tmp_*"
     cmd """
     curl --location-trusted -u 
${context.config.jdbcUser}:${context.config.jdbcPassword} -H "format:PARQUET" 
-H "Expect:100-continue" -T ${filePath} 
http://${context.config.feHttpAddress}/api/regression_test_nereids_p0_outfile_quantile_state/q_table2/_stream_load
     """
     Thread.sleep(10000)
     qt_test "select k1,quantile_percent(quantile_union_merge(k2),0.5) from 
q_table2 group by k1 order by k1;"
+
+    testHelper.close()
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to