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

csun5285 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 56a9fa7a093 [improvement](beut) add a temp file framework (#68494)
56a9fa7a093 is described below

commit 56a9fa7a093952b7bc7cb60a3ba92fe73755ecd6
Author: yiguolei <[email protected]>
AuthorDate: Fri Sep 25 15:21:12 2026 +0800

    [improvement](beut) add a temp file framework (#68494)
    
    ### What problem does this PR solve?
    
    Issue Number: close #xxx
    
    Related PR: #xxx
    
    Problem Summary:
    
    Currently, many be ut will create a temp file. But if two developers are
    running beut concurrently on same machine, then then will try to access
    the same file. For example, one process under user A may create temp
    file /tmp/a and another process may want to delete it. In this case,
    another developer will failed because the process does not have the
    correct privilege.
    
    I add temp dir framework to create temp file dir randomly to avoid this
    problem.
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test <!-- At least one of them must be included. -->
        - [ ] Regression test
        - [ ] Unit Test
        - [ ] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    - [ ] This is a refactor/code format and no logic has been changed.
            - [ ] Previous test can cover this change.
            - [ ] No code files have been changed.
            - [ ] Other reason <!-- Add your reason?  -->
    
    - Behavior changed:
        - [ ] No.
        - [ ] Yes. <!-- Explain the behavior change -->
    
    - Does this need documentation?
        - [ ] No.
    - [ ] Yes. <!-- Add document PR link here. eg:
    https://github.com/apache/doris-website/pull/1214 -->
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label <!-- Add branch pick label that this PR
    should merge into -->
---
 .../format/table/iceberg/iceberg_reader_test.cpp   |  7 ++-
 be/test/format_v2/json/json_reader_test.cpp        |  6 +--
 be/test/format_v2/table_reader_test.cpp            |  8 ++-
 .../kuromoji/kuromoji_dictionary_builder_test.cpp  | 29 ++++++-----
 .../analyzer/kuromoji/kuromoji_dictionary_test.cpp | 16 +++---
 .../analyzer/kuromoji/kuromoji_real_dict_test.cpp  | 13 +++--
 .../analyzer/kuromoji/kuromoji_viterbi_test.cpp    | 20 ++++----
 be/test/testutil/scoped_temp_dir.h                 | 60 ++++++++++++++++++++++
 8 files changed, 111 insertions(+), 48 deletions(-)

diff --git a/be/test/format/table/iceberg/iceberg_reader_test.cpp 
b/be/test/format/table/iceberg/iceberg_reader_test.cpp
index 0dee309fb7f..d736d2429b9 100644
--- a/be/test/format/table/iceberg/iceberg_reader_test.cpp
+++ b/be/test/format/table/iceberg/iceberg_reader_test.cpp
@@ -72,6 +72,7 @@
 #include "runtime/descriptors.h"
 #include "runtime/runtime_state.h"
 #include "storage/olap_scan_common.h"
+#include "testutil/scoped_temp_dir.h"
 #include "util/timezone_utils.h"
 
 namespace doris {
@@ -1753,10 +1754,8 @@ TEST_F(IcebergReaderTest, 
v1_position_delete_consumes_delete_file_size) {
 
 // An inflated size must break the read, proving the v1 equality-delete path 
consumes the FE file_size.
 TEST_F(IcebergReaderTest, v1_equality_delete_consumes_delete_file_size) {
-    const auto test_dir = std::filesystem::temp_directory_path() / 
"doris_v1_eq_delete_size_test";
-    std::filesystem::remove_all(test_dir);
-    std::filesystem::create_directories(test_dir);
-    const auto delete_file_path = (test_dir / 
"equality-delete.parquet").string();
+    const doris::test::ScopedTempDirectory 
test_dir("doris_v1_eq_delete_size_test");
+    const auto delete_file_path = (test_dir.path() / 
"equality-delete.parquet").string();
     write_iceberg_int_equality_delete_parquet_file(delete_file_path, "id", 0, 
2);
 
     RuntimeState runtime_state = RuntimeState(TQueryOptions(), 
TQueryGlobals());
diff --git a/be/test/format_v2/json/json_reader_test.cpp 
b/be/test/format_v2/json/json_reader_test.cpp
index e8d9aec40af..c55bb3f512e 100644
--- a/be/test/format_v2/json/json_reader_test.cpp
+++ b/be/test/format_v2/json/json_reader_test.cpp
@@ -45,6 +45,7 @@
 #include "runtime/descriptors.h"
 #include "runtime/runtime_profile.h"
 #include "testutil/mock/mock_runtime_state.h"
+#include "testutil/scoped_temp_dir.h"
 
 namespace doris::format::json {
 namespace {
@@ -136,9 +137,8 @@ std::unique_ptr<io::FileDescription> file_description(const 
std::string& path) {
 }
 
 std::filesystem::path write_json_file(const std::string& name, const 
std::string& content) {
-    const auto test_dir = std::filesystem::temp_directory_path() / 
"doris_format_v2_json_reader";
-    std::filesystem::create_directories(test_dir);
-    const auto file_path = test_dir / name;
+    static const doris::test::ScopedTempDirectory 
test_dir("doris_format_v2_json_reader");
+    const auto file_path = test_dir.path() / name;
     std::ofstream out(file_path);
     out << content;
     return file_path;
diff --git a/be/test/format_v2/table_reader_test.cpp 
b/be/test/format_v2/table_reader_test.cpp
index e4741a63467..334ea1ce72b 100644
--- a/be/test/format_v2/table_reader_test.cpp
+++ b/be/test/format_v2/table_reader_test.cpp
@@ -66,6 +66,7 @@
 #include "runtime/runtime_profile.h"
 #include "runtime/runtime_state.h"
 #include "storage/segment/condition_cache.h"
+#include "testutil/scoped_temp_dir.h"
 
 namespace doris::format {
 namespace {
@@ -2095,11 +2096,8 @@ TEST(TableReaderTest, 
PendingRuntimeFilterDisablesTableLevelCount) {
 }
 
 TEST(TableReaderTest, CountStarFallbackKeepsLateRuntimeFilterCarrierValues) {
-    const auto test_dir =
-            std::filesystem::temp_directory_path() / 
"doris_table_reader_count_star_late_rf_test";
-    std::filesystem::remove_all(test_dir);
-    std::filesystem::create_directories(test_dir);
-    const auto file_path = (test_dir / "split.parquet").string();
+    const doris::test::ScopedTempDirectory 
test_dir("doris_table_reader_count_star_late_rf_test");
+    const auto file_path = (test_dir.path() / "split.parquet").string();
     write_int_pair_parquet_file(file_path, {1, 2, 3, 4, 5, 6}, {10, 20, 30, 
40, 50, 60},
                                 {"one", "two", "three", "four", "five", 
"six"}, 2);
 
diff --git 
a/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_dictionary_builder_test.cpp
 
b/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_dictionary_builder_test.cpp
index 8ee710dc597..86cd6f68d82 100644
--- 
a/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_dictionary_builder_test.cpp
+++ 
b/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_dictionary_builder_test.cpp
@@ -23,6 +23,7 @@
 #include <string>
 
 #include "storage/index/inverted/analyzer/kuromoji/dict/kuromoji_dict_format.h"
+#include "testutil/scoped_temp_dir.h"
 
 namespace doris::segment_v2::inverted_index::kuromoji {
 
@@ -31,44 +32,48 @@ static bool file_nonempty(const std::string& p) {
     return ::stat(p.c_str(), &st) == 0 && st.st_size > 
static_cast<off_t>(sizeof(KmjFileHeader));
 }
 
-TEST(KuromojiDictionaryBuilderTest, WritesFourFiles) {
-    std::string dir = std::string(::testing::TempDir()) + "/kmj_build_test";
-    ::mkdir(dir.c_str(), 0755);
+class KuromojiDictionaryBuilderTest : public ::testing::Test {
+protected:
+    doris::test::ScopedTempDirectory _temp_dir {"kuromoji_builder"};
+    std::string _dir = _temp_dir.path().string();
+};
 
+TEST_F(KuromojiDictionaryBuilderTest, WritesFourFiles) {
     SystemDictInput sys;
     sys.surfaces.push_back({"\xE6\x9D\xB1", {{1, 1, 100, "POS,East"}}});       
      // 東
     sys.surfaces.push_back({"\xE6\x9D\xB1\xE4\xBA\xAC", {{2, 2, 50, 
"POS,Tokyo"}}}); // 東京
-    ASSERT_TRUE(KuromojiDictionaryBuilder::write_system(dir + "/system.bin", 
sys).ok());
+    auto st = KuromojiDictionaryBuilder::write_system(_dir + "/system.bin", 
sys);
+    ASSERT_TRUE(st.ok()) << st.to_string();
 
     MatrixInput m;
     m.forward_size = 3;
     m.backward_size = 3;
     m.cells.assign(9, 7);
-    ASSERT_TRUE(KuromojiDictionaryBuilder::write_matrix(dir + "/matrix.bin", 
m).ok());
+    ASSERT_TRUE(KuromojiDictionaryBuilder::write_matrix(_dir + "/matrix.bin", 
m).ok());
 
     CharDefInput cd;
     cd.catmap.fill(CAT_DEFAULT);
     cd.catmap[0x6771] = CAT_KANJI; // 東
     cd.defs.assign(CAT_CLASS_COUNT, CategoryDef {0, 0, 0});
     cd.defs[CAT_DEFAULT] = CategoryDef {1, 1, 0};
-    ASSERT_TRUE(KuromojiDictionaryBuilder::write_chardef(dir + "/chardef.bin", 
cd).ok());
+    ASSERT_TRUE(KuromojiDictionaryBuilder::write_chardef(_dir + 
"/chardef.bin", cd).ok());
 
     UnkDictInput unk;
     unk.per_category.resize(CAT_CLASS_COUNT);
     unk.per_category[CAT_DEFAULT].push_back({5, 5, 4769, "SYMBOL"});
-    ASSERT_TRUE(KuromojiDictionaryBuilder::write_unkdict(dir + "/unkdict.bin", 
unk).ok());
+    ASSERT_TRUE(KuromojiDictionaryBuilder::write_unkdict(_dir + 
"/unkdict.bin", unk).ok());
 
-    EXPECT_TRUE(file_nonempty(dir + "/system.bin"));
-    EXPECT_TRUE(file_nonempty(dir + "/matrix.bin"));
-    EXPECT_TRUE(file_nonempty(dir + "/chardef.bin"));
-    EXPECT_TRUE(file_nonempty(dir + "/unkdict.bin"));
+    EXPECT_TRUE(file_nonempty(_dir + "/system.bin"));
+    EXPECT_TRUE(file_nonempty(_dir + "/matrix.bin"));
+    EXPECT_TRUE(file_nonempty(_dir + "/chardef.bin"));
+    EXPECT_TRUE(file_nonempty(_dir + "/unkdict.bin"));
 
     // matrix rejects a wrong cell count.
     MatrixInput bad;
     bad.forward_size = 2;
     bad.backward_size = 2;
     bad.cells.assign(3, 0);
-    EXPECT_FALSE(KuromojiDictionaryBuilder::write_matrix(dir + "/bad.bin", 
bad).ok());
+    EXPECT_FALSE(KuromojiDictionaryBuilder::write_matrix(_dir + "/bad.bin", 
bad).ok());
 }
 
 } // namespace doris::segment_v2::inverted_index::kuromoji
diff --git 
a/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_dictionary_test.cpp 
b/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_dictionary_test.cpp
index 4c7543eff47..8488529fe9d 100644
--- 
a/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_dictionary_test.cpp
+++ 
b/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_dictionary_test.cpp
@@ -18,10 +18,10 @@
 #include "storage/index/inverted/analyzer/kuromoji/dict/kuromoji_dictionary.h"
 
 #include <gtest/gtest.h>
-#include <sys/stat.h>
 
 #include <cstddef>
 #include <cstring>
+#include <filesystem>
 #include <fstream>
 #include <iterator>
 #include <memory>
@@ -30,21 +30,21 @@
 
 #include "storage/index/inverted/analyzer/kuromoji/dict/kuromoji_dict_format.h"
 #include 
"storage/index/inverted/analyzer/kuromoji/dict/kuromoji_dictionary_builder.h"
+#include "testutil/scoped_temp_dir.h"
 
 namespace doris::segment_v2::inverted_index::kuromoji {
 
 class KuromojiDictionaryTest : public ::testing::Test {
 protected:
-    std::string _dir;
+    doris::test::ScopedTempDirectory _temp_dir {"kuromoji_dictionary"};
+    std::string _dir = _temp_dir.path().string();
 
     void SetUp() override {
-        _dir = std::string(::testing::TempDir()) + "/kmj_dict_rt";
-        ::mkdir(_dir.c_str(), 0755);
-
         SystemDictInput sys;
         sys.surfaces.push_back({"\xE6\x9D\xB1", {{1, 1, 100, "f-east"}}});     
        // 東
         sys.surfaces.push_back({"\xE6\x9D\xB1\xE4\xBA\xAC", {{2, 2, 50, 
"f-tokyo"}}}); // 東京
-        ASSERT_TRUE(KuromojiDictionaryBuilder::write_system(_dir + 
"/system.bin", sys).ok());
+        auto st = KuromojiDictionaryBuilder::write_system(_dir + 
"/system.bin", sys);
+        ASSERT_TRUE(st.ok()) << st.to_string();
 
         MatrixInput m;
         m.forward_size = 3;
@@ -108,8 +108,8 @@ TEST_F(KuromojiDictionaryTest, LoadAndQuery) {
 
 TEST_F(KuromojiDictionaryTest, RejectsMissingFiles) {
     // Point at a directory with no dictionary files -> must fail, not crash.
-    std::string bad = std::string(::testing::TempDir()) + "/kmj_dict_bad";
-    ::mkdir(bad.c_str(), 0755);
+    const std::string bad = _dir + "/missing_files";
+    ASSERT_TRUE(std::filesystem::create_directory(bad));
     std::unique_ptr<KuromojiDictionary> dict;
     EXPECT_FALSE(KuromojiDictionary::load(bad, &dict).ok());
 }
diff --git 
a/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_real_dict_test.cpp 
b/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_real_dict_test.cpp
index 5f3950f2809..c140a669424 100644
--- 
a/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_real_dict_test.cpp
+++ 
b/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_real_dict_test.cpp
@@ -20,6 +20,7 @@
 
 #include <algorithm>
 #include <cstdlib>
+#include <filesystem>
 #include <iostream>
 #include <memory>
 #include <string>
@@ -31,6 +32,7 @@
 #include "storage/index/inverted/analyzer/kuromoji/dict/kuromoji_dictionary.h"
 #include "storage/index/inverted/analyzer/kuromoji/kuromoji_viterbi.h"
 #include "storage/index/inverted/inverted_index_parser.h"
+#include "util/defer_op.h"
 
 // End-to-end against the REAL IPADIC dictionary generated under 
be/dict/kuromoji.
 // Skips if the dictionary has not been generated on this host (e.g. plain CI),
@@ -125,11 +127,14 @@ TEST(KuromojiRealDictTest, 
AnalyzerSegmentsViaBuiltinParser) {
     if (!real_dict_present()) {
         GTEST_SKIP() << "real IPADIC dictionary not generated at " << 
real_dict_dir();
     }
-    const char* home = std::getenv("DORIS_HOME");
     const std::string saved = doris::config::inverted_index_dict_path;
-    doris::config::inverted_index_dict_path =
-            std::string(home != nullptr ? home : ".") + "/be/dict";
     const bool saved_enable = doris::config::enable_kuromoji_analyzer;
+    Defer restore_config {[&] {
+        doris::config::inverted_index_dict_path = saved;
+        doris::config::enable_kuromoji_analyzer = saved_enable;
+    }};
+    doris::config::inverted_index_dict_path =
+            std::filesystem::path(real_dict_dir()).parent_path().string();
     doris::config::enable_kuromoji_analyzer = true;
 
     std::vector<std::string> toks;
@@ -152,8 +157,6 @@ TEST(KuromojiRealDictTest, 
AnalyzerSegmentsViaBuiltinParser) {
             toks.emplace_back(t.termBuffer<char>(), 
static_cast<std::size_t>(t.termLength<char>()));
         }
     }
-    doris::config::inverted_index_dict_path = saved;
-    doris::config::enable_kuromoji_analyzer = saved_enable;
 
     std::cout << "analyzer tokens for 東京都に住んでいます:";
     for (const auto& t : toks) {
diff --git 
a/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_viterbi_test.cpp 
b/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_viterbi_test.cpp
index 553651aa5df..c2ec9aa5a0f 100644
--- a/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_viterbi_test.cpp
+++ b/be/test/storage/index/inverted/analyzer/kuromoji/kuromoji_viterbi_test.cpp
@@ -18,7 +18,6 @@
 #include "storage/index/inverted/analyzer/kuromoji/kuromoji_viterbi.h"
 
 #include <gtest/gtest.h>
-#include <sys/stat.h>
 
 #include <memory>
 #include <string>
@@ -30,6 +29,7 @@
 #include "storage/index/inverted/analyzer/kuromoji/dict/kuromoji_dict_format.h"
 #include "storage/index/inverted/analyzer/kuromoji/dict/kuromoji_dictionary.h"
 #include 
"storage/index/inverted/analyzer/kuromoji/dict/kuromoji_dictionary_builder.h"
+#include "testutil/scoped_temp_dir.h"
 
 namespace doris::segment_v2::inverted_index::kuromoji {
 
@@ -40,20 +40,19 @@ static const std::string FU = "\xE5\xBA\x9C";
 
 class KuromojiViterbiTest : public ::testing::Test {
 protected:
-    std::string _dir;
+    doris::test::ScopedTempDirectory _temp_dir {"kuromoji_viterbi"};
+    std::string _dir = _temp_dir.path().string();
     std::unique_ptr<KuromojiDictionary> _dict;
 
     void SetUp() override {
-        _dir = std::string(::testing::TempDir()) + "/kmj_viterbi";
-        ::mkdir(_dir.c_str(), 0755);
-
         // Lexicon: single chars cost 1000 each; the compound "東京" costs 100.
         // With an all-zero connection matrix, the min-cost path must pick 
"東京".
         SystemDictInput sys;
         sys.surfaces.push_back({TOU, {{1, 1, 1000, "noun,East"}}});
         sys.surfaces.push_back({KYO, {{1, 1, 1000, "noun,Capital"}}});
         sys.surfaces.push_back({TOU + KYO, {{1, 1, 100, "noun,Tokyo"}}});
-        ASSERT_TRUE(KuromojiDictionaryBuilder::write_system(_dir + 
"/system.bin", sys).ok());
+        auto st = KuromojiDictionaryBuilder::write_system(_dir + 
"/system.bin", sys);
+        ASSERT_TRUE(st.ok()) << st.to_string();
 
         MatrixInput m;
         m.forward_size = 2; // context ids: 0 = BOS/EOS, 1 = word
@@ -159,13 +158,11 @@ static const std::string KAWA = "\xE5\xB7\x9D";
 // short (<= 2 kanji) compounds are never split.
 class KuromojiSearchModeTest : public ::testing::Test {
 protected:
-    std::string _dir;
+    doris::test::ScopedTempDirectory _temp_dir {"kuromoji_search"};
+    std::string _dir = _temp_dir.path().string();
     std::unique_ptr<KuromojiDictionary> _dict;
 
     void SetUp() override {
-        _dir = std::string(::testing::TempDir()) + "/kmj_search";
-        ::mkdir(_dir.c_str(), 0755);
-
         // Single kanji cost 1000 each; compounds "東京都" and "山川" cost 100.
         SystemDictInput sys;
         sys.surfaces.push_back({TOU, {{1, 1, 1000, "noun"}}});
@@ -175,7 +172,8 @@ protected:
         sys.surfaces.push_back({KAWA, {{1, 1, 1000, "noun"}}});
         sys.surfaces.push_back({TOU + KYO + TO, {{1, 1, 100, "noun"}}});
         sys.surfaces.push_back({YAMA + KAWA, {{1, 1, 100, "noun"}}});
-        ASSERT_TRUE(KuromojiDictionaryBuilder::write_system(_dir + 
"/system.bin", sys).ok());
+        auto st = KuromojiDictionaryBuilder::write_system(_dir + 
"/system.bin", sys);
+        ASSERT_TRUE(st.ok()) << st.to_string();
 
         MatrixInput m;
         m.forward_size = 2;
diff --git a/be/test/testutil/scoped_temp_dir.h 
b/be/test/testutil/scoped_temp_dir.h
new file mode 100644
index 00000000000..0c5e02a95ed
--- /dev/null
+++ b/be/test/testutil/scoped_temp_dir.h
@@ -0,0 +1,60 @@
+// 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.
+
+#pragma once
+
+#include <unistd.h>
+
+#include <cerrno>
+#include <filesystem>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <utility>
+
+namespace doris::test {
+
+class ScopedTempDirectory {
+public:
+    explicit ScopedTempDirectory(std::string_view prefix) {
+        std::string path_template =
+                (std::filesystem::temp_directory_path() / (std::string(prefix) 
+ "_XXXXXX"))
+                        .string();
+        if (::mkdtemp(path_template.data()) == nullptr) {
+            const int error = errno;
+            throw std::filesystem::filesystem_error(
+                    "cannot create temporary directory", path_template,
+                    std::error_code(error, std::generic_category()));
+        }
+        _path = std::move(path_template);
+    }
+
+    ~ScopedTempDirectory() {
+        std::error_code error;
+        std::filesystem::remove_all(_path, error);
+    }
+
+    ScopedTempDirectory(const ScopedTempDirectory&) = delete;
+    ScopedTempDirectory& operator=(const ScopedTempDirectory&) = delete;
+
+    const std::filesystem::path& path() const { return _path; }
+
+private:
+    std::filesystem::path _path;
+};
+
+} // namespace doris::test


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

Reply via email to